How to create a sensor plugin

From RifidiWiki

Jump to: navigation, search

This page describes how to develop a plugin using the Rifidi Edge Server Sensor API that connects to a sensor and collects information from it. For a more detailed description of the architecture of the sensor API, see this page. This HOWTO assumes that you know java, are relatively familiar with eclipse (or an equivalent IDE such as netbeans). It does not require that you know OSGi or spring dependency injection, but knowledge in these areas will help you understand why the steps are being taken and will help debugging.

Prerequisites

Naming your project
We recommend a name such as the following: com.yourcompany.org.rifidi.edge.sensorplugin.yourreadertype where yourcompany is the name of your company, and yourreadertype is the brand or model of the sensor you are making the plugin for.
  1. Follow the instructions on how to set up a Rifidi Edge Server development environment.
  2. Use the Plug-in Project wizard in eclipse to create a new plugin-project. Make sure that the target platform is set to run with a standard OSGi framework. In the second page, uncheck the option to automatically generate an Activator. In the third page, do not use a template.
  3. Open up the Manifest.MF file. Click on the dependencies tab. Add the following Plug-in dependencies:
    org.rifidi.edge.core
    org.rifidi.edge.core.services
    org.rifidi.edge.api
  4. Add 'Import package' dependencies to the following pacakges:
    javax.jms
    org.apacahe.activemq.command
    org.apache.commons.logging
    org.osgi.framework
    org.springframework.core
    org.springframework.jms.core
    org.springframework.osgi.service.importer
  5. Add a new folder called 'spring' into the META-INF folder
  6. Add a new file called spring.xml in the spring folder
  7. Add a new file called osgi.xml in the spring folder
  8. Add a new package. Typically the package has the same name as your project (for example, if the project is called com.yourcompany.org.rifidi.edge.sensorplugin.yourreadertype, that is also the name of the top level package.

Quick Start

Viewing Source of Other Sensor Plugins
This HOWTO references existing plugins. One of the best ways to learn how to make your own sensor plugin is by looking at the soruce code of other plugins. To view the source code of the plugins, open up the plugin view, find the plugin (such as the org.rifidi.edge.readerplugin.alien plugin) right-click on it and select import as->source project.

This is a high level overview of what needs to happen to create a new sensor plugin. You should look at the source for other reader plugins and read the explanations in this document to get a better idea about how to create them.

  1. Create a Session that extends AbstractPollIPSensorSession (if your sensor uses TCP/IP) or AbstractSensorSession if it does not.
  2. Create a Sensor that extends AbstractSensor. It should produce sessions of the type created in the previous step.
  3. Create a SensorFactory that extends AbstractSensorFactory. It should produce sensors of the type created in the previous step.
  4. Configure the osgi.xml and spring.xml files so that the AbstractSensorFactory is created and registered in the OSGi registry
  5. If your session is not TCP/IP based, write the connection logic
  6. If you session is TCP/IP based, write the onConnect method and create a MessageParsingStrategy.
  7. Add the necessary getter/setter properties to the Sensor. TCP/IP based readers normally have the following properties:
    IP:String
    Port:Integer
    Reconnect Interval: Integer
    Reconnection Attempts: Integer
  8. Add the @Property annotations over the getter methods
  9. Create a new package which will contain commands
  10. Create a Command class that extends Command
  11. Create a CommandConfiguration class that extends AbstrctCommandConfiguration. It should produce commands of the type created in the previous step.
  12. Create a CommandConfiguraitonFactory class that extends AbstractCommandConfigurationFactory. It should produce command configurations of the type created in the previous step.
  13. Configure the osgi.xml and spring.xml to create and register the CommandConfigurationFactory.
  14. Add the required functionality in the run method of the command
  15. Add getter and setter properties (if necessary) to the CommandConfiguration.
  16. Add @Property methods over the getter methods in the Commandconfiguration
  17. Add in notifier service calls when one of the notifier service's events happens (such as a session being created, started, or stopped).

Short Introduction to the Sensor API

The sensor factory consists of three main components:

SensorFactory
The sensor factory creates new Sensors when the createInstance method is called. There is one instance of SensorFactory per sensor type. For example, in a running instance of the edge server, there is only one instance of an AlienSensorFactory which can create multiple instances of AlienSensor objects. The SensorFactory is normally created in the spring.xml of a sensor plugin and registered in the OSGi service registry in the osgi.xml.
Sensor
The Sensor object's main duty is to create SensoSession objects. There is normally one Sensor object for each physical sensor in the RFID system. For example, if your system has one Alien reader at a Dock Door and one at a weigh station, then there would be two instances of the AlienSensor object. Sensors also normally have getter-setter methods for setting properties of that sensor. For example, it is common for sensors to have methods to get and set the IP and port of the sensor to connect to. These properties are then used when creating new sessions. It is possible for a sensor to have more than one session (certain sensors (such as a database plugin) may support parallel sessions), but it is common for the sensor to allow only one session at a time.
SensorSession
The SensorSession has two main functions: 1) To connect to a sensor (over a network, serial, usb, etc) and to read data from it, and 2) To manage commands that have been submitted. Many sensors offer a networked TCP/IP interface. The Rifidi Sensor API offers abstract classes that other classes can extend which will take care of the TCP sockets and threads. If the manufacturer of the sensor makes a java API available to connect to the sensor, the Rifidi Sensor API allows you to use that instead.
This image shows the relationship between the three main classes needed to create a plugin for the Rifidi Sensor Abstraction Layer. Only a few important methods are shown in each class, just to give the reader an idea about what the class does
.

