edu.hawaii.soest.kilonalu.ctd.CTDSource.java Source code

Java tutorial

Introduction

Here is the source code for edu.hawaii.soest.kilonalu.ctd.CTDSource.java

Source

/**
 *  Copyright: 2007 Regents of the University of Hawaii and the
 *             School of Ocean and Earth Science and Technology
 *    Purpose: To convert a Seacat ASCII data source into RBNB Data Turbine
 *             frames for archival and realtime access.
 *    Authors: Christopher Jones
 *
 * $HeadURL$
 * $LastChangedDate$
 * $LastChangedBy$
 * $LastChangedRevision$
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */
package edu.hawaii.soest.kilonalu.ctd;

import com.rbnb.sapi.ChannelMap;
import com.rbnb.sapi.Source;
import com.rbnb.sapi.SAPIException;

import edu.hawaii.soest.kilonalu.ctd.CTDParser;
import edu.hawaii.soest.kilonalu.utilities.SerialChannel;

import gnu.io.CommPort;
import gnu.io.CommPortIdentifier;
import gnu.io.NoSuchPortException;
import gnu.io.PortInUseException;
import gnu.io.SerialPort;

import java.lang.InterruptedException;
import java.lang.StringBuffer;

import java.io.PrintWriter;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.IOException;

import java.net.InetSocketAddress;
import java.net.UnknownHostException;

import java.nio.ByteBuffer;
import java.nio.channels.ByteChannel;
import java.nio.channels.FileChannel;
import java.nio.channels.SocketChannel;

import java.text.ParseException;
import java.text.SimpleDateFormat;

import java.util.Calendar;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.TimeZone;

import org.apache.commons.cli.Options;
import org.apache.commons.cli.CommandLine;

import org.apache.commons.codec.binary.Hex;

import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.configuration.HierarchicalConfiguration;
import org.apache.commons.configuration.XMLConfiguration;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.dhmp.util.HierarchicalMap;
import org.dhmp.util.BasicHierarchicalMap;

import org.nees.rbnb.RBNBBase;
import org.nees.rbnb.RBNBSource;

/**
 * A simple class used to harvest a decimal ASCII data stream from a Seacat 
 * 16plus CTD) over either 1) a TCP socket connection with a Raven XT cellular 
 * modem and serial2ip converter host or, 2) a local serial port. 
 * The data stream is then converted into RBNB frames 
 * and pushed into the RBNB DataTurbine real time server.  This class extends 
 * org.nees.rbnb.RBNBSource, which in turn extends org.nees.rbnb.RBNBBase, 
 * and therefore follows the API conventions found in the org.nees.rbnb code.  
 *
 * The parsing of the data stream relies on the premise that each sample of data
 * is a comma delimited string of values, and that each sample is terminated
 * by two newline characters (\n\n).  Each line is also prefixed by the pound (#)
 * character.
 *
 */
public class CTDSource extends RBNBSource {

    /*
     *  A default archive mode for the given source connection to the RBNB server.
     * Valid modes include 'append', 'create', 'load' and 'none'.
     */
    private final String DEFAULT_ARCHIVE_MODE = "append";

    /*
     * The mode in which the source interacts with the RBNB archive. Valid modes 
     * include 'append', 'create', 'load' and 'none', however, Kilo Nalu 
     * instruments should append to an archive, which will create one if none 
     * exist.
     *
     * @see setArchiveMode()
     * @see getArchiveMode()
     */
    private String archiveMode = DEFAULT_ARCHIVE_MODE;

    /*
     * The default size of the ByteBuffer used to beffer the TCP stream from the
     * source instrument.
     */
    private int DEFAULT_BUFFER_SIZE = 8192; // 8K

    /**
     * The size of the ByteBuffer used to beffer the TCP stream from the 
     * instrument.
     */
    private int bufferSize = DEFAULT_BUFFER_SIZE;

    /* A default RBNB channel name for the given source instrument */
    private String DEFAULT_RBNB_CHANNEL = "DecimalASCIISampleData";

    /* The name of the RBNB channel for this data stream */
    private String rbnbChannelName = DEFAULT_RBNB_CHANNEL;

    /* A default source IP address for the given source instrument */
    private final String DEFAULT_SOURCE_HOST_NAME = "68.25.65.111";

    /**
     * The domain name or IP address of the host machine that this Source 
     * represents and from which the data will stream. 
     */
    private String sourceHostName = DEFAULT_SOURCE_HOST_NAME;

    /* A default source TCP port for the given source instrument */
    private final int DEFAULT_SOURCE_HOST_PORT = 5111;

    /* The TCP port to connect to on the Source host machine */
    private int sourceHostPort = DEFAULT_SOURCE_HOST_PORT;

    /** The default serial port name used for serial communications */
    private static String DEFAULT_SERIAL_PORT = "/dev/ttyS0";

    /** The  serial port name used for serial communications */
    private String serialPortName = DEFAULT_SERIAL_PORT;

    /* The number of bytes in the ensemble as each byte is read from the stream */
    private int sampleByteCount = 0;

    /* The Logger instance used to log system messages */
    static Log logger = LogFactory.getLog(CTDSource.class);

    protected int state = 0;

    private boolean readyToStream = false;

    private Thread streamingThread;

    /* A field indicating if the connection is serial or socket */
    private String connectionType;

    /* A field indicating if the CTD query output type is xml or text */
    private String outputType;

    /* A boolean field indicating if a command has been sent to the instrument */
    private boolean sentCommand = false;

    /* The command prefix used to send commands to the instrument */
    private String commandPrefix = "";

    /* The command suffix used to send commands to the instrument */
    private String commandSuffix = "\r\n";

    /* The default sampling interval in samples per hour (i.e every 4 minutes) */
    private final String DEFAULT_SAMPLE_INTERVAL = "15";

    /* The sampling interval in samples per hour */
    private final String sampleInterval = DEFAULT_SAMPLE_INTERVAL;

    /* The command used to set the sampling interval on the instrument */
    private String sampleIntervalCommand = "SampleInterval=";

    /* The command used to start the autonomous sampling on the instrument */
    private String startSamplingCommand = "StartNow";

    /* The command used to stop the autonomous sampling on the instrument */
    private String stopSamplingCommand = "Stop";

    /* The command used to set the observation datetime on the instrument */
    private String setDateTimeCommand = "DateTime="; // use mmddyyyyhhmmss

    /* The legacy command to display status information from the instrument */
    private String displayStatusCommand = "DS";

    /* The legacy command to display calibration information from the instrument */
    private String displayCalibrationCommand = "DCal";

    /* The command to get status information from the instrument */
    private String getStatusCommand = "GetSD";

    /* The command to get configuration information from the instrument */
    private String getConfigurationCommand = "GetCD";

    /* The command to get calibration information from the instrument */
    private String getCalibrationCommand = "GetCC";

    /* The command to get event information from the instrument */
    private String getEventsCommand = "GetED";

    /* The command to get hardware information from the instrument */
    private String getHardwareCommand = "GetHD";

    /* A boolean field that indicates if all CTDParser metadata fields are set */
    private boolean hasMetadata = false;

    /* A boolean field that indicates if sending commands should be enabled */
    private boolean enableSendCommands = false;

    /* A boolean field that indicates if sampling has been stopped on the instrument */
    private boolean samplingIsStopped = false;

    /* The command used to query or command the instrument */
    private String command;

    /* A boolean stating if the instrument clock is synced to NTP time */
    private boolean clockIsSynced = false;

    /* The datetime when the instrument clock was last synced to NTP time */
    private Date clockSyncDate;

    /* The date format for the timestamp applied to instrument clock */
    private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("ddMMyyyyHHmmss");

    /* The timezone used for the sample date */
    private static final TimeZone TZ = TimeZone.getTimeZone("HST");

    /* The socket or file channel used for instrument communication */
    private ByteChannel channel;

    /* The instance of the CTD Parser class used to parse CTD output */
    private CTDParser ctdParser;

    /* The response string used as the output variable from the CTD */
    String responseString;

    /*
     * An internal Thread setting used to specify how long, in milliseconds, the
     * execution of the data streaming Thread should wait before re-executing
     * 
     * @see execute()
     */
    private final int RETRY_INTERVAL = 5000;

    /**
     * Constructor - create an empty instance of the CTDSource object, using
     * default values for the RBNB server name and port, source instrument name
     * and port, archive mode, archive frame size, and cache frame size. 
     */
    public CTDSource() {
    }

    /**
     * Constructor - create an instance of the CTDSource object, using the
     * argument values for the source instrument name and port, and the RBNB 
     * server name and port.  This constructor will use default values for the
     * archive mode, archive frame size, and cache frame size. 
     *
     * @param sourceHostName  the name or IP address of the source instrument
     * @param sourceHostPort  the TCP port of the source host instrument
     * @param serverName      the name or IP address of the RBNB server connection
     * @param serverPort      the TCP port of the RBNB server
     */
    public CTDSource(String sourceHostName, String sourceHostPort, String serverName, String serverPort) {

        setHostName(sourceHostName);
        setHostPort(Integer.parseInt(sourceHostPort));
        setServerName(serverName);
        setServerPort(Integer.parseInt(serverPort));
    }

    /**
     * Constructor - create an instance of the CTDSource object, using the
     * argument values for the source instrument name and port, and the RBNB 
     * server name and port, the archive mode, archive frame size, and cache 
     * frame size.  A frame is created at each call to flush() to an RBNB server,
     * and so the frame sizes below are relative to the number of bytes of data
     * loaded in the ChannelMap that is flushed to the RBNB server.
     *
     * @param sourceHostName   the name or IP address of the source instrument
     * @param sourceHostPort   the TCP port of the source host instrument
     * @param serverName       the name or IP address of the RBNB server 
     * @param serverPort       the TCP port of the RBNB server
     * @param archiveMode      the RBNB archive mode: append, load, create, none
     * @param archiveFrameSize the size, in frames, for the RBNB server to archive
     * @param cacheFrameSize   the size, in frames, for the RBNB server to cache
     * @param rbnbClientName   the unique name of the source RBNB client
     */
    public CTDSource(String sourceHostName, String sourceHostPort, String serverName, String serverPort,
            String archiveMode, int archiveFrameSize, int cacheFrameSize, String rbnbClientName) {

        setHostName(sourceHostName);
        setHostPort(Integer.parseInt(sourceHostPort));
        setServerName(serverName);
        setServerPort(Integer.parseInt(serverPort));
        setArchiveMode(archiveMode);
        setArchiveSize(archiveFrameSize);
        setCacheSize(cacheFrameSize);
        setRBNBClientName(rbnbClientName);
    }

