Skip to content

X GSN Develop

sarni edited this page Nov 28, 2013 · 14 revisions

System requirements

X-GSN requires Java JDK 1.6 to run and is not compatible with higher Java versions. If you are using Java 1.7 or higher, you have to explicitly compile X-GSN with 1.6 as the source and target versions. If you use the Maven build file, this will be done automatically through the compiler plugin.

X-GSN relies on a number of other libraries but all are specified in the maven dependencies. In particular, it has an embedded web server based on Jetty.

Download

Source code can be cloned using GIT from OpenIoT’s repository on GitHub at the following address: https://github.com/OpenIotOrg/openiot.git

X-GSN module can be found under modules/x-gsn

Deploy from source code

X-GSN relies on Maven for its deployment. If you have not yet done so, you must configure Maven before testing deployment.

Once Maven is correctly configured, you can generate the X-GSN jar file and fetch necessary libraries by calling the “package” phase, as follows:

mvn package

This phase automatically runs all the necessary phases for preparing X-GSN to be executed including the following procedures:

  • Compile
  • Binding XML beans descriptions to Java through JiBX
  • Generating JAR file
  • Copying all dependencies from local Maven repository to target folder in order to be included in the class path for the execution of X-GSN.

Once all JAR files are in place, you can run X-GSN with the following commands:

gsn-start	Starts the X-GSN server
gsn-stop	Stops the X-GSN server
lsm-register	Registers sensor metadata to LSM

The X-GSN web server will be running and listening on default port 22001. You can see it running by browsing the page at: http://localhost:22001

Running or debugging in Eclipse

X-GSN can be started with the gsn-start command (see previous paragraph). If you want to run or debug it it within Eclipse, you have to specify the class to run, the classpath and set a few JVM arguments, as follows:

  • class to run: org.openiot.gsn.Main
    • command line argument (control port number): 22232
  • classpath: make sure to include all the necessary JAR files (prepared by maven phases)
  • JVM arguments:
    • -Dorg.apache.commons.logging.Log=org.apache.commons.logging.impl.Log4JLogger
    • -Dorg.mortbay.log.LogFactory.noDiscovery=false

Writing new wrappers

All standard wrappers are subclasses of gsn.wrapper.AbstractWrapper. Subclasses must implement the following four (4) methods:

  1. boolean initialize()
  2. void finalize()
  3. String getWrapperName()
  4. DataField[] getOutputFormat()

Each wrapper is a thread in the GSN. If you want to do some kind of processing in a fixed time interval, you can override the run() method. The run method is useful for time driven wrappers in which the production of a sensor data is triggered by a timer. Optionally, you may wish to override the method

boolean sendToWrapper(String action, String[] paramNames, Object[] paramValues);

initialize() method

This method is called after the wrapper object creation. The complete method prototype is as follows:

public boolean initialize();

In this method, the wrapper should try to initialize its connection to the actual data producing/receiving device(s) (e.g., wireless sensor networks or cameras). The wrapper should return true if it can successfully initialize the connection, false otherwise. GSN provides access to the wrapper parameters through the following method call:

getActiveAddressBean().getPredicateValue("parameter-name");

For example, if you have the following fragment in the virtual sensor configuration file: `<source ...>

100 0
`

You can access the initialization parameter named range with the following code:

if(getActiveAddressBean().getPredicateValue("range") != null)
{...}

By default GSN assumes that the timestamps of the data produced in a wrapper are local, that is, the wrapper produced them using the system (or GSN) time. If you have cases where this assumption is not valid and GSN should assume remote timestamps for stream elements, add the following line in the initialize() method:

setUsingRemoteTimestamp(true);

finalize()method

In finalize() method, you should release all the resources you acquired during the initialization procedure or during the life cycle of the wrapper. Note that this is the last chance for the wrapper to release all its reserved resources and after this call the wrapper instance virtually won't exist anymore. For example, if you open a file in the initialization phase, you should close it in the finalization phase.

getWrapperName()method

This method returns a name for the wrapper.

getOutputFormat()method