Creating the Classes

The Sensor API provides several base classes for you to extend when creating a new sensor plugin. Because the Sensor and SensorFactory classes use generics when referring to the classes that they can create, it is easiest to create the session first, then the Sensor, the the SensorFactory.

Session

As stated above, the session has two main functions: 1) to connect to a sensor and read data from it, and 2) To handle the submission and execution of commands. Every session must extend the SensorSession abstract class at some level. However, most SensorSession implementations won't extend the SensorSession abstract class directly and will instead take advantage of one of the other abstract classes that provide some basic functionality.

Avtive vs Passive Sessions

The first thing to figure out when creating the session class is whether the session is active or passive.

Active Sessions
An active session is one in which it is possible to send commands to the sensor. For example, the AlienSession is active because it is possible to send a command such as 'get taglist'. An LLRPSession is active because it can send GET_ROSPEC messages. A database session would probably be active since you would probably want to submit SQL queries to it. In fact, most sessions will probably be active ones.
Passive Sessions
Passive session simply listen for the sensors to push tag reads to them. They cannot send commands to the sensor. The only example of this kind of session at the moment is the AlienAutonomousSession. It simply opens up a port and listens for tag reads to be pushed to it by an Alien Reader operating in autonomous mode.

Please note that this active/passive terminology has nothing to do with active and passive RFID tag technology.

Communication Channel

The next thing to figure out about the sensor you are connecting to is the medium of communication. Many sensors are networked and use TCP/IP. However, the Sensor API is channel-agnostic, and it is possible to connect to sensors that use USB or serial, for example. Currently, the Sensor API only has base classes that handle the work of connecting to TCP/IP sensors. If the sensor is USB or serial, you will need to write this functionality yourself.

Tag Request Model

Active SensorSession normally operate in one of two ways when receiving tag data from a sensor

Poll
In this case the SensorSession will request that the Sensor send back its tag data immediately. An example of this is the Alien's 'get taglist' command.
Push
In this case, the SensorSession will configure the Sensor to send back tag data continuously until some stop trigger happens (e.g. sending a stop command)

Java API Availability

Sometimes the manufacturer of the sensor provides a java API that handles the connection to the sensor. If so, you can use this API. You will need to turn the supplied jar into an OSGi bundle if it is not one already. This can be accomplished by using the "new plug-in project from existing jar" wizard from eclipse.