    /**
     * Constructor - create an instance of the CTDSource object, using the
     * argument values for the RBNB server name and port, the archive mode,
     * archive frame size, and cache * frame size. A frame is created at each call
     * to flush() to an RBNB server, * and so the frame sizes below are relative
     * to the number of bytes of data * loaded in the ChannelMap that is flushed
     * to the RBNB server.
     *
     * @param serverName       the name or IP address of the RBNB server 
     * @param serverPort       the TCP port of the RBNB server
     * @param archiveMode      the RBNB archive mode: append, load, create, none
     * @param archiveFrameSize the size, in frames, for the RBNB server to archive
     * @param cacheFrameSize   the size, in frames, for the RBNB server to cache
     * @param rbnbClientName   the unique name of the source RBNB client
     */
    public CTDSource(String serverName, String serverPort, String archiveMode, int archiveFrameSize,
            int cacheFrameSize, String rbnbClientName) {

        setServerName(serverName);
        setServerPort(Integer.parseInt(serverPort));
        setArchiveMode(archiveMode);
        setArchiveSize(archiveFrameSize);
        setCacheSize(cacheFrameSize);
        setRBNBClientName(rbnbClientName);
    }

    /**
     * A method that processes the data object passed and flushes the
     * data to the DataTurbine given the sensor properties in the XMLConfiguration
     * passed in. This method is largely called from a dispatcher that is handling
     * the data streaming, like StorXDispatcher.
     *
     * @param xmlConfig - the XMLConfiguration object containing the list of
     *                    sensor properties
     * @param frameMap  - the parsed data as a HierarchicalMap object
     */
    public boolean process(XMLConfiguration xmlConfig, HierarchicalMap frameMap) {

        logger.debug("CTDSource.process() called.");
        // do not execute the stream if there is no connection
        if (!isConnected())
            return false;

        boolean success = false;

        try {

            // add channels of data that will be pushed to the server.  
            // Each sample will be sent to the Data Turbine as an rbnb frame.  Information
            // on each channel is found in the XMLConfiguration file (email.account.properties.xml)
            // and the StorXParser object (to get the data string)
            ChannelMap rbnbChannelMap = new ChannelMap(); // used to flush channels
            ChannelMap registerChannelMap = new ChannelMap(); // used to register channels
            int channelIndex = 0;

            // this.storXParser = new StorXParser(storXFrame);

            String sensorName = null;
            String sensorSerialNumber = null;
            String sensorDescription = null;
            boolean isImmersed = false;
            String[] calibrationURLs = null;
            String calibrationURL = null;
            String type = null;

            List sensorList = xmlConfig.configurationsAt("account.logger.sensor");

            for (Iterator sIterator = sensorList.iterator(); sIterator.hasNext();) {
                //  
                HierarchicalConfiguration sensorConfig = (HierarchicalConfiguration) sIterator.next();
                sensorSerialNumber = sensorConfig.getString("serialNumber");

                // find the correct sensor configuration properties
                if (sensorSerialNumber.equals(frameMap.get("serialNumber"))) {

                    sensorName = sensorConfig.getString("name");
                    sensorDescription = sensorConfig.getString("description");
                    isImmersed = new Boolean(sensorConfig.getString("isImmersed")).booleanValue();
                    //calibrationURLs = sensorConfig.getStringArray("calibrationURL");
                    type = (String) frameMap.get("type");

                    // Build the RBNB channel map 

                    // get the sample date and convert it to seconds since the epoch
                    Date frameDate = (Date) frameMap.get("date");
                    Calendar frameDateTime = Calendar.getInstance();
                    frameDateTime.setTime(frameDate);
                    double sampleTimeAsSecondsSinceEpoch = (double) (frameDateTime.getTimeInMillis() / 1000);
                    // and create a string formatted date for the given time zone
                    DATE_FORMAT.setTimeZone(TZ);
                    String frameDateAsString = DATE_FORMAT.format(frameDate).toString();

                    // get the sample data from the frame map
                    ByteBuffer rawFrame = (ByteBuffer) frameMap.get("rawFrame");
                    CTDFrame ctdFrame = (CTDFrame) frameMap.get("parsedFrameObject");

                    String sampleString = ctdFrame.getSample();

                    // add the sample timestamp to the rbnb channel map
                    //registerChannelMap.PutTime(sampleTimeAsSecondsSinceEpoch, 0d);
                    rbnbChannelMap.PutTime(sampleTimeAsSecondsSinceEpoch, 0d);

                    // add the BinaryRawSatlanticFrameData channel to the channelMap
                    channelIndex = registerChannelMap.Add("BinaryRawSatlanticFrameData");
                    registerChannelMap.PutUserInfo(channelIndex, "units=none");
                    channelIndex = rbnbChannelMap.Add("BinaryRawSatlanticFrameData");
                    rbnbChannelMap.PutMime(channelIndex, "application/octet-stream");
                    rbnbChannelMap.PutDataAsByteArray(channelIndex, rawFrame.array());

                    // add the DecimalASCIISampleData channel to the channelMap
                    channelIndex = registerChannelMap.Add(getRBNBChannelName());
                    registerChannelMap.PutUserInfo(channelIndex, "units=none");
                    channelIndex = rbnbChannelMap.Add(getRBNBChannelName());
                    rbnbChannelMap.PutMime(channelIndex, "text/plain");
                    rbnbChannelMap.PutDataAsString(channelIndex, sampleString);

                    // Now register the RBNB channels, and flush the rbnbChannelMap to the
                    // DataTurbine
                    getSource().Register(registerChannelMap);
                    getSource().Flush(rbnbChannelMap);
                    logger.info("Sample sent to the DataTurbine: (" + sensorSerialNumber + ") " + sampleString);

                    registerChannelMap.Clear();
                    rbnbChannelMap.Clear();

                } // end if()

            } // end for()                                             

            success = true;

        } catch (Exception sapie) {
            success = false;
            sapie.printStackTrace();
            return success;

        }

        return success;
    } // end if (  !isConnected() ) 