The method getOutputFormat() returns a description of the data structure produced by this wrapper. This description is an array of DataField objects. A DataField object can be created with a call to the constructor

public DataField(String name, String type, String description)

The name is the field name, the type is one of GSN data types (TINYINT, SMALLINT, INTEGER, BIGINT, CHAR(#), BINARY[(#)], VARCHAR(#), DOUBLE, TIME) , and description is a text describing the field. The following examples should help you get started:

Wireless Sensor Network Example

Assuming that you have a wrapper for a wireless sensor network which produces the average temperature and light value of the nodes in the network, you can implement getOutputFormat() as follows:

public DataField[] getOutputFormat() {
DataField[] outputFormat = new DataField[2];
outputFormat[0] = new DataField("Temperature", "double","Average of temperature readings from the sensor network");
outputFormat[1] = new DataField("light", "double","Average of light readings from the sensor network");
return outputFormat;
}

Webcam Example

If you have a wrapper producing jpeg images as output (e.g., from wireless camera), the method is similar to below:

public DataField[] getOutputFormat() {
    DataField[] outputFormat = new DataField[1];
    outputFormat[0] = new DataField("Picture", "binary:jpeg",
            "Picture from the Camera at room BC143");
    return outputFormat;
}

run() method

As described before, the wrapper acts as a bridge between the actual hardware device(s) or other kinds of stream data sources and GSN, thus in order for the wrapper to produce data, it should keep track of the newly produced data items. This method is responsible for forwarding the newly received data to the GSN engine. You should not try to start the thread by yourself: GSN takes care of this. The method should be implemented as below :

while(isActive()) {
{
  // The thread should wait here until arrival of a new data notifies it
  // or the thread should sleep for some finite time before polling the data source or producing 
  // the next data
}
//Application dependent processing ...
StreamElement streamElement = new StreamElement ( ...);
postStreamElement( streamElement ); // This method in the AbstractWrapper sends the data to the registered StreamSources

}

Webcam example

Assume that we have a wireless camera which runs a HTTP server and provides pictures whenever it receives a GET request. In this case we are in a data on demand scenario (most of the network cameras are like this). To get the data at the rate of 1 picture every 5 seconds we can do the following:

while(isActive()) {
    byte[] received_image = getPictureFromCamera();
    postStreamElement(System.currentTimeMillis(), new Serializable[] {received_image});
    Thread.sleep(5*1000); // Sleeping 5 seconds

}

Data driven systems

Compared to the previous example, we do sometimes deal with devices that are data driven. This means that we don't have control of either when the data is produced by them (e.g., when they do the capturing) or the rate at which data is received from them. For example, having an alarm system, we don't know when we are going to receive a packet, or how frequently the alarm system will send data packets to GSN. These kind of systems are typically implemented using a callback interface. In the callback interface, one needs to set a flag indicating the data reception state of the wrapper and control that flag in the run method to process the received data.

sendToWrapper()

In GSN, the wrappers can not only receive data from a source, but also send data to it. Thus wrappers are actually two-way bridges between GSN and the data source. In the wrapper interface, the method sendToWrapper() is called whenever there is a data item which should be send to the source. A data item could be as simple as a command for turning on a sensor inside the sensor network, or it could be as complex as a complete routing table which should be used for routing the packets in the sensor network. The full syntax of sendToWrapper() is as follows:

public boolean sendToWrapper(String action, String[] paramNames, Object[] paramValues) throws OperationNotSupportedException;

The default implementation of the afore-mentioned method throws an OperationNotSupportedException exception because the wrapper doesn't support this operation. This design choice is justified by the observation that not all kind of devices (sensors) can accept data from a computer. For instance, a typical wireless camera doesn't accept commands from the wrapper. If the sensing device supports this operation, one needs to override this method so that instead of the default action (throwing the exception), the wrapper sends the data to the sensor. You can consult the gsn.wrappers.general.SerialWrapper class for an example.

A fully functional wrapper

/**
 * This wrapper presents a MultiFormat protocol in which the data comes from the
 * system clock. Think about a sensor network which produces packets with
 * several different formats. In this example we have 3 different packets
 * produced by three different types of sensors. Here are the packet structures
 * : [temperature:double] , [light:double] , [temperature:double, light:double]
 * The first packet is for sensors which can only measure temperature while the
 * latter is for the sensors equipped with both temperature and light sensors.
 * 
 */
public class MultiFormatWrapper extends AbstractWrapper {
  private DataField[] collection = new DataField[] { new DataField("packet_type", "int", "packet type"),
      new DataField("temperature", "double", "Presents the temperature sensor."), new DataField("light",  "double", "Presents the light sensor.") };
  private final transient Logger logger = Logger.getLogger(MultiFormatWrapper.class);
  private int counter;
  private AddressBean params;
  private long rate = 1000;
  public boolean initialize() {
    setName("MultiFormatWrapper" + counter++);    
    params = getActiveAddressBean();  
    if ( params.getPredicateValue( "rate" ) != null ) {
      rate = (long) Integer.parseInt( params.getPredicateValue( "rate"));     
      logger.info("Sampling rate set to " + params.getPredicateValue( "rate") + " msec.");
    }   
    return true;
  }
  public void run() {
    Double light = 0.0, temperature = 0.0;
    int packetType = 0;   
    while (isActive()) {
      try {
        // delay 
        Thread.sleep(rate);
      } catch (InterruptedException e) {
        logger.error(e.getMessage(), e);
      }   
      // create some random readings
      light = ((int) (Math.random() * 10000)) / 10.0;
      temperature = ((int) (Math.random() * 1000)) / 10.0;
      packetType = 2;
      // post the data to GSN
      postStreamElement(new Serializable[] { packetType, temperature, light });       
    }
  }
  public DataField[] getOutputFormat() {
    return collection;
  }
  public String getWrapperName() {
    return "MultiFormat Sample Wrapper";
  }  
  public void finalize() {
    counter--;
  }
}

Writing new processing classes

In GSN, a processing class is a piece of code which acts in the final stage of data processing as sits between the wrapper and the data publishing engine. The processing class is the last processing stage on the data and its inputs are specified in the virtual sensor file. All virtual sensors are subclass of the AbstractVirtualSensor (package gsn.vsensor). It requires its subclasses to implement the following three methods:

public boolean initialize();
public void dataAvailable(String inputStreamName, StreamElement se);
public void dispose();

initialize() method

initialize is the first method to be called after object creation. This method should configure the virtual sensor according to its parameters, if any, and return true in case of success, false if otherwise. If this method returns false, GSN will generate an error message in the log and stops using the processing class hence stops loading the virtual sensor.

Dispose() method

dispose is called when GSN destroys the virtual sensor. It should release all system resources in use by this virtual sensor. This method is typically called when we want to shutdown the GSN instance.

dataAvailable() method

dataAvailable is called each time that GSN has data for this processing class, according to the virtual sensor file. If the processing class produces data, it should encapsulate this data in a StreamElement object and deliver it to GSN by calling dataProduced(StreamElement se) method. Note that a processing class should always use the same StreamElement structure for delivering its output. Changing the structure type is not allowed and trying to do so will result in an error. However, a virtual sensor can be configured at initialization time with the kind of StreamElement it will produce (e.g., setting the output type to be the super set of all the possible outputs and providing null whenever the value is missing). This allows to produce different types of StreamElements by the same VS depending on its usage. But one instance of the VS will still be limited to produce the same structure type. If a virtual sensor really needs to produce several different stream elements, user must provide the set of all possibilities in the stream elements and provide Null whenever the data item is not applicable. The processing class can read the init parameters from the virtual sensor description file. Example:

<class-name>gsn.vsensor.MyProcessor</class-name>
<init-params>
    <param name="param1">DATA</param>
    <param name="param2">1234</param> 
</init-params> 

And inside the processing class's initialization method:

String param1 = getVirtualSensorConfiguration().getMainClassInitialParams().get( "param1" );
String param2 = getVirtualSensorConfiguration().getMainClassInitialParams().get( "param2" );
Clone this wiki locally