SensorSession Hierarchy

There are several base classes you can extend in the Sensor API that provide useful SensorSession functionality.

  • SensorSession - This is the class at the root of the hierarchy. It has many abstract methods for concrete classes implement.
    • AbstractSensorSession - This is an abstract class that extends SensorSession by adding functionality for submitting and killing commands. All concrete SensorSessions will probably at least want to extend this class. If your sensor has an API that manages the communication channel for you, or if your sensor does not use TCP/IP you will want to extend this class directly. For an example of a SensorSession that extends this class directly see the LLRPSession
      • AbstractIPSensorSession - This is a base class for a SensorSession that uses TCP/IP. It handles socket I/O and threads. This class will not normally be extended directly by concrete implementations. Instead, concrete implementations should extend one of the following two classes:
        • AbstractPollIPSensorSession - This class should be extended by sensor sessions that use TCP/IP communication and operate by periodically polling the sensor for tag data. See AlienSensorSession for an example
        • AbstractPubSubIPSensorSession - This class should be extended by sensor sessions that use TCP/IP communication and operate by configuring the sensors to push data back to them. See the AwidSensorSession as an example.
      • AbstractServerSocketSensorSession - This class should be used by passive sessions that use TCP/IP communication. See AlienAutonomousSensorSession for an example.

Sesnor

After creating your Session class, its time to create the sensor class. All sensor classes should extend the AbstractSensor class. There are two important pieces to the Sensor class.

Creating Session Objects

The sensor object's most important functionality is to create sessions. Most sensors will only have one session active at a time, however the API is designed to allow sensors to have multiple sessions. There are two methods for creating sensor sessions. The one that takes a SessionDTO is used to recreate persisted sessions.

Property Getters and Setters

The sensors expose getter and setters methods to allow users to configure the sensor. These getter and setter methods are also used to recreate sensors from persistence. Each getter and setter method pair has an @Property annotation over the getter method. This exposes the property to workbench via Mbeans objects.

One important thing to understand is that sessions are immutable; Once they are created, users cannot adjust properties on them. For example, suppose a sensor exposes a port property. A user can adjust this property using the getter/setter methods. Once the session is created however, changing the port property has no effect on the created session. To modify the port on the session, the user must destroy the session and create a new one.

SensorFactory

The SensorFactory is the class that creates the Sensor objects. All concrete implementations should extend the AbstractSensorFactory class. Most AbstractSensor factories will need to have two object injected by spring:

NotifierService
This service allows the sensor and sensorSession to notify clients (such as workbench) when an important event happens such as a session being created. For more information on this service see this page.
JMSTemplate
Tag events need to be placed on the internal JMS queue. The template is a helper object from spring that makes it easy to send JMS notification messages. For more information about JMS in the edge server see this page.

Short Introduction to the Command Framework

Although command and sensor classes can exist in the same bundle, it is encouraged to separate out these into separate bundles. This allows commands to be updated at runtime without bringing down a whole sensor bundle.

The SensorSession objects execute commands from the Command framework. The command framework enables commands to be created and configured. There are three main components to the command framwork:

CommandConfigurationFactory
A CommandConfigurationFactory produces CommandConfiguration objects. There is one instance of a CommandConfigurationFactory for every command type. For example, the Alien sensor plugin has three command types available to it: Alien-Poll, Alien-Push-Start, and Alien-Push-Stop. That means there are three factories instantiated when the plugin starts. The factories are created in the spring.xml and registered in the OSGi registry in the osgi.xml.
CommandConfiguration
A CommandConfiguration produces commands. Users can modify parameters of a Command by using the properties exposed through the @Property annotations and getter/setter methods.
Command
A command is a runnable that wraps some logical piece of communication with a sensor. For example, the LLRPConfigure command configures the LLRP command to send back tag reads. To do this, it sends several LLRP messages. Some commands should be scheduled for repeated execution, while others should only execute once. Regardless, the run() method inside a command should execute quickly (i.e. there should be no loops that wait on I/O or sleep).