    /**
     * A method that executes the streaming of data from the source to the RBNB
     * server after all configuration of settings, connections to hosts, and
     * thread initiatizing occurs.  This method contains the detailed code for 
     * streaming the data and interpreting the stream.
     */
    protected boolean execute() {
        logger.debug("CTDSource.execute() called.");

        // do not execute the stream if there is no connection
        if (!isConnected())
            return false;

        boolean failed = false;

        // test the connection type
        if (this.connectionType.equals("serial")) {

            // create a serial connection to the local serial port
            this.channel = getSerialConnection();

        } else if (this.connectionType.equals("socket")) {

            // otherwise create a TCP or UDP socket connection to the remote host
            this.channel = getSocketConnection();

        } else {
            logger.info("There was an error establishing either a serial or "
                    + "socket connection to the instrument.  Please be sure "
                    + "the connection type is set to either 'serial' or 'socket'.");
            return false;

        }

        // while data are being sent, read them into the buffer
        try {
            // create four byte placeholders used to evaluate up to a four-byte 
            // window.  The FIFO layout looks like:
            //           -------------------------
            //   in ---> | One | Two |Three|Four |  ---> out
            //           -------------------------
            byte byteOne = 0x00, // set initial placeholder values
                    byteTwo = 0x00, byteThree = 0x00, byteFour = 0x00;

            // Create a buffer that will store the sample bytes as they are read
            ByteBuffer sampleBuffer = ByteBuffer.allocate(getBufferSize());

            // Declare sample variables to be used in the response parsing
            byte[] sampleArray;

            // create a byte buffer to store bytes from the TCP stream
            ByteBuffer buffer = ByteBuffer.allocateDirect(getBufferSize());

            // add a channel of data that will be pushed to the server.  
            // Each sample will be sent to the Data Turbine as an rbnb frame.
            ChannelMap rbnbChannelMap = new ChannelMap();

            // while there are bytes to read from the channel ...
            while (this.channel.read(buffer) != -1 || buffer.position() > 0) {

                // prepare the buffer for reading
                buffer.flip();

                // while there are unread bytes in the ByteBuffer
                while (buffer.hasRemaining()) {
                    byteOne = buffer.get();
                    logger.debug("b1: " + new String(Hex.encodeHex((new byte[] { byteOne }))) + "\t" + "b2: "
                            + new String(Hex.encodeHex((new byte[] { byteTwo }))) + "\t" + "b3: "
                            + new String(Hex.encodeHex((new byte[] { byteThree }))) + "\t" + "b4: "
                            + new String(Hex.encodeHex((new byte[] { byteFour }))) + "\t" + "sample pos: "
                            + sampleBuffer.position() + "\t" + "sample rem: " + sampleBuffer.remaining() + "\t"
                            + "sample cnt: " + sampleByteCount + "\t" + "buffer pos: " + buffer.position() + "\t"
                            + "buffer rem: " + buffer.remaining() + "\t" + "state: " + this.state);

                    // Use a State Machine to process the byte stream.
                    // Start building an rbnb frame for the entire sample, first by 
                    // inserting a timestamp into the channelMap.  This time is merely
                    // the time of insert into the data turbine, not the time of
                    // observations of the measurements.  That time should be parsed out
                    // of the sample in the Sink client code

                    switch (this.state) {

                    case 0: // wake up the instrument

                        // check for instrument metadata fields
                        if (this.enableSendCommands && !this.hasMetadata) {

                            // wake the instrument with an initial '\r\n' command
                            this.command = this.commandSuffix;
                            this.sentCommand = queryInstrument(this.command);
                            this.sentCommand = queryInstrument(this.command);
                            streamingThread.sleep(2000);

                            this.state = 1;
                            break;

                        } else {

                            this.state = 11;
                            break;

                        }

                    case 1: // stop the sampling

                        // be sure the instrument woke (look for S> prompt)
                        //if (byteOne == 0x3E && byteTwo == 0x53 ) {
                        //  
                        //  sampleByteCount = 0;
                        //  sampleBuffer.clear();
                        //  
                        //  // send the stop sampling command
                        this.command = this.commandPrefix + this.stopSamplingCommand + this.commandSuffix;
                        this.sentCommand = queryInstrument(command);

                        sampleBuffer.clear();
                        sampleByteCount = 0;
                        this.state = 2;
                        break;

                    //} else {
                    //  // handle instrument hardware response
                    //  sampleByteCount++; // add the last byte found to the count
                    //  
                    //  // add the last byte found to the sample buffer
                    //  if ( sampleBuffer.remaining() > 0 ) {
                    //    sampleBuffer.put(byteOne);
                    //  
                    //  } else {
                    //    sampleBuffer.compact();
                    //    sampleBuffer.put(byteOne);
                    //    
                    //  }                
                    //  
                    //  break; // continue reading bytes
                    //  
                    //}

                    case 2: // based on outputType, get metadata from the instrument

                        // the response should end in <Executed/>
                        if (byteOne == 0x3E && byteTwo == 0x2F && byteThree == 0x64 && byteFour == 0x65) {

                            sampleBuffer.clear();
                            sampleByteCount = 0;
                            this.samplingIsStopped = true;

                            // for newer firmware CTDs, use xml-based query commands
                            if (getOutputType().equals("xml")) {
                                // create the CTD parser instance used to parse CTD output
                                this.ctdParser = new CTDParser();
                                this.state = 3;
                                break;

                                // otherwise, use text-based query commands
                            } else if (getOutputType().equals("text")) {
                                this.state = 12; // process DS and DCal commands
                                break;

                            } else {

                                logger.info("The CTD output type is not recognized. "
                                        + "Please set the output type to either " + "'xml' or 'text'.");
                                failed = true;
                                this.state = 0;

                                // close the serial or socket channel
                                if (this.channel != null && this.channel.isOpen()) {
                                    try {
                                        this.channel.close();

                                    } catch (IOException cioe) {
                                        logger.debug("An error occurred trying to close the byte channel. "
                                                + " The error message was: " + cioe.getMessage());
                                        return !failed;

                                    }
                                }

                                // disconnect from the RBNB
                                if (isConnected()) {
                                    disconnect();
                                }

                                return !failed;

                            }

                        } else {

                            // handle instrument hardware response
                            sampleByteCount++; // add the last byte found to the count

                            // add the last byte found to the sample buffer
                            if (sampleBuffer.remaining() > 0) {
                                sampleBuffer.put(byteOne);

                            } else {
                                sampleBuffer.compact();
                                sampleBuffer.put(byteOne);

                            }

                            break; // continue reading bytes

                        }

                    case 3: // get the instrument status metadata

                        if (!this.ctdParser.getHasStatusMetadata()) {

                            this.command = this.commandPrefix + this.getStatusCommand + this.commandSuffix;
                            this.sentCommand = queryInstrument(command);
                            streamingThread.sleep(5000);
                            this.state = 4;
                            break;

                        } else {

                            // get the configuration metadata
                            this.command = this.commandPrefix + this.getConfigurationCommand + this.commandSuffix;
                            this.sentCommand = queryInstrument(command);
                            streamingThread.sleep(5000);
                            this.state = 5;
                            break;

                        }

                    case 4: // handle instrument status response

                        // command response ends with <Executed/> (so find: ed/>)
                        if (byteOne == 0x3E && byteTwo == 0x2F && byteThree == 0x64 && byteFour == 0x65) {

                            // handle instrument status response
                            sampleByteCount++; // add the last byte found to the count

                            // add the last byte found to the sample buffer
                            if (sampleBuffer.remaining() > 0) {
                                sampleBuffer.put(byteOne);

                            } else {
                                sampleBuffer.compact();
                                sampleBuffer.put(byteOne);

                            }

                            // extract the sampleByteCount length from the sampleBuffer
                            sampleArray = new byte[sampleByteCount];
                            sampleBuffer.flip();
                            sampleBuffer.get(sampleArray);
                            this.responseString = new String(sampleArray, "US-ASCII");

                            // set the CTD metadata
                            int executedIndex = this.responseString.indexOf("<Executed/>");
                            this.responseString = this.responseString.substring(0, executedIndex - 1);

                            this.ctdParser.setMetadata(this.responseString);

                            // reset variables for the next sample
                            sampleBuffer.clear();
                            sampleByteCount = 0;

                            // then get the instrument configuration metadata
                            if (!this.ctdParser.getHasConfigurationMetadata()) {

                                this.command = this.commandPrefix + this.getConfigurationCommand
                                        + this.commandSuffix;
                                this.sentCommand = queryInstrument(command);
                                streamingThread.sleep(5000);
                                this.state = 5;
                                break;

                            } else {

                                // get the calibration metadata
                                this.command = this.commandPrefix + this.getCalibrationCommand + this.commandSuffix;
                                this.sentCommand = queryInstrument(command);
                                streamingThread.sleep(5000);
                                this.state = 6;
                                break;

                            }

                        } else {
                            break; // continue reading bytes

                        }

                    case 5: // handle the instrument configuration metadata

                        // command response ends with <Executed/> (so find: ed/>)
                        if (byteOne == 0x3E && byteTwo == 0x2F && byteThree == 0x64 && byteFour == 0x65) {

                            // handle instrument configration response
                            sampleByteCount++; // add the last byte found to the count

                            // add the last byte found to the sample buffer
                            if (sampleBuffer.remaining() > 0) {
                                sampleBuffer.put(byteOne);

                            } else {
                                sampleBuffer.compact();
                                sampleBuffer.put(byteOne);

                            }

                            // extract the sampleByteCount length from the sampleBuffer
                            sampleArray = new byte[sampleByteCount];
                            sampleBuffer.flip();
                            sampleBuffer.get(sampleArray);
                            this.responseString = new String(sampleArray, "US-ASCII");

                            // set the CTD metadata
                            int executedIndex = this.responseString.indexOf("<Executed/>");
                            this.responseString = this.responseString.substring(0, executedIndex - 1);

                            this.ctdParser.setMetadata(this.responseString);

                            // reset variables for the next sample
                            sampleBuffer.clear();
                            sampleByteCount = 0;

                            // then get the instrument calibration metadata
                            if (!this.ctdParser.getHasCalibrationMetadata()) {

                                this.command = this.commandPrefix + this.getCalibrationCommand + this.commandSuffix;
                                this.sentCommand = queryInstrument(command);
                                streamingThread.sleep(5000);
                                this.state = 6;
                                break;

                            } else {

                                this.command = this.commandPrefix + this.getEventsCommand + this.commandSuffix;
                                this.sentCommand = queryInstrument(command);
                                streamingThread.sleep(5000);
                                this.state = 7;
                                break;

                            }

                        } else {
                            break; // continue reading bytes

                        }

                    case 6: // handle the instrument calibration metadata

                        // command response ends with <Executed/> (so find: ed/>)
                        if (byteOne == 0x3E && byteTwo == 0x2F && byteThree == 0x64 && byteFour == 0x65) {

                            // handle instrument calibration response
                            sampleByteCount++; // add the last byte found to the count

                            // add the last byte found to the sample buffer
                            if (sampleBuffer.remaining() > 0) {
                                sampleBuffer.put(byteOne);

                            } else {
                                sampleBuffer.compact();
                                sampleBuffer.put(byteOne);

                            }

                            // extract the sampleByteCount length from the sampleBuffer
                            sampleArray = new byte[sampleByteCount];
                            sampleBuffer.flip();
                            sampleBuffer.get(sampleArray);
                            this.responseString = new String(sampleArray, "US-ASCII");

                            // set the CTD metadata
                            int executedIndex = this.responseString.indexOf("<Executed/>");
                            this.responseString = this.responseString.substring(0, executedIndex - 1);

                            this.ctdParser.setMetadata(this.responseString);

                            // reset variables for the next sample
                            sampleBuffer.clear();
                            sampleByteCount = 0;

                            // then get the instrument event metadata
                            if (!this.ctdParser.getHasEventMetadata()) {

                                this.command = this.commandPrefix + this.getEventsCommand + this.commandSuffix;
                                this.sentCommand = queryInstrument(command);
                                streamingThread.sleep(5000);
                                this.state = 7;
                                break;

                            } else {

                                this.command = this.commandPrefix + this.getHardwareCommand + this.commandSuffix;
                                this.sentCommand = queryInstrument(command);
                                streamingThread.sleep(5000);
                                this.state = 8;
                                break;

                            }

                        } else {
                            break; // continue reading bytes

                        }

                    case 7: // handle instrument event metadata

                        // command response ends with <Executed/> (so find: ed/>)
                        if (byteOne == 0x3E && byteTwo == 0x2F && byteThree == 0x64 && byteFour == 0x65) {

                            // handle instrument events response
                            sampleByteCount++; // add the last byte found to the count

                            // add the last byte found to the sample buffer
                            if (sampleBuffer.remaining() > 0) {
                                sampleBuffer.put(byteOne);

                            } else {
                                sampleBuffer.compact();
                                sampleBuffer.put(byteOne);

                            }

                            // extract the sampleByteCount length from the sampleBuffer
                            sampleArray = new byte[sampleByteCount];
                            sampleBuffer.flip();
                            sampleBuffer.get(sampleArray);
                            this.responseString = new String(sampleArray, "US-ASCII");

                            // set the CTD metadata
                            int executedIndex = this.responseString.indexOf("<Executed/>");
                            this.responseString = this.responseString.substring(0, executedIndex - 1);

                            this.ctdParser.setMetadata(this.responseString);

                            // reset variables for the next sample
                            sampleBuffer.clear();
                            sampleByteCount = 0;

                            // then get the instrument hardware metadata
                            if (!this.ctdParser.getHasHardwareMetadata()) {

                                this.command = this.commandPrefix + this.getHardwareCommand + this.commandSuffix;
                                this.sentCommand = queryInstrument(command);
                                streamingThread.sleep(5000);
                                this.state = 8;
                                break;

                            } else {

                                this.state = 9;
                                break;

                            }

                        } else {
                            break; // continue reading bytes

                        }

                    case 8: // handle the instrument hardware response

                        // command response ends with <Executed/> (so find: ed/>)
                        if (byteOne == 0x3E && byteTwo == 0x2F && byteThree == 0x64 && byteFour == 0x65) {

                            // handle instrument hardware response
                            sampleByteCount++; // add the last byte found to the count

                            // add the last byte found to the sample buffer
                            if (sampleBuffer.remaining() > 0) {
                                sampleBuffer.put(byteOne);

                            } else {
                                sampleBuffer.compact();
                                sampleBuffer.put(byteOne);

                            }

                            // extract the sampleByteCount length from the sampleBuffer
                            sampleArray = new byte[sampleByteCount];
                            sampleBuffer.flip();
                            sampleBuffer.get(sampleArray);
                            this.responseString = new String(sampleArray, "US-ASCII");

                            // set the CTD metadata
                            int executedIndex = this.responseString.indexOf("<Executed/>");
                            this.responseString = this.responseString.substring(0, executedIndex - 1);

                            this.ctdParser.setMetadata(this.responseString);

                            // reset variables for the next sample
                            sampleBuffer.clear();
                            sampleByteCount = 0;

                            // sync the clock if it is not synced
                            if (!this.clockIsSynced) {

                                this.state = 9;
                                break;

                            } else {
                                this.state = 10;
                                break;

                            }

                        } else {
                            break; // continue reading bytes

                        }

                    case 9: // set the instrument clock

                        // is sampling stopped?
                        if (!this.samplingIsStopped) {
                            // wake the instrument with an initial '\r\n' command
                            this.command = this.commandSuffix;
                            this.sentCommand = queryInstrument(this.command);
                            streamingThread.sleep(2000);

                            // then stop the sampling
                            this.command = this.commandPrefix + this.stopSamplingCommand + this.commandSuffix;
                            this.sentCommand = queryInstrument(command);
                            this.samplingIsStopped = true;

                        }

                        // now set the clock
                        if (this.sentCommand) {
                            this.clockSyncDate = new Date();
                            DATE_FORMAT.setTimeZone(TZ);
                            String dateAsString = DATE_FORMAT.format(this.clockSyncDate);

                            this.command = this.commandPrefix + this.setDateTimeCommand + dateAsString
                                    + this.commandSuffix;
                            this.sentCommand = queryInstrument(command);
                            streamingThread.sleep(5000);
                            this.clockIsSynced = true;
                            logger.info("The instrument clock has bee synced at " + this.clockSyncDate.toString());
                            this.state = 10;
                            break;

                        } else {

                            break; // try the clock sync again due to failure

                        }

                    case 10: // restart the instrument sampling

                        if (this.samplingIsStopped) {

                            this.hasMetadata = true;

                            this.command = this.commandPrefix + this.startSamplingCommand + this.commandSuffix;
                            this.sentCommand = queryInstrument(command);
                            streamingThread.sleep(5000);

                            if (this.sentCommand) {
                                this.state = 11;
                                break;

                            } else {
                                break; // try starting the sampling again due to failure
                            }

                        } else {

                            break;

                        }

                    case 11: // read bytes to the next EOL characters

                        // sample line is terminated by \r\n
                        // note bytes are in reverse order in the FIFO window
                        if (byteOne == 0x0A && byteTwo == 0x0D) {

                            sampleByteCount++; // add the last byte found to the count

                            // add the last byte found to the sample buffer
                            if (sampleBuffer.remaining() > 0) {
                                sampleBuffer.put(byteOne);

                            } else {
                                sampleBuffer.compact();
                                sampleBuffer.put(byteOne);

                            }

                            // extract just the length of the sample bytes out of the
                            // sample buffer, and place it in the channel map as a 
                            // byte array.  Then, send it to the data turbine.
                            sampleArray = new byte[sampleByteCount];
                            sampleBuffer.flip();
                            sampleBuffer.get(sampleArray);

                            this.responseString = new String(sampleArray, "US-ASCII");

                            // test if the sample is not just an instrument message
                            if (this.responseString.matches("^# [0-9].*\r\n")
                                    || this.responseString.matches("^#  [0-9].*\r\n")
                                    || this.responseString.matches("^ [0-9].*\r\n")) {

                                // add the data observations string to the CTDParser object
                                // and populate the CTDParser data fields
                                //this.ctdParser.setData(this.responseString);
                                //this.ctdParser.parse();

                                // build the channel map with all of the data and metadata channels:                  
                                int channelIndex = rbnbChannelMap.Add(getRBNBChannelName());
                                rbnbChannelMap.PutMime(channelIndex, "text/plain");
                                rbnbChannelMap.PutTimeAuto("server");

                                // add the ASCII sample data field
                                rbnbChannelMap.PutDataAsString(channelIndex, this.responseString);

                                // add other metadata and data fields to the map if metadata was collected
                                if (this.hasMetadata && this.ctdParser != null) {

                                    // add the samplingMode field data                                                                                 
                                    channelIndex = rbnbChannelMap.Add("samplingMode");
                                    rbnbChannelMap.PutMime(channelIndex, "text/plain");
                                    rbnbChannelMap.PutDataAsString(channelIndex, this.ctdParser.getSamplingMode()); // String

                                    // add the temperatureSerialNumber field data                                                                      
                                    channelIndex = rbnbChannelMap.Add("temperatureSerialNumber");
                                    rbnbChannelMap.PutMime(channelIndex, "text/plain");
                                    rbnbChannelMap.PutDataAsString(channelIndex,
                                            this.ctdParser.getTemperatureSerialNumber()); // String   

                                    // add the conductivitySerialNumber field data                                                                     
                                    channelIndex = rbnbChannelMap.Add("conductivitySerialNumber");
                                    rbnbChannelMap.PutMime(channelIndex, "text/plain");
                                    rbnbChannelMap.PutDataAsString(channelIndex,
                                            this.ctdParser.getConductivitySerialNumber()); // String   

                                    // add the mainBatteryVoltage field data                                                                           
                                    channelIndex = rbnbChannelMap.Add("mainBatteryVoltage");
                                    rbnbChannelMap.PutMime(channelIndex, "application/octet-stream");
                                    rbnbChannelMap.PutDataAsFloat64(channelIndex,
                                            new double[] { this.ctdParser.getMainBatteryVoltage() }); // double   

                                    // add the lithiumBatteryVoltage field data                                                                        
                                    channelIndex = rbnbChannelMap.Add("lithiumBatteryVoltage");
                                    rbnbChannelMap.PutMime(channelIndex, "application/octet-stream");
                                    rbnbChannelMap.PutDataAsFloat64(channelIndex,
                                            new double[] { this.ctdParser.getLithiumBatteryVoltage() }); // double   

                                    // add the operatingCurrent field data                                                                             
                                    channelIndex = rbnbChannelMap.Add("operatingCurrent");
                                    rbnbChannelMap.PutMime(channelIndex, "application/octet-stream");
                                    rbnbChannelMap.PutDataAsFloat64(channelIndex,
                                            new double[] { this.ctdParser.getOperatingCurrent() }); // double   

                                    // add the pumpCurrent field data                                                                                  
                                    channelIndex = rbnbChannelMap.Add("pumpCurrent");
                                    rbnbChannelMap.PutMime(channelIndex, "application/octet-stream");
                                    rbnbChannelMap.PutDataAsFloat64(channelIndex,
                                            new double[] { this.ctdParser.getPumpCurrent() }); // double   

                                    // add the channels01ExternalCurrent field data                                                                    
                                    channelIndex = rbnbChannelMap.Add("channels01ExternalCurrent");
                                    rbnbChannelMap.PutMime(channelIndex, "application/octet-stream");
                                    rbnbChannelMap.PutDataAsFloat64(channelIndex,
                                            new double[] { this.ctdParser.getChannels01ExternalCurrent() }); // double   

                                    // add the channels23ExternalCurrent field data                                                                    
                                    channelIndex = rbnbChannelMap.Add("channels23ExternalCurrent");
                                    rbnbChannelMap.PutMime(channelIndex, "application/octet-stream");
                                    rbnbChannelMap.PutDataAsFloat64(channelIndex,
                                            new double[] { this.ctdParser.getChannels23ExternalCurrent() }); // double   

                                    // add the loggingStatus field data                                                                                
                                    channelIndex = rbnbChannelMap.Add("loggingStatus");
                                    rbnbChannelMap.PutMime(channelIndex, "text/plain");
                                    rbnbChannelMap.PutDataAsString(channelIndex, this.ctdParser.getLoggingStatus()); // String   

                                    // add the numberOfScansToAverage field data                                                                       
                                    channelIndex = rbnbChannelMap.Add("numberOfScansToAverage");
                                    rbnbChannelMap.PutMime(channelIndex, "application/octet-stream");
                                    rbnbChannelMap.PutDataAsInt32(channelIndex,
                                            new int[] { this.ctdParser.getNumberOfScansToAverage() }); // int      

                                    // add the numberOfSamples field data                                                                              
                                    channelIndex = rbnbChannelMap.Add("numberOfSamples");
                                    rbnbChannelMap.PutMime(channelIndex, "application/octet-stream");
                                    rbnbChannelMap.PutDataAsInt32(channelIndex,
                                            new int[] { this.ctdParser.getNumberOfSamples() }); // int      

                                    // add the numberOfAvailableSamples field data                                                                     
                                    channelIndex = rbnbChannelMap.Add("numberOfAvailableSamples");
                                    rbnbChannelMap.PutMime(channelIndex, "application/octet-stream");
                                    rbnbChannelMap.PutDataAsInt32(channelIndex,
                                            new int[] { this.ctdParser.getNumberOfAvailableSamples() }); // int      

                                    // add the sampleInterval field data                                                                               
                                    channelIndex = rbnbChannelMap.Add("sampleInterval");
                                    rbnbChannelMap.PutMime(channelIndex, "application/octet-stream");
                                    rbnbChannelMap.PutDataAsInt32(channelIndex,
                                            new int[] { this.ctdParser.getSampleInterval() }); // int      

                                    // add the measurementsPerSample field data                                                                        
                                    channelIndex = rbnbChannelMap.Add("measurementsPerSample");
                                    rbnbChannelMap.PutMime(channelIndex, "application/octet-stream");
                                    rbnbChannelMap.PutDataAsInt32(channelIndex,
                                            new int[] { this.ctdParser.getMeasurementsPerSample() }); // int      

                                    // add the transmitRealtime field data                                                                             
                                    channelIndex = rbnbChannelMap.Add("transmitRealtime");
                                    rbnbChannelMap.PutMime(channelIndex, "text/plain");
                                    rbnbChannelMap.PutDataAsString(channelIndex,
                                            this.ctdParser.getTransmitRealtime()); // String   

                                    // add the numberOfCasts field data                                                                                
                                    channelIndex = rbnbChannelMap.Add("numberOfCasts");
                                    rbnbChannelMap.PutMime(channelIndex, "application/octet-stream");
                                    rbnbChannelMap.PutDataAsInt32(channelIndex,
                                            new int[] { this.ctdParser.getNumberOfCasts() }); // int      

                                    // add the minimumConductivityFrequency field data                                                                 
                                    channelIndex = rbnbChannelMap.Add("minimumConductivityFrequency");
                                    rbnbChannelMap.PutMime(channelIndex, "application/octet-stream");
                                    rbnbChannelMap.PutDataAsInt32(channelIndex,
                                            new int[] { this.ctdParser.getMinimumConductivityFrequency() }); // int      

                                    // add the pumpDelay field data                                                                                    
                                    channelIndex = rbnbChannelMap.Add("pumpDelay");
                                    rbnbChannelMap.PutMime(channelIndex, "application/octet-stream");
                                    rbnbChannelMap.PutDataAsInt32(channelIndex,
                                            new int[] { this.ctdParser.getPumpDelay() }); // int      

                                    // add the automaticLogging field data                                                                             
                                    channelIndex = rbnbChannelMap.Add("automaticLogging");
                                    rbnbChannelMap.PutMime(channelIndex, "text/plain");
                                    rbnbChannelMap.PutDataAsString(channelIndex,
                                            this.ctdParser.getAutomaticLogging()); // String   

                                    // add the ignoreMagneticSwitch field data                                                                         
                                    channelIndex = rbnbChannelMap.Add("ignoreMagneticSwitch");
                                    rbnbChannelMap.PutMime(channelIndex, "text/plain");
                                    rbnbChannelMap.PutDataAsString(channelIndex,
                                            this.ctdParser.getIgnoreMagneticSwitch()); // String   

                                    // add the batteryType field data                                                                                  
                                    channelIndex = rbnbChannelMap.Add("batteryType");
                                    rbnbChannelMap.PutMime(channelIndex, "text/plain");
                                    rbnbChannelMap.PutDataAsString(channelIndex, this.ctdParser.getBatteryType()); // String   

                                    // add the batteryCutoff field data                                                                                
                                    channelIndex = rbnbChannelMap.Add("batteryCutoff");
                                    rbnbChannelMap.PutMime(channelIndex, "text/plain");
                                    rbnbChannelMap.PutDataAsString(channelIndex, this.ctdParser.getBatteryCutoff()); // String   

                                    // add the pressureSensorType field data                                                                           
                                    channelIndex = rbnbChannelMap.Add("pressureSensorType");
                                    rbnbChannelMap.PutMime(channelIndex, "text/plain");
                                    rbnbChannelMap.PutDataAsString(channelIndex,
                                            this.ctdParser.getPressureSensorType()); // String   

                                    // add the pressureSensorRange field data                                                                          
                                    channelIndex = rbnbChannelMap.Add("pressureSensorRange");
                                    rbnbChannelMap.PutMime(channelIndex, "text/plain");
                                    rbnbChannelMap.PutDataAsString(channelIndex,
                                            this.ctdParser.getPressureSensorRange()); // String   

                                    // add the sbe38TemperatureSensor field data                                                                       
                                    channelIndex = rbnbChannelMap.Add("sbe38TemperatureSensor");
                                    rbnbChannelMap.PutMime(channelIndex, "text/plain");
                                    rbnbChannelMap.PutDataAsString(channelIndex,
                                            this.ctdParser.getSbe38TemperatureSensor()); // String   

                                    // add the gasTensionDevice field data                                                                             
                                    channelIndex = rbnbChannelMap.Add("gasTensionDevice");
                                    rbnbChannelMap.PutMime(channelIndex, "text/plain");
                                    rbnbChannelMap.PutDataAsString(channelIndex,
                                            this.ctdParser.getGasTensionDevice()); // String   

                                    // add the externalVoltageChannelZero field data                                                                   
                                    channelIndex = rbnbChannelMap.Add("externalVoltageChannelZero");
                                    rbnbChannelMap.PutMime(channelIndex, "text/plain");
                                    rbnbChannelMap.PutDataAsString(channelIndex,
                                            this.ctdParser.getExternalVoltageChannelZero()); // String   

                                    // add the externalVoltageChannelOne field data                                                                    
                                    channelIndex = rbnbChannelMap.Add("externalVoltageChannelOne");
                                    rbnbChannelMap.PutMime(channelIndex, "text/plain");
                                    rbnbChannelMap.PutDataAsString(channelIndex,
                                            this.ctdParser.getExternalVoltageChannelOne()); // String   

                                    // add the externalVoltageChannelTwo field data                                                                    
                                    channelIndex = rbnbChannelMap.Add("externalVoltageChannelTwo");
                                    rbnbChannelMap.PutMime(channelIndex, "text/plain");
                                    rbnbChannelMap.PutDataAsString(channelIndex,
                                            this.ctdParser.getExternalVoltageChannelTwo()); // String   

                                    // add the externalVoltageChannelThree field data                                                                  
                                    channelIndex = rbnbChannelMap.Add("externalVoltageChannelThree");
                                    rbnbChannelMap.PutMime(channelIndex, "text/plain");
                                    rbnbChannelMap.PutDataAsString(channelIndex,
                                            this.ctdParser.getExternalVoltageChannelThree()); // String   

                                    // add the echoCommands field data                                                                                 
                                    channelIndex = rbnbChannelMap.Add("echoCommands");
                                    rbnbChannelMap.PutMime(channelIndex, "text/plain");
                                    rbnbChannelMap.PutDataAsString(channelIndex, this.ctdParser.getEchoCommands()); // String   

                                    // add the outputFormat field data                                                                                 
                                    channelIndex = rbnbChannelMap.Add("outputFormat");
                                    rbnbChannelMap.PutMime(channelIndex, "text/plain");
                                    rbnbChannelMap.PutDataAsString(channelIndex, this.ctdParser.getOutputFormat()); // String   

                                    // add the temperatureCalibrationDate field data                                                                   
                                    channelIndex = rbnbChannelMap.Add("temperatureCalibrationDate");
                                    rbnbChannelMap.PutMime(channelIndex, "text/plain");
                                    rbnbChannelMap.PutDataAsString(channelIndex,
                                            this.ctdParser.getTemperatureCalibrationDate()); // String   

                                    // add the temperatureCoefficientTA0 field data                                                                    
                                    channelIndex = rbnbChannelMap.Add("temperatureCoefficientTA0");
                                    rbnbChannelMap.PutMime(channelIndex, "application/octet-stream");
                                    rbnbChannelMap.PutDataAsFloat64(channelIndex,
                                            new double[] { this.ctdParser.getTemperatureCoefficientTA0() }); // double   

                                    // add the temperatureCoefficientTA1 field data                                                                    
                                    channelIndex = rbnbChannelMap.Add("temperatureCoefficientTA1");
                                    rbnbChannelMap.PutMime(channelIndex, "application/octet-stream");
                                    rbnbChannelMap.PutDataAsFloat64(channelIndex,
                                            new double[] { this.ctdParser.getTemperatureCoefficientTA1() }); // double   

                                    // add the temperatureCoefficientTA2 field data                                                                    
                                    channelIndex = rbnbChannelMap.Add("temperatureCoefficientTA2");
                                    rbnbChannelMap.PutMime(channelIndex, "application/octet-stream");
                                    rbnbChannelMap.PutDataAsFloat64(channelIndex,
                                            new double[] { this.ctdParser.getTemperatureCoefficientTA2() }); // double   

                                    // add the temperatureCoefficientTA3 field data                                                                    
                                    channelIndex = rbnbChannelMap.Add("temperatureCoefficientTA3");
                                    rbnbChannelMap.PutMime(channelIndex, "application/octet-stream");
                                    rbnbChannelMap.PutDataAsFloat64(channelIndex,
                                            new double[] { this.ctdParser.getTemperatureCoefficientTA3() }); // double   

                                    // add the temperatureOffsetCoefficient field data                                                                 
                                    channelIndex = rbnbChannelMap.Add("temperatureOffsetCoefficient");
                                    rbnbChannelMap.PutMime(channelIndex, "application/octet-stream");
                                    rbnbChannelMap.PutDataAsFloat64(channelIndex,
                                            new double[] { this.ctdParser.getTemperatureOffsetCoefficient() }); // double   

                                    // add the conductivityCalibrationDate field data                                                                  
                                    channelIndex = rbnbChannelMap.Add("conductivityCalibrationDate");
                                    rbnbChannelMap.PutMime(channelIndex, "text/plain");
                                    rbnbChannelMap.PutDataAsString(channelIndex,
                                            this.ctdParser.getConductivityCalibrationDate()); // String   

                                    // add the conductivityCoefficientG field data                                                                     
                                    channelIndex = rbnbChannelMap.Add("conductivityCoefficientG");
                                    rbnbChannelMap.PutMime(channelIndex, "application/octet-stream");
                                    rbnbChannelMap.PutDataAsFloat64(channelIndex,
                                            new double[] { this.ctdParser.getConductivityCoefficientG() }); // double   

                                    // add the conductivityCoefficientH field data                                                                     
                                    channelIndex = rbnbChannelMap.Add("conductivityCoefficientH");
                                    rbnbChannelMap.PutMime(channelIndex, "application/octet-stream");
                                    rbnbChannelMap.PutDataAsFloat64(channelIndex,
                                            new double[] { this.ctdParser.getConductivityCoefficientH() }); // double   

                                    // add the conductivityCoefficientI field data                                                                     
                                    channelIndex = rbnbChannelMap.Add("conductivityCoefficientI");
                                    rbnbChannelMap.PutMime(channelIndex, "application/octet-stream");
                                    rbnbChannelMap.PutDataAsFloat64(channelIndex,
                                            new double[] { this.ctdParser.getConductivityCoefficientI() }); // double   

                                    // add the conductivityCoefficientJ field data                                                                     
                                    channelIndex = rbnbChannelMap.Add("conductivityCoefficientJ");
                                    rbnbChannelMap.PutMime(channelIndex, "application/octet-stream");
                                    rbnbChannelMap.PutDataAsFloat64(channelIndex,
                                            new double[] { this.ctdParser.getConductivityCoefficientJ() }); // double   

                                    // add the conductivityCoefficientCF0 field data                                                                   
                                    channelIndex = rbnbChannelMap.Add("conductivityCoefficientCF0");
                                    rbnbChannelMap.PutMime(channelIndex, "application/octet-stream");
                                    rbnbChannelMap.PutDataAsFloat64(channelIndex,
                                            new double[] { this.ctdParser.getConductivityCoefficientCF0() }); // double   

                                    // add the conductivityCoefficientCPCOR field data                                                                 
                                    channelIndex = rbnbChannelMap.Add("conductivityCoefficientCPCOR");
                                    rbnbChannelMap.PutMime(channelIndex, "application/octet-stream");
                                    rbnbChannelMap.PutDataAsFloat64(channelIndex,
                                            new double[] { this.ctdParser.getConductivityCoefficientCPCOR() }); // double   

                                    // add the conductivityCoefficientCTCOR field data                                                                 
                                    channelIndex = rbnbChannelMap.Add("conductivityCoefficientCTCOR");
                                    rbnbChannelMap.PutMime(channelIndex, "application/octet-stream");
                                    rbnbChannelMap.PutDataAsFloat64(channelIndex,
                                            new double[] { this.ctdParser.getConductivityCoefficientCTCOR() }); // double   

                                    // add the conductivityCoefficientCSLOPE field data                                                                
                                    channelIndex = rbnbChannelMap.Add("conductivityCoefficientCSLOPE");
                                    rbnbChannelMap.PutMime(channelIndex, "application/octet-stream");
                                    rbnbChannelMap.PutDataAsFloat64(channelIndex,
                                            new double[] { this.ctdParser.getConductivityCoefficientCSLOPE() }); // double   

                                    // add the pressureSerialNumber field data                                                                         
                                    channelIndex = rbnbChannelMap.Add("pressureSerialNumber");
                                    rbnbChannelMap.PutMime(channelIndex, "text/plain");
                                    rbnbChannelMap.PutDataAsString(channelIndex,
                                            this.ctdParser.getPressureSerialNumber()); // String   

                                    // add the pressureCoefficientPA0 field data                                                                       
                                    channelIndex = rbnbChannelMap.Add("pressureCoefficientPA0");
                                    rbnbChannelMap.PutMime(channelIndex, "application/octet-stream");
                                    rbnbChannelMap.PutDataAsFloat64(channelIndex,
                                            new double[] { this.ctdParser.getPressureCoefficientPA0() }); // double   

                                    // add the pressureCoefficientPA1 field data                                                                       
                                    channelIndex = rbnbChannelMap.Add("pressureCoefficientPA1");
                                    rbnbChannelMap.PutMime(channelIndex, "application/octet-stream");
                                    rbnbChannelMap.PutDataAsFloat64(channelIndex,
                                            new double[] { this.ctdParser.getPressureCoefficientPA1() }); // double   

                                    // add the pressureCoefficientPA2 field data                                                                       
                                    channelIndex = rbnbChannelMap.Add("pressureCoefficientPA2");
                                    rbnbChannelMap.PutMime(channelIndex, "application/octet-stream");
                                    rbnbChannelMap.PutDataAsFloat64(channelIndex,
                                            new double[] { this.ctdParser.getPressureCoefficientPA2() }); // double   

                                    // add the pressureCoefficientPTCA0 field data                                                                     
                                    channelIndex = rbnbChannelMap.Add("pressureCoefficientPTCA0");
                                    rbnbChannelMap.PutMime(channelIndex, "application/octet-stream");
                                    rbnbChannelMap.PutDataAsFloat64(channelIndex,
                                            new double[] { this.ctdParser.getPressureCoefficientPTCA0() }); // double   

                                    // add the pressureCoefficientPTCA1 field data                                                                     
                                    channelIndex = rbnbChannelMap.Add("pressureCoefficientPTCA1");
                                    rbnbChannelMap.PutMime(channelIndex, "application/octet-stream");
                                    rbnbChannelMap.PutDataAsFloat64(channelIndex,
                                            new double[] { this.ctdParser.getPressureCoefficientPTCA1() }); // double   

                                    // add the pressureCoefficientPTCA2 field data                                                                     
                                    channelIndex = rbnbChannelMap.Add("pressureCoefficientPTCA2");
                                    rbnbChannelMap.PutMime(channelIndex, "application/octet-stream");
                                    rbnbChannelMap.PutDataAsFloat64(channelIndex,
                                            new double[] { this.ctdParser.getPressureCoefficientPTCA2() }); // double   

                                    // add the pressureCoefficientPTCB0 field data                                                                     
                                    channelIndex = rbnbChannelMap.Add("pressureCoefficientPTCB0");
                                    rbnbChannelMap.PutMime(channelIndex, "application/octet-stream");
                                    rbnbChannelMap.PutDataAsFloat64(channelIndex,
                                            new double[] { this.ctdParser.getPressureCoefficientPTCB0() }); // double   

                                    // add the pressureCoefficientPTCB1 field data                                                                     
                                    channelIndex = rbnbChannelMap.Add("pressureCoefficientPTCB1");
                                    rbnbChannelMap.PutMime(channelIndex, "application/octet-stream");
                                    rbnbChannelMap.PutDataAsFloat64(channelIndex,
                                            new double[] { this.ctdParser.getPressureCoefficientPTCB1() }); // double   

                                    // add the pressureCoefficientPTCB2 field data                                                                     
                                    channelIndex = rbnbChannelMap.Add("pressureCoefficientPTCB2");
                                    rbnbChannelMap.PutMime(channelIndex, "application/octet-stream");
                                    rbnbChannelMap.PutDataAsFloat64(channelIndex,
                                            new double[] { this.ctdParser.getPressureCoefficientPTCB2() }); // double   

                                    // add the pressureCoefficientPTEMPA0 field data                                                                   
                                    channelIndex = rbnbChannelMap.Add("pressureCoefficientPTEMPA0");
                                    rbnbChannelMap.PutMime(channelIndex, "application/octet-stream");
                                    rbnbChannelMap.PutDataAsFloat64(channelIndex,
                                            new double[] { this.ctdParser.getPressureCoefficientPTEMPA0() }); // double   

                                    // add the pressureCoefficientPTEMPA1 field data                                                                   
                                    channelIndex = rbnbChannelMap.Add("pressureCoefficientPTEMPA1");
                                    rbnbChannelMap.PutMime(channelIndex, "application/octet-stream");
                                    rbnbChannelMap.PutDataAsFloat64(channelIndex,
                                            new double[] { this.ctdParser.getPressureCoefficientPTEMPA1() }); // double   

                                    // add the pressureCoefficientPTEMPA2 field data                                                                   
                                    channelIndex = rbnbChannelMap.Add("pressureCoefficientPTEMPA2");
                                    rbnbChannelMap.PutMime(channelIndex, "application/octet-stream");
                                    rbnbChannelMap.PutDataAsFloat64(channelIndex,
                                            new double[] { this.ctdParser.getPressureCoefficientPTEMPA2() }); // double   

                                    // add the pressureOffsetCoefficient field data                                                                    
                                    channelIndex = rbnbChannelMap.Add("pressureOffsetCoefficient");
                                    rbnbChannelMap.PutMime(channelIndex, "application/octet-stream");
                                    rbnbChannelMap.PutDataAsFloat64(channelIndex,
                                            new double[] { this.ctdParser.getPressureOffsetCoefficient() }); // double   
                                }

                                // send the sample to the data turbine
                                getSource().Flush(rbnbChannelMap);
                                logger.info("Sent sample to the DataTurbine: " + this.responseString);

                                // reset variables for the next sample
                                sampleBuffer.clear();
                                sampleByteCount = 0;
                                channelIndex = 0;
                                rbnbChannelMap.Clear();
                                logger.debug("Cleared b1,b2,b3,b4. Cleared sampleBuffer. Cleared rbnbChannelMap.");

                                // check if the clock needs syncing (daily)
                                if (this.enableSendCommands) {

                                    // get the current datetime
                                    Calendar currentCalendar = Calendar.getInstance();
                                    currentCalendar.setTime(new Date());
                                    Calendar lastSyncedCalendar = Calendar.getInstance();
                                    lastSyncedCalendar.setTime(this.clockSyncDate);

                                    // round the dates to the day
                                    currentCalendar.clear(Calendar.MILLISECOND);
                                    currentCalendar.clear(Calendar.SECOND);
                                    currentCalendar.clear(Calendar.MINUTE);
                                    currentCalendar.clear(Calendar.HOUR);

                                    lastSyncedCalendar.clear(Calendar.MILLISECOND);
                                    lastSyncedCalendar.clear(Calendar.SECOND);
                                    lastSyncedCalendar.clear(Calendar.MINUTE);
                                    lastSyncedCalendar.clear(Calendar.HOUR);

                                    // sync the clock daily
                                    if (currentCalendar.before(lastSyncedCalendar)) {
                                        this.state = 8;

                                    }
                                }

                                // otherwise stay in state = 11                   
                                break;

                                // the sample looks more like an instrument message, don't flush
                            } else {

                                logger.info("This string does not look like a sample, "
                                        + "and was not sent to the DataTurbine.");
                                logger.info("Skipping sample: " + this.responseString);

                                // reset variables for the next sample
                                sampleBuffer.clear();
                                sampleByteCount = 0;
                                //rbnbChannelMap.Clear();                      
                                logger.debug("Cleared b1,b2,b3,b4. Cleared sampleBuffer. Cleared rbnbChannelMap.");
                                this.state = 11;
                                break;

                            }

                        } else { // not 0x0A0D

                            // still in the middle of the sample, keep adding bytes
                            sampleByteCount++; // add each byte found

                            if (sampleBuffer.remaining() > 0) {
                                sampleBuffer.put(byteOne);
                            } else {
                                sampleBuffer.compact();
                                logger.debug("Compacting sampleBuffer ...");
                                sampleBuffer.put(byteOne);

                            }

                            break;
                        } // end if for 0x0A0D EOL

                    case 12: // alternatively use legacy DS and DCal commands

                        if (this.enableSendCommands) {

                            // start by getting the DS status output
                            this.command = this.commandPrefix + this.displayStatusCommand + this.commandSuffix;
                            this.sentCommand = queryInstrument(command);
                            streamingThread.sleep(5000);
                            this.state = 13;
                            break;

                        } else {

                            this.state = 0;
                            break;

                        }

                    case 13: // handle the DS command response

                        // command should end with the S> prompt
                        if (byteOne == 0x7E && byteTwo == 0x53) {

                            // handle instrument status response
                            sampleByteCount++; // add the last byte found to the count

                            // add the last byte found to the sample buffer
                            if (sampleBuffer.remaining() > 0) {
                                sampleBuffer.put(byteOne);

                            } else {
                                sampleBuffer.compact();
                                sampleBuffer.put(byteOne);

                            }

                            // extract the sampleByteCount length from the sampleBuffer
                            sampleArray = new byte[sampleByteCount - 2]; //subtract "S>"
                            sampleBuffer.flip();
                            sampleBuffer.get(sampleArray);
                            this.responseString = new String(sampleArray, "US-ASCII");

                            // reset variables for the next sample
                            sampleBuffer.clear();
                            sampleByteCount = 0;

                            // then get the instrument calibration metadata
                            this.command = this.commandPrefix + this.displayCalibrationCommand + this.commandSuffix;
                            this.sentCommand = queryInstrument(command);
                            streamingThread.sleep(5000);
                            this.state = 14;
                            break;

                        } else {
                            break; // continue reading bytes

                        }

                    case 14: // handle the DCal command response

                        // command should end with the S> prompt
                        if (byteOne == 0x7E && byteTwo == 0x53) {

                            // handle instrument status response
                            sampleByteCount++; // add the last byte found to the count

                            // add the last byte found to the sample buffer
                            if (sampleBuffer.remaining() > 0) {
                                sampleBuffer.put(byteOne);

                            } else {
                                sampleBuffer.compact();
                                sampleBuffer.put(byteOne);

                            }

                            // extract the sampleByteCount length from the sampleBuffer
                            sampleArray = new byte[sampleByteCount - 2]; // subtract "S>"
                            sampleBuffer.flip();
                            sampleBuffer.get(sampleArray);

                            // append the DCal output to the DS output
                            this.responseString = this.responseString.concat(new String(sampleArray, "US-ASCII"));

                            // and add the data delimiter expected in the CTDParser
                            this.responseString = this.responseString.concat("*END*\r\n\r\n");

                            // build the CTDParser object with legacy DS and DCal metadata
                            this.ctdParser = new CTDParser(this.responseString);

                            // reset variables for the next sample
                            sampleBuffer.clear();
                            sampleByteCount = 0;

                            this.state = 9; // set the clock and start sampling
                            break;

                        } else {
                            break; // continue reading bytes

                        }

                    } // end switch statement

                    // shift the bytes in the FIFO window
                    byteFour = byteThree;
                    byteThree = byteTwo;
                    byteTwo = byteOne;

                } //end while (more unread bytes)

                // prepare the buffer to read in more bytes from the stream
                buffer.compact();

            } // end while (more channel bytes to read)

            this.channel.close();

        } catch (IOException e) {
            // handle exceptions
            // In the event of an i/o exception, log the exception, and allow execute()
            // to return false, which will prompt a retry.
            failed = true;
            this.state = 0;

            // close the serial or socket channel
            if (this.channel != null && this.channel.isOpen()) {
                try {
                    this.channel.close();

                } catch (IOException cioe) {
                    logger.debug("An error occurred trying to close the byte channel. " + " The error message was: "
                            + cioe.getMessage());

                }
            }

            // disconnect from the RBNB
            if (isConnected()) {
                disconnect();
            }

            e.printStackTrace();
            return !failed;

        } catch (InterruptedException intde) {
            // in the event that the streamingThread is interrupted
            failed = true;
            this.state = 0;

            // close the serial or socket channel
            if (this.channel != null && this.channel.isOpen()) {
                try {
                    this.channel.close();

                } catch (IOException cioe) {
                    logger.debug("An error occurred trying to close the byte channel. " + " The error message was: "
                            + cioe.getMessage());

                }
            }

            // disconnect from the RBNB
            if (isConnected()) {
                disconnect();
            }

            intde.printStackTrace();
            return !failed;

        } catch (SAPIException sapie) {
            // In the event of an RBNB communication  exception, log the exception, 
            // and allow execute() to return false, which will prompt a retry.
            //this.channel.close();
            failed = true;
            this.state = 0;

            // close the serial or socket channel
            if (this.channel != null && this.channel.isOpen()) {
                try {
                    this.channel.close();

                } catch (IOException cioe) {
                    logger.debug("An error occurred trying to close the byte channel. " + " The error message was: "
                            + cioe.getMessage());

                }
            }

            // disconnect from the RBNB
            if (isConnected()) {
                disconnect();
            }

            sapie.printStackTrace();
            return !failed;

        } catch (ParseException pe) {
            failed = true;
            this.state = 0;

            // close the serial or socket channel
            if (this.channel != null && this.channel.isOpen()) {
                try {
                    this.channel.close();

                } catch (IOException cioe) {
                    logger.debug("An error occurred trying to close the byte channel. " + " The error message was: "
                            + cioe.getMessage());

                }
            }

            // disconnect from the RBNB
            if (isConnected()) {
                disconnect();
            }

            logger.info("There was an error parsing the metadata response. " + "The error message was: "
                    + pe.getMessage());
            return !failed;

        } finally {

            this.state = 0;

            // close the serial or socket channel
            if (this.channel != null && this.channel.isOpen()) {
                try {
                    this.channel.close();

                } catch (IOException cioe) {
                    logger.debug("An error occurred trying to close the byte channel. " + " The error message was: "
                            + cioe.getMessage());

                }
            }

        }

        return !failed;
    }

