edu.hawaii.soest.kilonalu.adam.AdamSource.java Source code

Java tutorial

Introduction

Here is the source code for edu.hawaii.soest.kilonalu.adam.AdamSource.java

Source

/**
 *  Copyright: 2007 Regents of the University of Hawaii and the
 *             School of Ocean and Earth Science and Technology
 *    Purpose: To convert Advantech ADAM 60XX binary engineering data 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.adam;

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

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

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

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.net.UnknownHostException;

import java.nio.ByteBuffer;

import java.text.SimpleDateFormat;

import java.util.Arrays;
import java.util.Date;
import java.util.TreeMap;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
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.XMLConfiguration;

import org.apache.commons.lang.exception.NestableException;

import org.apache.log4j.Logger;
import org.apache.log4j.BasicConfigurator;
import org.apache.log4j.PropertyConfigurator;

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

/**
 * A simple class used to process a binary data stream from an Advantech
 * ADAM 6XXX module.  The data stream is 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 is performed by the <code>AdamParser</code>
 * class.
 *
 */
public class AdamSource extends RBNBSource {

    /**
     * The default log configuration file location
     */
    private final String DEFAULT_LOG_CONFIGURATION_FILE = "lib/log4j.properties";

    /**
     * The log configuration file location
     */
    private String logConfigurationFile = DEFAULT_LOG_CONFIGURATION_FILE;

    /**
     * The Logger instance used to log system messages 
     */
    private static Logger logger = Logger.getLogger(AdamSource.class);

    ///**
    // * The XML configuration file location for the list of sensor properties
    // */
    //private String xmlConfigurationFile = "lib/sensor.properties.xml";

    /*
     *  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.
     */
    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 = 256; // bytes

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

    /*
     *  A default source IP address for the source sensor data
     */
    private final String DEFAULT_SOURCE_HOST_NAME = "localhost";

    /*
     *  A default channel name for the source sensor ASCII data
     */
    private final String DEFAULT_CHANNEL_NAME = "DecimalASCIISampleData";

    /**
     * The RBNB channel name for the ASCII data
     */
    private String rbnbChannelName = DEFAULT_CHANNEL_NAME;

    /** 
     * The date format for the timestamp applied to the LOOP sample 04 Aug 2008 09:15:01
     */
    private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("dd MMM yyyy HH:mm:ss");

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

    /*
     * The instance of the AdamParser object used to parse the binary LOOP
     * data packet and retrieve each of the data fields
     */
    private AdamParser adamParser = null;