Creating the Command Classes

For each kind of command that you have, you need to implement three classes:

  1. A Command class that extends the Command abstract class. The run method is where the functionality goes. You can call super.sensorSession to get a hold of the session which can be used to send or receive data.
  2. A CommandConfiguration class that extends AbstractCommandConfiguration. This class should produce commands. Like the Sensor class, it can have getter/setter methods which allow users to change property values of the CommandConfiguration. Again, once a command has been created, the properties on it cannot change.
  3. A CommandConfigurationFactory class that extends AbstractCommandConfigurationFactory.

Creating Factories and registering them with OSGi Service Registry

All factories (both SensorFactories and CommandConfigurationFactories) must be created and registered using spring.

Sensor Factories

The following is an example of a SensorFactory being created in the spring.xml

<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.springframework.org/schema/beans 
	http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">

	<!--  Create Reader Configuration Factory -->
	<bean id="factory"
		class="org.rifidi.edge.readerplugin.thingmagic.ThingmagicReaderFactory">
		<property name="context" ref="bundleContext" />
		<property name="template" ref="internalMB" />
		<property name="notifierService" ref="JMSNotifierService" />
		<property name="commandConfigurations" ref="thingmagicCommands" />
	</bean>

</beans>

This code shows 1) How to register a sensor factory in the osgi registry and 2) How to get references to the notifier service and the jms template so that they can be injected into the factory in the above xml.

<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:osgi="http://www.springframework.org/schema/osgi"
	xsi:schemaLocation="http://www.springframework.org/schema/beans 
	http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
    http://www.springframework.org/schema/osgi 
    http://www.springframework.org/schema/osgi/spring-osgi.xsd">

	<!-- Put reader configuration service in OSGi Registry -->
	<osgi:service id="thingmagicConfigurationFactoryService" ref="factory">
		<osgi:interfaces>
			<value>org.rifidi.edge.core.configuration.ServiceFactory</value>
			<value>org.rifidi.edge.core.sensors.base.AbstractSensorFactory</value>
		</osgi:interfaces>
	</osgi:service>
	
	<!-- Create a set that listens for ThingMagic command configurations -->
	<osgi:set id="thingmagicCommands" interface="org.rifidi.edge.core.sensors.commands.AbstractCommandConfiguration"
		cardinality="0..N" filter="(reader=ThingMagic)">
		<osgi:listener ref="thingmagicConfigurationFactory" bind-method="bindCommandConfiguration" unbind-method="unbindCommandConfiguration"/>
	</osgi:set>
	
	<!-- Get a reference to the NotifierService -->
	<osgi:reference id="JMSNotifierService"
		interface="org.rifidi.edge.core.services.notification.NotifierService" />

	<!-- Get a reference to the JMS Queue -->
	<osgi:reference id="internalMB"
		interface="org.springframework.jms.core.JmsTemplate" bean-name="internalJMSTemplate" />
</beans>

Notice how these work together: each ref attribute references the ID attribute of another bean. For example, in the spring.xml a bean is crated with an id of "factory". The osgi:service tag references this ID in the osgi.xml.

It is also important that the filter value in the osgi:set is set to the ID of the sensor factory.

Command Factories

The following show the CommandFactories being created for the Alien Sensor:

<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:osgi="http://www.springframework.org/schema/osgi"
	xsi:schemaLocation="http://www.springframework.org/schema/beans 
	http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
    http://www.springframework.org/schema/osgi 
    http://www.springframework.org/schema/osgi/spring-osgi.xsd">

	<bean id="alienGetTagListCommandConfigurationFactory"
		class="org.rifidi.edge.readerplugin.alien.commands.AlienGetTagListCommandConfigurationFactory">
		<property name="context" ref="bundleContext" />
	</bean>
	<bean id="alienAutonomousModeCommandConfigurationFactory"
		class="org.rifidi.edge.readerplugin.alien.commands.AlienAutonomousModeCommandConfigurationFactory">
		<property name="context" ref="bundleContext" />
	</bean>
	<bean id="alienAutonomousModeStopCommandConfigurationFactory"
		class="org.rifidi.edge.readerplugin.alien.commands.AlienAutonomousModeStopCommandConfigurationFactory">
		<property name="context" ref="bundleContext" />
	</bean>	