    /**
    * A method used to the TCP socket of the remote source host for communication
    * @param host       the name or IP address of the host to connect to for the
    *                   socket connection (reading)
    * @param portNumber the number of the TCP port to connect to (i.e. 2604)
    */
    protected SocketChannel getSocketConnection() {

        String host = getHostName();
        int portNumber = new Integer(getHostPort()).intValue();
        SocketChannel dataSocket = null;

        try {

            // create the socket channel connection to the data source via the 
            // converter serial2IP converter      
            dataSocket = SocketChannel.open();
            dataSocket.connect(new InetSocketAddress(host, portNumber));

            // if the connection to the source fails, also disconnect from the RBNB
            // server and return null
            if (!dataSocket.isConnected()) {
                dataSocket.close();
                disconnect();
                dataSocket = null;
            }
        } catch (UnknownHostException ukhe) {

            logger.info("Unable to look up host: " + host + "\n");
            disconnect();
            dataSocket = null;
        } catch (IOException nioe) {
            logger.info("Couldn't get I/O connection to: " + host);
            disconnect();
            dataSocket = null;
        } catch (Exception e) {
            disconnect();
            dataSocket = null;
        }
        return dataSocket;

    }

    /**
    * A method used to get a serial connection for communication
    */
    protected ByteChannel getSerialConnection() {
        logger.debug("CTDSource.getSerialConnection() called.");

        ByteChannel serialChannel = (ByteChannel) new SerialChannel(getSerialPort());
        return serialChannel;

    }

