Developer How-To: Add Your Own Protocol to OpenRemote 2.0 Controller
This how-to explains the steps necessary to add your own protocol implementation into OpenRemote 2.0 Controller codebase.
1. Protocol Implementation
In OpenRemote 2.0, protocol implementations must be written in Java. Create your Java classes in org.openremote.controller.protocol.* package. For this, you will need the following:
CommandBuilder Interface
The CommandBuilder interface is responsible for taking the XML definition from the controller.xml file and turning it into Java object instances (which must in turn implement the Command interface).
The generic structure of all command XML definitions is following:
<command protocol = "protocol-id" >
<property name = "argname1" value = "..."/>
<property name = "argname2" value = "..."/>
<property name = "argnameX" value = "..."/>
</command>
Listing 1: Generic XML structure of a protocol command in OpenRemote 2.0 Controller
Where each command is represented by its own element with an arbitrary number of property elements as its children. Properties are defined as name, value pairs.
The Java interface to implement is simply defined as:
public interface CommandBuilder
{
Command build(Element element);
}
Listing 2: CommandBuilder interface
As a response from the CommandBuilder build(Element element) method, you should return a concrete, protocol specific command implementation. The command instance should implement network or other media based communication for reading or writing to a device. See more details below in the "Command Interface" section.
XML Parsing
The command definition is stored in Controller's controller.xml configuration file (normally generated by the OpenRemote 2.0 Designer tool). This XML definition must be turned into a concrete Java implementation in the CommandBuilder implementation.
Each command is stored as a list of name and property values (see Listing 1 above). These properties can be used to instruct the CommandBuilder implementation on how to construct and initialize its concrete Command instances.
The XML API library used is JDOM. A simple parsing of command properties from the JDOM Element instance passed as an argument to CommandBuilder's build(Element element) method can be achieved as follows:
public class MyCommandBuilder implements CommandBuilder
{
...
public Command build(Element element)
{
List<Element> propertyElements =
element.getChildren(CommandBuilder.XML_ELEMENT_PROPERTY, element.getNamespace());
for (Element el : propertyElements)
{
String propertyName = el.getAttributeValue(CommandBuilder.XML_ATTRIBUTENAME_NAME);
String propertyValue = el.getAttributeValue(CommandBuilder.XML_ATTRIBUTENAME_VALUE);
...
}
return new MyCommand(myProperties);
}
}
Listing 3: Example of XML parsing Command's properties
Lifecycle
Command builders are instantiated by the Spring service container which is configured with the controller's applicationContext.xml file (see later). Under normal circumstances command builders should be considered as singletons and instantiated only once during the controller's lifecycle. This is the default bean lifecycle unless specified otherwise.
Should you choose a non-singleton command builder model, it is advisable to consider the cost of initializing/constructing the command builder instance, especially if it is directly tied to incoming request frequency.
Command Interface
The Command interface is a tagging interface with no methods to implement. However, it's sub-interfaces are ExecutableCommand and StatusCommand. Your concrete protocol should implement either one of these (or both).
The ExecutableCommand interface is intended for "write" commands toward the device (setting values, invoking commands). It defines a simple send() method.
public interface ExecutableCommand extends Command
{
public void send();
}
Listing 4: ExecutableCommand Interface
The StatusCommand interface is intended for "read" commands. It is used to read device state, configuration or property values.
public interface StatusCommand extends Command
{
public String read(EnumSensorType sensorType, Map<String, String> stateMap);
}
Listing 5: StatusCommand Interface
Read commands are used by sensors to poll data from devices on a given interval. Both read and write commands can be invoked through the controller's HTTP REST interface.
The enum sensorType gives you an indication what type of return value the sensor expects from the read command. The return values are encoded as String values. Fixed sensor types are:
- Switch - strings "on" or "off" are valid return values
- Level - integer value in the range [0-100] as a string is a valid return value
- Range - integer as a string is a valid return value
Lifecycle
Command instances are usually created per request. The constructors should be relatively light-weight to avoid performance impact. It is also a good idea to attempt to design your Command implementations as immutable instances where possible, to prevent possible race conditions.
For sophisticated use-cases, it would be possible to implement a Command Builder that re-uses command instances, especially if they are implemented as immutable classes. The design considerations of this would have to be considered carefully, though.
Examples
You can study existing protocol implementations for more detailed samples of CommandBuilder, ExecutableCommand and StatusCommand interfaces:
2. Controller Configuration
Once your protocol implementation is complete, you need to configure to add it to the controller's startup script. This is the applicationContext.xml file. It can be found under the 'config' directory of the Subversion checkout.
The relevant section is the "commandFactory" bean in the file, shown below
...
<bean id = "commandFactory" class = "org.openremote.controller.command.CommandFactory">
<property name = "commandBuilders">
<props>
<prop key = "ir">irCommandBuilder</prop>
<prop key = "knx">knxCommandBuilder</prop>
<prop key = "x10">x10CommandBuilder</prop>
<prop key = "tcpSocket">tcpSocketCommandBuilder</prop>
<prop key = "telnet">telnetCommandBuilder</prop>
<prop key = "httpGet">httpGetCommandBuilder</prop>
<prop key = "upnp">upnpCommandBuilder</prop>
<prop key = "mine">myCommandBuilder</prop>
</props>
</property>
</bean>
...
Listing 6: Command Factory builders in Controller's applicationContext.xml file
Give your protocol a unique ID (the "mine" key attribute in the above XML) and a bean name that you will use to instantiate your protocol implementation CommandBuilder ("myCommandBuilder" in the above listing).
Next, locate the relevant section in the applicationContext.xml to add the command builder instantiation configuration (constructor parameters) as shown below:
...
<!-- COMMAND BUILDERS ========================================================
|
| Implementation specific builders for the Event Factory bean. In short, each
| distinct command type, as they appear in controller.xml, will need a
| corresponding builder implementation.
|
| See the org.openremote.controller.command.CommandBuilder interface for
| details if you seek to extend the implementation with additional command
| types.
+-->
<bean id = "irCommandBuilder"
class = "org.openremote.controller.protocol.infrared.IRCommandBuilder"/>
...
<bean id = "myCommandBuilder"
class = "org.openremote.cotroller.protocol.mine.MyCommandBuilder"/>
...
Listing 7: CommandBuilder JavaBeans in Controller's applicationContext.xml
With the above configuration, the expected XML snippet that your command builder implementation needs to parse – which is passed to your implementation as the element argument in CommandBuilder.build(Element element) API – would be:
<command protocol = "mine" >
<property name = "argname1" value = "..."/>
<property name = "argname2" value = "..."/>
<property name = "argnameX" value = "..."/>
</command>
Listing 8: Example protocol command XML snippet in Controller's controller.xml file
With that, your protocol is now hooked into the OpenRemote 2.0 Controller implementation. Continue to add your protocol support into the OpenRemote 2.0 Designer by following the XML configuration steps in Developer How To - Adding Your Own Protocol to OpenRemote Boss 2.0 Designer.
See Also