</beans>

Using @Property annotations

Note:

  1. To make editable in workbench - writable = true and need both a getter and setter defined in the property section - look at displayName as example below - note getter and setters are case sensitive such as getdisplayName and setdisplayName needs to match property displayName
  2. Category tells workbench what category to group parameter under - if category = "connection" then if will be available in New Reader Connection wizard
  3. Type defines as string (PropertyType.PT_STRING) or integer (PropertyType.PT_INTEGER)- if integer one can specify optionally a range minValue = "0", maxValue = "65535" - look at Port property in Alien code as an example
  4. default is the default value
  5. description is the label in workbench
  6. ordervalue specifies form order in workbench
  7. In getter and setters one can create custom validation/display logic if needed - see persistTime property setter as an example

Alien9800Reader.java https://transcends.svn.cloudforge.com/rifidi/rifidi/trunk/org.rifidi.edge.adapter.alien/src/org/rifidi/edge/adapter/alien/Alien9800Reader.java

Alien9800Reader.java @properties snippet below - for such items as displayName, IPAddress, Port etc..

/*
	 * JMX PROPERTY GETTER/SETTERS
	 */

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.rifidi.edge.sensors.base.AbstractSensor#getDisplayName()
	 */
	@Override
	@Property(displayName = "Display Name", description = "Logical Name of Reader", writable = true, type = PropertyType.PT_STRING, category = "connection", defaultValue = "Alien", orderValue = 0)
	public String getDisplayName() {
		return displayName;
	}

	public void setDisplayName(String displayName) {
		this.displayName = displayName;
	}

	/**
	 * @return the IPADDRESS
	 */
	@Property(displayName = "IP Address", description = "IP Address of "
			+ "the Reader", writable = true, type = PropertyType.PT_STRING, category = "conn"
			+ "ection", defaultValue = AlienReaderDefaultValues.IPADDRESS, orderValue = 0)
	public String getIpAddress() {
		return ipAddress;
	}

	/**
	 * @param IPADDRESS
	 *            the IPADDRESS to set
	 */
	public void setIpAddress(String ipAddress) {
		this.ipAddress = ipAddress;
	}

	/**
	 * @return the PORT
	 */
	// 
	@Property(displayName = "Port", description = "Port of the" + " Reader", writable = true, type = PropertyType.PT_INTEGER, category = "conn"
			+ "ection", orderValue = 1, defaultValue = AlienReaderDefaultValues.PORT, minValue = "0", maxValue = "65535")
	public Integer getPort() {
		return port;
	}

	/**
	 * @param PORT
	 *            the PORT to set
	 */
	public void setPort(Integer port) {
		this.port = port;
	}

	/**
	 * @return the serverSocketPort
	 */
	@Property(displayName = "Notify Port", category = "connection", defaultValue = "54321", description = "The port configured in the Alien's Notify Address", type = PropertyType.PT_INTEGER, writable = true, minValue = "0", maxValue = "65535", orderValue = 1.5f)
	public Integer getNotifyPort() {
		return notifyPort;
	}

	/**
	 * @param ioStreamPort
	 *            the serverSocketPort to set
	 */
	public void setIOStreamPort(Integer ioStreamPort) {
		this.ioStreamPort = ioStreamPort;
	}

	/**
	 * @return the serverSocketPort
	 */
	@Property(displayName = "IO Stream Port", category = "connection", defaultValue = "54322", description = "The port configured in the Alien's IO Stream Address", type = PropertyType.PT_INTEGER, writable = true, minValue = "0", maxValue = "65535", orderValue = 1.75f)
	public Integer getIOStreamPort() {
		return ioStreamPort;
	}

	/**
	 * @param notifyPort
	 *            the serverSocketPort to set
	 */
	public void setNotifyPort(Integer notifyPort) {
		this.notifyPort = notifyPort;
	}

	/**
	 * @return the USERNAME
	 */
	@Property(displayName = "Username", description = "Username for logging "
			+ "into the Alien Reader", writable = true, category = "conn"
			+ "ection", defaultValue = AlienReaderDefaultValues.USERNAME, orderValue = 2)
	public String getUsername() {
		return username;
	}

	/**
	 * @param USERNAME
	 *            the USERNAME to set
	 */
	public void setUsername(String username) {
		this.username = username;
	}

	/**
	 * @return the PASSWORD
	 */
	@Property(displayName = "Password", description = "Password for logging"
			+ " into the Alien Reader", writable = true, category = "conn"
			+ "ection", defaultValue = AlienReaderDefaultValues.PASSWORD, orderValue = 3)
	public String getPassword() {
		return password;
	}

	/**
	 * @param PASSWORD
	 *            the PASSWORD to set
	 */
	public void setPassword(String password) {
		this.password = password;
	}

	/**
	 * @return the RECONNECTION_INTERVAL
	 */
	@Property(displayName = "Reconnection Interval", description = "Upon connection failure, the time to wait between two connection attempts (ms)", writable = true, type = PropertyType.PT_INTEGER, category = "conn"
			+ "ection", defaultValue = AlienReaderDefaultValues.RECONNECTION_INTERVAL, orderValue = 4, minValue = "0")
	public Integer getReconnectionInterval() {
		return reconnectionInterval;
	}

	/**
	 * @param RECONNECTION_INTERVAL
	 *            the RECONNECTION_INTERVAL to set
	 */
	public void setReconnectionInterval(Integer reconnectionInterval) {
		this.reconnectionInterval = reconnectionInterval;
	}

	/**
	 * @return the MAX_CONNECTION_ATTEMPTS
	 */
	@Property(displayName = "Maximum Connection Attempts", description = "Upon connection failure, the number of times to attempt to recconnect before giving up. If set to '-1', then try forever", writable = true, type = PropertyType.PT_INTEGER, category = "connection", defaultValue = AlienReaderDefaultValues.MAX_CONNECTION_ATTEMPTS, orderValue = 5, minValue = "-1")
	public Integer getMaxNumConnectionAttempts() {
		return maxNumConnectionAttempts;
	}

	/**
	 * @param MAX_CONNECTION_ATTEMPTS
	 *            the MAX_CONNECTION_ATTEMPTS to set
	 */
	public void setMaxNumConnectionAttempts(Integer maxNumConnectionAttempts) {
		this.maxNumConnectionAttempts = maxNumConnectionAttempts;
	}

	@Property(displayName = "GPO Output", description = "Set the GPO bitmap value", writable = true, type = PropertyType.PT_INTEGER, minValue = "0"
			+ "", maxValue = "255", category = "GPIO")
	public Integer getExternalOutput() {
		return Integer.parseInt(readerProperties.get(PROP_EXTERNAL_OUTPUT));
	}

	public void setExternalOutput(Integer externalOutput) {
		if (externalOutput >= 0 && externalOutput <= 255) {
			readerProperties.put(PROP_EXTERNAL_OUTPUT, Integer
					.toString(externalOutput));
			propCommandsToBeExecuted.add(new AlienCommandObjectWrapper(
					PROP_EXTERNAL_OUTPUT, new AlienSetCommandObject(
							Alien9800ReaderSession.COMMAND_EXTERNAL_OUTPUT,
							Integer.toString(externalOutput))));
			return;
		}
		logger.warn("ExternalOutput must be an"
				+ " integer between 0 and 255, but was " + externalOutput);
	}

	/**
	 * 
	 * @return
	 */
	@Property(displayName = "Persist Time", description = "Time in seconds the tags will persist "
			+ "in memory before they are removed (-1 is an infinite amount of time)"
			+ "", writable = true, type = PropertyType.PT_INTEGER, minValue = "-1"
			+ "", maxValue = "16535", category = "General", defaultValue = "-1")
	public Integer getPersistTime() {
		return Integer.parseInt(readerProperties.get(PROP_PERSIST_TIME));
	}

	/**
	 * 
	 * @param persistTime
	 */
	public void setPersistTime(Integer persistTime) {
		if (logger.isDebugEnabled()) {
			logger.debug("Attempting to set the persist time");
		}
		if (persistTime >= -1 && persistTime <= 16535) {
			readerProperties.put(PROP_PERSIST_TIME, Integer
					.toString(persistTime));
			propCommandsToBeExecuted.add(new AlienCommandObjectWrapper(
					PROP_PERSIST_TIME, new AlienSetCommandObject(
							Alien9800ReaderSession.COMMAND_PERSIST_TIME,
							Integer.toString(persistTime))));
			return;
		}
		logger.warn("Persist Time must be an"
				+ " integer between -1 and 16535, but was " + persistTime);
	}

	@Property(displayName = "Invert External Output", description = "Inverts the "
			+ "GPO", writable = true, type = PropertyType.PT_STRING, category = "GPIO", defaultValue = "OFF")
	public String getInvertExternalOutput() {
		return readerProperties.get(PROP_INVERT_EXTERNAL_OUTPUT);
	}

	public void setInvertExternalOutput(String invertExternalOutput) {
		if (invertExternalOutput.equalsIgnoreCase("OFF")
				|| invertExternalOutput.equalsIgnoreCase("ON")) {
			readerProperties.put(PROP_INVERT_EXTERNAL_OUTPUT,
					invertExternalOutput);
			propCommandsToBeExecuted
					.add(new AlienCommandObjectWrapper(
							PROP_INVERT_EXTERNAL_OUTPUT,
							new AlienSetCommandObject(
									Alien9800ReaderSession.COMMAND_INVERT_EXTERNAL_OUTPUT,
									invertExternalOutput)));
			return;
		}
		logger.warn("InvertExternalInput must be either"
				+ " 'ON' or 'OFF', but was " + invertExternalOutput);
	}

	@Property(displayName = "Invert External Input", description = "Inverts the "
			+ "GPI", writable = true, type = PropertyType.PT_STRING, category = "GP"
			+ "IO", defaultValue = "OFF")
	public String getInvertExternalInput() {
		return readerProperties.get(PROP_INVERT_EXTERNAL_OUTPUT);
	}

	public void setInvertExternalInput(String invertExternalInput) {
		if (invertExternalInput.equalsIgnoreCase("OFF")
				|| invertExternalInput.equalsIgnoreCase("ON")) {
			readerProperties.put(PROP_INVERT_EXTERNAL_INPUT,
					invertExternalInput);
			propCommandsToBeExecuted
					.add(new AlienCommandObjectWrapper(
							PROP_INVERT_EXTERNAL_INPUT,
							new AlienSetCommandObject(
									Alien9800ReaderSession.COMMAND_INVERT_EXTERNAL_INPUT,
									invertExternalInput)));
			return;
		}
		logger.warn("InvertExternalInput must be either"
				+ " 'ON' or 'OFF', but was " + invertExternalInput);
	}

	@Property(displayName = "RF Attenuation", description = "RF "
			+ "Attenuation", writable = true, type = PropertyType.PT_INTEGER)
	public Integer getRFAttenuation() {
		return Integer.parseInt(readerProperties.get(PROP_RF_ATTENUATION));
	}

	public void setRFAttenuation(Integer rfAttenuation) {
		if (rfAttenuation >= 0 && rfAttenuation <= 100) {
			readerProperties.put(PROP_RF_ATTENUATION, Integer
					.toString(rfAttenuation));
			propCommandsToBeExecuted.add(new AlienCommandObjectWrapper(
					PROP_RF_ATTENUATION, new AlienSetCommandObject(
							Alien9800ReaderSession.COMMAND_RF_ATTENUATION,
							Integer.toString(rfAttenuation))));
		} else {
			logger.warn("RFAttenuation bust be an integer "
					+ "between 0 and 100,  but was " + rfAttenuation);
		}
	}

	@Property(displayName = "Reader Number", description = "Reader Number", writable = true, type = PropertyType.PT_STRING, category = "General")
	public String getReaderNumber() {
		return readerProperties.get(PROP_READER_NUMBER);
	}

	public void setReaderNumber(String readerNumber) {
		readerProperties.put(PROP_READER_NUMBER, readerNumber);
		propCommandsToBeExecuted.add(new AlienCommandObjectWrapper(
				PROP_READER_NUMBER, new AlienSetCommandObject(
						Alien9800ReaderSession.COMMAND_RF_ATTENUATION,
						readerNumber)));
	}

	/**
	 * 
	 * 
	 * @return
	 */
	@Property(displayName = "Alien Reader Version", description = "Version Number of "
			+ "the Alien Reader", writable = false, category = "General")
	public String getReaderVersion() {
		return (String) readerProperties.get(PROP_READER_VERSION);
	}

	/**
	 * The type of reader that this is.
	 * 
	 * @return
	 */
	@Property(displayName = "Alien Reader Type", description = "Type of "
			+ " Alien Reader", writable = false)
	public String getReaderType() {
		return (String) readerProperties.get(PROP_READER_TYPE);
	}

	/**
	 * The Maximum number of antennas this reader can possess.
	 * 
	 * @return
	 */
	@Property(displayName = "Max Antennas", description = "Maximum number "
			+ "of antennas", writable = false, type = PropertyType.PT_INTEGER, category = "G"
			+ "eneral")
	public Integer getMaxAntennas() {
		return Integer.parseInt(readerProperties.get(PROP_MAX_ANTENNA));
	}

	/**
	 * Returns the MAC address for the reader.
	 * 
	 * @return
	 */
	@Property(displayName = "MAC Address", description = "MAC address of the reader", writable = false, category = "General")
	public String getMACAddress() {
		return (String) readerProperties.get(PROP_MAC_ADDRESS);
	}

	/**
	 * The input of the GPI for the reader.
	 * 
	 * @return
	 */
	@Property(displayName = "GPI Input", description = "Current GPI Bitmap Value", writable = false, type = PropertyType.PT_INTEGER, category = "GPIO")
	public Integer getExternalInput() {
		return Integer.parseInt(readerProperties.get(PROP_EXTERNAL_INPUT));
	}

	/**
	 * The uptime of the reader.
	 * 
	 * @return
	 */
	@Property(displayName = "Uptime", description = "Uptime of "
			+ "Alien Reader", writable = false, type = PropertyType.PT_INTEGER, category = "General")
	public Integer getUptime() {
		return Integer.parseInt(readerProperties.get(PROP_UPTIME));
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * org.rifidi.edge.sensors.base.AbstractSensor#applyPropertyChanges()
	 */
	@Override
	@Operation(description = "Apply all property changes to reader")
	public synchronized void applyPropertyChanges() {
		applyPropertyChanges(this.propCommandsToBeExecuted, false);
	}

	public synchronized boolean applyPropertyChanges(
			LinkedBlockingQueue<AlienCommandObjectWrapper> propCommandsToBeExecuted,
			boolean block) {
		// TODO: may need to synchnonize the hashmap before I clear it?
		Alien9800ReaderSession aliensession = session.get();
		if (aliensession != null) {
			ArrayList<AlienCommandObjectWrapper> commands = new ArrayList<AlienCommandObjectWrapper>();
			propCommandsToBeExecuted.drainTo(commands);
			AlienPropertyCommand command = new AlienPropertyCommand("",
					readerProperties, commands);
			if (block) {
				return aliensession.submitAndBlock(command, 10,
						TimeUnit.SECONDS);

			} else {
				aliensession.submit(command);
				return true;
			}
		}
		return false;
	}
}
Personal tools