    /**
     * A method that sets the size, in bytes, of the ByteBuffer used in streaming 
     * data from a source instrument via a TCP connection
     */
    public int getBufferSize() {
        return this.bufferSize;
    }

    /**
     * A method that returns the domain name or IP address of the source 
     * instrument (i.e. the serial-to-IP converter to which it is attached)
     */
    public String getHostName() {
        return this.sourceHostName;
    }

    /**
     * A method that returns the name of the RBNB channel that contains the 
     * streaming data from this instrument
     */
    public String getRBNBChannelName() {
        return this.rbnbChannelName;
    }

    /**
     * A method that returns the TCP port of the source 
     * instrument (i.e. the serial-to-IP converter to which it is attached)
     */
    public int getHostPort() {
        return this.sourceHostPort;
    }

    /**
     * A method that returns the versioning info for this file.  In this case, 
     * it returns a String that includes the Subversion LastChangedDate, 
     * LastChangedBy, LastChangedRevision, and HeadURL fields.
     */

    /**
     * A method that returns the name of the serial port used for serial
     * communication.  This defaults to "/dev/ttyS0".  On Windows, this
     * should be set to the appropriate comm port (e.g. COM1).  For use with 
     * FTDI serial-to-USB adapters on Linux, use "/dev/ttyUSB0".
     *
     * @return serialPort - the name of the serial port
     */
    public String getSerialPort() {
        return this.serialPortName;

    }