    /**
     * Constructor - create an empty instance of the AdamSource 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 AdamSource() {
    }

    /**
     * Constructor - create an instance of the AdamSource 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 AdamSource(String serverName, String serverPort, String archiveMode, int archiveFrameSize,
            int cacheFrameSize, String rbnbClientName) {

        // Set up a simple logger that logs to the console                   
        PropertyConfigurator.configure(getLogConfigurationFile());

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

    /**
     * A method that processes the data ByteBuffer passed in for the given IP
     * address of the ADAM sensor, parses the binary ADAM data, and flushes the
     * data to the DataTurbine given the sensor properties in the XMLConfiguration
     * passed in.
     *
     * @param datagramAddress - the IP address of the datagram of this packet of data
     * @param xmlConfig       - the XMLConfiguration object containing the list of
     *                          sensor properties
     * @param sampleBuffer    - the binary data sample as a ByteBuffer
     */
    protected boolean process(String datagramAddress, XMLConfiguration xmlConfig, ByteBuffer sampleBuffer) {

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

        boolean failed = 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 (sensors.properties.xml)
            // and the AdamParser object (to get the actual voltages for each ADAM channel)
            ChannelMap rbnbChannelMap = new ChannelMap(); // used to flush channels
            ChannelMap registerChannelMap = new ChannelMap(); // used to register channels
            int channelIndex = 0;

            this.adamParser = new AdamParser(sampleBuffer);

            logger.debug("\n" + "channelZero       : " + this.adamParser.getChannelZero() + "\n"
                    + "channelOne        : " + this.adamParser.getChannelOne() + "\n" + "channelTwo        : "
                    + this.adamParser.getChannelTwo() + "\n" + "channelThree      : "
                    + this.adamParser.getChannelThree() + "\n" + "channelFour       : "
                    + this.adamParser.getChannelFour() + "\n" + "channelFive       : "
                    + this.adamParser.getChannelFive() + "\n" + "channelSix        : "
                    + this.adamParser.getChannelSix() + "\n" + "channelSeven      : "
                    + this.adamParser.getChannelSeven() + "\n" + "channelAverage    : "
                    + this.adamParser.getChannelAverage() + "\n" + "channelZeroMax    : "
                    + this.adamParser.getChannelZeroMax() + "\n" + "channelOneMax     : "
                    + this.adamParser.getChannelOneMax() + "\n" + "channelTwoMax     : "
                    + this.adamParser.getChannelTwoMax() + "\n" + "channelThreeMax   : "
                    + this.adamParser.getChannelThreeMax() + "\n" + "channelFourMax    : "
                    + this.adamParser.getChannelFourMax() + "\n" + "channelFiveMax    : "
                    + this.adamParser.getChannelFiveMax() + "\n" + "channelSixMax     : "
                    + this.adamParser.getChannelSixMax() + "\n" + "channelSevenMax   : "
                    + this.adamParser.getChannelSevenMax() + "\n" + "channelAverageMax : "
                    + this.adamParser.getChannelAverageMax() + "\n" + "channelZeroMin    : "
                    + this.adamParser.getChannelZeroMin() + "\n" + "channelOneMin     : "
                    + this.adamParser.getChannelOneMin() + "\n" + "channelTwoMin     : "
                    + this.adamParser.getChannelTwoMin() + "\n" + "channelThreeMin   : "
                    + this.adamParser.getChannelThreeMin() + "\n" + "channelFourMin    : "
                    + this.adamParser.getChannelFourMin() + "\n" + "channelFiveMin    : "
                    + this.adamParser.getChannelFiveMin() + "\n" + "channelSixMin     : "
                    + this.adamParser.getChannelSixMin() + "\n" + "channelSevenMin   : "
                    + this.adamParser.getChannelSevenMin() + "\n" + "channelAverageMin : "
                    + this.adamParser.getChannelAverageMin() + "\n"

            );

            // create a TreeMap to hold the voltageChannel and its associated
            // RBNB ChannelMap channel string.  When the RBNB ChannelMap is
            // populated, this TreeMap will be consulted
            TreeMap<Integer, String> voltageChannelTreeMap = new TreeMap<Integer, String>();

            // create a character string to store characters from the voltage values
            StringBuilder decimalASCIISampleData = new StringBuilder();

            // Create a list of sensors from the properties file, and iterate through
            // the list, matching the datagram IP address to the address in the 
            // xml configuration file.  If there is a match, find the correct voltage
            // channel to measurement mappings, create a corresponding RBNB channel
            // map, and flush the data to the DataTurbine.        

            List sensorList = xmlConfig.getList("sensor.address");

            // declare the properties that will be pulled from the 
            // sensor.properties.xml file
            String address = "";
            String sourceName = "";
            String description = "";
            String type = "";
            String cacheSize = "";
            String archiveSize = "";
            String archiveChannel = "";
            String portNumber = "";
            String voltageChannel = "";
            String measurement = "";

            // evaluate each sensor listed in the sensor.properties.xml file
            for (Iterator sIterator = sensorList.iterator(); sIterator.hasNext();) {

                // get each property value of the sensor
                int index = sensorList.indexOf(sIterator.next());
                address = (String) xmlConfig.getProperty("sensor(" + index + ").address");
                sourceName = (String) xmlConfig.getProperty("sensor(" + index + ").name");
                description = (String) xmlConfig.getProperty("sensor(" + index + ").description");
                type = (String) xmlConfig.getProperty("sensor(" + index + ").type");

                logger.debug("Sensor details:" + "\n\t\t\t\t\t\t\t\t\t\taddress     : " + address
                        + "\n\t\t\t\t\t\t\t\t\t\tname        : " + sourceName
                        + "\n\t\t\t\t\t\t\t\t\t\tdescription : " + description
                        + "\n\t\t\t\t\t\t\t\t\t\ttype        : " + type);

                // move to the next sensor if this doesn't match the RBNB source name
                if (!sourceName.equals(getRBNBClientName())) {
                    continue;
                }

                List portList = xmlConfig.getList("sensor(" + index + ").ports.port[@number]");
                // get each port of the sensor, along with the port properties
                for (Iterator pIterator = portList.iterator(); pIterator.hasNext();) {
                    int pindex = portList.indexOf(pIterator.next());

                    // get the port number value
                    portNumber = (String) xmlConfig
                            .getProperty("sensor(" + index + ").ports.port(" + pindex + ")[@number]");

                    logger.debug("\tport " + portNumber + " details:");

                    List measurementList = xmlConfig
                            .getList("sensor(" + index + ").ports.port(" + pindex + ").measurement[@label]");

                    // get each measurement and voltageChannel for the given port
                    for (Iterator mIterator = measurementList.iterator(); mIterator.hasNext();) {
                        int mindex = measurementList.indexOf(mIterator.next());

                        // build the property paths into the config file
                        String voltagePath = "sensor(" + index + ").ports.port(" + pindex + ").measurement("
                                + mindex + ").voltageChannel";

                        String measurementPath = "sensor(" + index + ").ports.port(" + pindex + ").measurement("
                                + mindex + ")[@label]";

                        // get the voltageChannel and measurement label values
                        voltageChannel = (String) xmlConfig.getProperty(voltagePath);
                        measurement = (String) xmlConfig.getProperty(measurementPath);
                        logger.debug("\t\t" + "voltageChannel: " + voltageChannel + "\n\t\t\t\t\t\t\t\t\t\t\t"
                                + "measurement label: " + measurement);

                        // Match the datagram address with the address in the xmlConfig file
                        if (datagramAddress.equals(address)) {

                            // and only add channel data for this class instance RBNB Source name
                            if (sourceName.equals(getRBNBClientName())) {

                                // create an Integer out of the voltageChannel
                                Integer voltageChannelInt = new Integer(voltageChannel);
                                // build the RBNB channel path string
                                String channelPath = "port" + "/" + portNumber + "/" + measurement;
                                voltageChannelTreeMap.put(voltageChannelInt, channelPath);

                            } else {
                                logger.debug("\t\tSource names don't match: " + sourceName + " != "
                                        + getRBNBClientName());

                            } // end sourceName if() statement

                        } else {
                            logger.debug("\t\tNo IP address match. " + datagramAddress + " != " + address);

                        } //end IP address if() statement
                    } // end for each channel
                } // end for each port

                // now that we've found the correct sensor, exit the sensor loop
                break;

            } // end for each sensor

            // Build the RBNB channel map from the entries in the tree map
            // by doing a lookup of the ADAM voltage channel values based
            // on the voltage channel number in the treemap.  Also add the voltages
            // to the DecimalASCIISampleData string (and then channel)
            for (Iterator vcIterator = voltageChannelTreeMap.keySet().iterator(); vcIterator.hasNext();) {

                int voltageChannelFromMap = ((Integer) vcIterator.next()).intValue();
                String channelPathFromMap = voltageChannelTreeMap.get(voltageChannelFromMap);
                float voltageValue = -9999.0f;

                // look up the voltage value from the AdamParser object based
                // on the voltage channel set in the xmlConfig file (via the treemap)
                switch (voltageChannelFromMap) {
                case 0:
                    voltageValue = this.adamParser.getChannelZero();
                    break;
                case 1:
                    voltageValue = this.adamParser.getChannelOne();
                    break;
                case 2:
                    voltageValue = this.adamParser.getChannelTwo();
                    break;
                case 3:
                    voltageValue = this.adamParser.getChannelThree();
                    break;
                case 4:
                    voltageValue = this.adamParser.getChannelFour();
                    break;
                case 5:
                    voltageValue = this.adamParser.getChannelFive();
                    break;
                case 6:
                    voltageValue = this.adamParser.getChannelSix();
                    break;
                case 7:
                    voltageValue = this.adamParser.getChannelSeven();
                    break;
                }

                // now add the channel and the voltage value to the RBNB channel maps

                channelIndex = registerChannelMap.Add(channelPathFromMap);
                registerChannelMap.PutUserInfo(channelIndex, "units=volts");
                registerChannelMap.PutUserInfo(channelIndex, "description=" + description);

                logger.debug("Voltage Channel Tree Map: " + voltageChannelTreeMap.toString());

                // then the channel and voltage
                channelIndex = rbnbChannelMap.Add(channelPathFromMap);
                rbnbChannelMap.PutMime(channelIndex, "application/octet-stream");
                rbnbChannelMap.PutDataAsFloat32(channelIndex, new float[] { voltageValue });
                decimalASCIISampleData.append(String.format("%05.3f", (Object) voltageValue) + ", ");

            }

            // and only flush data for this class instance RBNB Source name
            if (sourceName.equals(getRBNBClientName()) && datagramAddress.equals(address)) {

                // add the timestamp to the rbnb channel map
                registerChannelMap.PutTimeAuto("server");
                rbnbChannelMap.PutTimeAuto("server");

                // then add a timestamp to the end of the ASCII version of the sample
                DATE_FORMAT.setTimeZone(TZ);
                String sampleDateAsString = DATE_FORMAT.format(new Date()).toString();
                decimalASCIISampleData.append(sampleDateAsString);
                decimalASCIISampleData.append("\n");

                // add the DecimalASCIISampleData channel to the channelMap
                channelIndex = registerChannelMap.Add(getRBNBChannelName());
                channelIndex = rbnbChannelMap.Add(getRBNBChannelName());
                rbnbChannelMap.PutMime(channelIndex, "text/plain");
                rbnbChannelMap.PutDataAsString(channelIndex, decimalASCIISampleData.toString());

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

                sampleBuffer.clear();
            } else {
                logger.debug("\t\tSource names don't match: " + sourceName + " != " + getRBNBClientName());
                registerChannelMap.Clear();
                rbnbChannelMap.Clear();

                sampleBuffer.clear();
            }

        } 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.
            failed = true;
            sapie.printStackTrace();
            return !failed;

        }

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

    /**
     * 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 name of the RBNB channel that contains the 
     * streaming data from this instrument
     */
    public String getRBNBChannelName() {
        return this.rbnbChannelName;
    }

    /**
     * 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.
     */

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

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

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

    /**
     * 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) {

        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 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());

        return options;
    }

    /**
     * A method that gets the log configuration file location
     *
     * @return logConfigurationFile  the log configuration file location
     */
    public String getLogConfigurationFile() {
        return this.logConfigurationFile;
    }

    /**
     * A method that sets the log configuration file name
     *
     * @param logConfigurationFile  the log configuration file name
     */
    public void setLogConfigurationFile(String logConfigurationFile) {
        this.logConfigurationFile = logConfigurationFile;
    }

}