    /**
     * A method used to set the name of the serial port used for serial
     * communication.
     */
    private void setSerialPort(String serialPortName) {
        this.serialPortName = serialPortName;

    }

    /**
     * A method used to set the connection type, either serial or socket.
     */
    private void setConnectionType(String connectionType) {
        this.connectionType = connectionType;

    }

    /**
     * A method used to get the connection type, either serial or socket.
     *
     * @return connectionType - the connection type as a string.
     */
    public String getConnectionType() {
        return this.connectionType;

    }

    /**
     * A method used to set the output type, either xml or text.  By using
     * xml, xml-based CTD query commands will be used (GetSD, GetCD, GetCC, etc.).
     * When using text, legacy text-based commands will be used to query the
     * CTD (DS, DCal, etc.)
     */
    private void setOutputType(String outputType) {
        this.outputType = outputType;

    }

    /**
     * A method used to get the output type, either xml or text.
     *
     * @return outputType - the output type as a string.
     */
    public String getOutputType() {
        return this.outputType;

    }

    public String getCVSVersionString() {
        return ("$LastChangedDate$" + "$LastChangedBy$" + "$LastChangedRevision$" + "$HeadURL$");
    }

    /**
     * A method that returns true if the RBNB connection is established
     * and if the data streaming Thread has been started
     */
    public boolean isRunning() {
        // return the connection status and the thread status
        return (isConnected() && readyToStream);
    }

    /**
     * The main method for running the code
     * @ param args[] the command line list of string arguments, none are needed
     */

    public static void main(String args[]) {

        try {
            // create a new instance of the CTDSource object, and parse the command 
            // line arguments as settings for this instance
            final CTDSource ctdSource = new CTDSource();

            // Handle ctrl-c's and other abrupt death signals to the process
            Runtime.getRuntime().addShutdownHook(new Thread() {
                // stop the streaming process
                public void run() {
                    ctdSource.stop();
                }
            });

            // parse the commandline arguments to configure the connection, then 
            // start the streaming connection between the source and the RBNB server.
            if (ctdSource.parseArgs(args)) {
                ctdSource.start();
            }

        } catch (Exception e) {
            logger.info("Error in main(): " + e.getMessage());
            e.printStackTrace();
        }
    }

    /*
     * A method that runs the data streaming work performed by the execute()
     * by handling execution problems and continuously trying to re-execute after 
     * a specified retry interval for the thread.
     */
    private void runWork() {

        // handle execution problems by retrying if execute() fails
        boolean retry = true;

        while (retry) {

            // connect to the RBNB server
            if (connect()) {
                // run the data streaming code
                retry = !execute();
            }

            disconnect();

            if (retry) {
                try {
                    Thread.sleep(RETRY_INTERVAL);
                } catch (Exception e) {
                    logger.info("There was an execution problem. Retrying. Message is: " + e.getMessage());
                }
            }
        }
        // stop the streaming when we are done
        stop();
    }

    /**
     * A method that queries the instrument to obtain its ID 
     *
     * @return result - a boolean result, true if the command succeeds
     */
    public boolean queryInstrument(String command) {

        logger.debug("CTDSource.queryInstrument() called.");

        // the result of the query
        boolean result = false;

        // only send the command if the socket is connected
        if (this.channel.isOpen()) {
            ByteBuffer commandBuffer = ByteBuffer.allocate(command.length());

            // lock the byte buffer while writing to it to make it threadsafe
            synchronized (commandBuffer) {
                commandBuffer.put(command.getBytes());
                // don't flip the buffer
            }

            try {
                this.channel.write(commandBuffer);
                logger.debug("Wrote " + command + " to the instrument channel.");
                result = true;

            } catch (IOException ioe) {
                logger.info("There was a problem sending the command to the "
                        + "instrument. The error message was: " + ioe.getMessage());
                result = false;
            }
        }
        return result;
    }

    /**
     * A method that sets the command line arguments for this class.  This method 
     * calls the <code>RBNBSource.setBaseArgs()</code> method.
     * 
     * @param command  The CommandLine object being passed in from the command
     */
    protected boolean setArgs(CommandLine command) {

        // first set the base arguments that are included on the command line
        if (!setBaseArgs(command)) {
            return false;
        }

        // add command line arguments here

        // handle the -H option
        if (command.hasOption("H")) {
            String hostName = command.getOptionValue("H");
            if (hostName != null) {
                setHostName(hostName);
            }
        }

        // handle the -P option, test if it's an integer
        if (command.hasOption("P")) {
            String hostPort = command.getOptionValue("P");
            if (hostPort != null) {
                try {
                    setHostPort(Integer.parseInt(hostPort));

                } catch (NumberFormatException nfe) {
                    logger.info("Error: Enter a numeric value for the host port. " + hostPort
                            + " is not a valid number.");
                    return false;
                }
            }
        }

        // handle the -C option
        if (command.hasOption("C")) {
            String channelName = command.getOptionValue("C");
            if (channelName != null) {
                setChannelName(channelName);
            }
        }

        // handle the -c option
        if (command.hasOption("c")) {
            String communicationPort = command.getOptionValue("c");
            if (communicationPort != null) {
                setSerialPort(communicationPort);
            }
        }

        // handle the -t option
        if (command.hasOption("t")) {
            String connectionType = command.getOptionValue("t");
            if (connectionType != null) {
                if (connectionType.equals("serial") || connectionType.equals("socket")) {
                    setConnectionType(connectionType);

                } else {
                    logger.info("The connection type was not recognized.  Please "
                            + "use either 'serial' or 'socket' for the '-t' option.");
                    return false;

                }
            }
        }

        // handle the -o option
        if (command.hasOption("o")) {
            String outputType = command.getOptionValue("o");
            if (outputType != null) {

                if (outputType.equals("xml") || outputType.equals("text")) {
                    setOutputType(outputType);

                } else {
                    logger.info("The output type was not recognized.  Please "
                            + "use either 'xml' or 'text' for the '-o' option.");
                    return false;

                }

                setOutputType(outputType);
            }
        }

        if ((command.hasOption("H") || command.hasOption("P")) && command.hasOption("c")
                && (!command.hasOption("t"))) {
            logger.info("There was a configuration error.  The '-H' and '-P' "
                    + "options are mutually exclusive of the '-c' option "
                    + "(socket vs. serial connection type).  You must "
                    + "include the '-t' option designating the connection "
                    + "type, with either 'serial' or 'socket'.");
            return false;
        }

        return true;
    }

    /**
     * A method that sets the size, in bytes, of the ByteBuffer used in streaming 
     * data from a source instrument via a TCP connection
     *
     * @param bufferSize  the size, in bytes, of the ByteBuffer
     */
    public void setBuffersize(int bufferSize) {
        this.bufferSize = bufferSize;
    }

    /**
     * A method that sets the RBNB channel name of the source instrument's data
     * stream
     *
     * @param channelName  the name of the RBNB channel being streamed
     */
    public void setChannelName(String channelName) {
        this.rbnbChannelName = channelName;
    }

    /**
     * A method that sets the domain name or IP address of the source 
     * instrument (i.e. the serial-to-IP converter to which it is attached)
     *
     * @param hostName  the domain name or IP address of the source instrument
     */
    public void setHostName(String hostName) {
        this.sourceHostName = hostName;
    }

    /**
     * A method that sets the TCP port of the source 
     * instrument (i.e. the serial-to-IP converter to which it is attached)
     *
     * @param hostPort  the TCP port of the source instrument
     */
    public void setHostPort(int hostPort) {
        this.sourceHostPort = hostPort;
    }

    /**
     * A method that sets the command line options for this class.  This method 
     * calls the <code>RBNBSource.setBaseOptions()</code> method in order to set
     * properties such as the sourceHostName, sourceHostPort, serverName, and
     * serverPort.
     */
    protected Options setOptions() {
        Options options = setBaseOptions(new Options());

        // Note: 
        // Command line options already provided by RBNBBase include:
        // -h "Print help"
        // -s "RBNB Server Hostname"
        // -p "RBNB Server Port Number"
        // -S "RBNB Source Name"
        // -v "Print Version information"

        // Command line options already provided by RBNBSource include:
        // -z "Cache size"
        // -Z "Archive size"

        // add command line options here
        options.addOption("H", true, "Source host name or IP e.g. " + getHostName());
        options.addOption("P", true, "Source host port number e.g. " + getHostPort());
        options.addOption("C", true, "RBNB source channel name e.g. " + getRBNBChannelName());
        options.addOption("c", true, "communication serial port, e.g. /dev/ttyUSB0");
        options.addOption("o", true, "output type from the CTD, either 'xml' or 'text'");
        options.addOption("t", true, "connection type to the CTD, either 'serial' or 'socket'");

        return options;
    }

    /**
     * A method that starts the streaming of data from the source instrument to
     * the RBNB server via an established TCP connection.  
     */
    public boolean start() {

        // return false if the streaming is running
        if (isRunning()) {
            return false;
        }

        // reset the connection to the RBNB server
        if (isConnected()) {
            disconnect();
        }
        connect();

        // return false if the connection fails
        if (!isConnected()) {
            return false;
        }

        // begin the streaming thread to the source
        startThread();

        return true;
    }

    /**
     * A method that creates and starts a new Thread with a run() method that 
     * begins processing the data streaming from the source instrument.
     */
    private void startThread() {

        // build the runnable class and implement the run() method
        Runnable runner = new Runnable() {
            public void run() {
                runWork();
            }
        };

        // build the Thread and start it, indicating that it has been started
        readyToStream = true;
        streamingThread = new Thread(runner, "StreamingThread");
        streamingThread.start();
    }

    /**
     * A method that stops the streaming of data between the source instrument and
     * the RBNB server.  
     */
    public boolean stop() {

        // return false if the thread is not running
        if (!isRunning()) {
            return false;
        }

        // stop the thread and disconnect from the RBNB server
        stopThread();
        disconnect();
        return true;
    }

    /**
     * A method that interrupts the thread created in startThread()
     */
    private void stopThread() {
        // set the streaming status to false and stop the Thread
        readyToStream = false;
        streamingThread.interrupt();
    }

    /**
     * A method that starts the connection with the RBNB DataTurbine
     */
    public boolean startConnection() {
        return connect();
    }

    /**
     * A method that stops the connection with the RBNB DataTurbine
     */
    public void stopConnection() {
        disconnect();
    }

}