iCasa platform provides a command architecture allowing extension by third party developers. This architecture is based on the OSGi whiteboard pattern. In this tutorial we will show how to add new commands to the iCasa platform.
iCasa Commands could be used two different ways:
This benefits the platform in two main ways:
In order to add a new command to the iCasa platform, an OSGi service must be provided by the developer. This service has to implement the fr.liglab.adele.icasaICasaCommand interface, it is show below
/**
* An ICommandService is a service that can be executed with a set of arguments. It can execute a block of code and then
* returns the result object of executing this code.
*/
public interface ICasaCommand {
/**
* Execute a block of code and then returns the result from execution. This method should use {@code in} and
* {@code out} stream to print result instead of the direct use of {@code System.in} and {@code System.out}
*
* @param in : the input stream.
* @param out : the output stream.
* @param param a json object of parameters
*
* @return the result from the execution
* @throws Exception if anything goes wrong
*/
Object execute(InputStream in, PrintStream out, JSONObject param) throws Exception;
/**
* To validate the given parameters.
*
* @param param The parameters in JSON format
* @return true if parameters are valid, false if not.
* @throws Exception
*/
boolean validate(JSONObject param, Signature signature) throws Exception;
/**
* Get the command description.
*
* @return The description of the Script and command gogo.
*/
String getDescription();
/**
* Get the name of the Script and command gogo.
*
* @return The command name.
*/
String getName();
/**
* Get signature by passing the number of arguments..
*
* @return
*/
Signature getSignature(int size);
}
A command signature defines number and order of its parameters. When a command is used in the shell arguments are anonymous, iCasa platform determines the parameter correspondence only based in order. On the other hand, if the command is used in a script file, parameters are determined based on their names.
Commands could have more than one signature, however signatures cannot contain the same number of parameters. The fr.liglab.adele.icasa.Signature class is presented in the next code snipped, to create a Signature only is necessary to provide an array of string corresponding to the name of parameters in the expected order (see constructor).
public class Signature {
/**
* The list of parameters for a given iCasaCommand signature.
*/
private final String[] parameters;
public Signature(String[] params) {
this.parameters = params;
}
/**
* Get the list of parameters of this signature.
*
* @return the list of parameters.
*/
public String[] getParameters() {
return parameters;
}
...
}
In order to facilitate the creation of iCasa Commands, the abstract class fr.liglab.adele.icasa.commands.impl.AbstractCommand is provided by the iCasa platform. This class implements the ICasaCommand interface, it implements the validate and getSignature methods.
A command implementation should extend the AbstractCommand and implements the getName, getDescription and execute methods. In addition, in the constructor of the implementation class must be defined the signatures of the command using the method addSignature. Finally, the implementation class must be annotated with the iPOJO annotations to expose itself as OSGi a service, this service will implement the right interface ICasaCommand.
In order to illustrate how to create iCasa commands will show to different examples, the first one using no parameters, and other using parameters.
The following command displays the list of current devices in the iCasa platform. It does not take any parameter.
@Component(name = "ShowDevicesCommand")
@Provides
@Instantiate(name="show-devices-command")
public class ShowDevicesCommand extends AbstractCommand {
@Requires
private ContextManager manager;
/**
* Get the name of the Script and command gogo.
*
* @return The command name.
*/
@Override
public String getName() {
return "show-devices";
}
public ShowDevicesCommand(){
addSignature(new Signature(new String[0])); // Adding an empty signature, without parameters
}
@Override
public Object execute(InputStream in, PrintStream out, JSONObject param, Signature signature) throws Exception {
out.println("Devices: ");
List<LocatedDevice> devices = manager.getDevices();
for (LocatedDevice locatedDevice : devices) {
out.println("Device " + locatedDevice);
}
return null;
}
@Override
public String getDescription(){
return "Shows the list of devices.\n\t" + super.getDescription();
}
}
The following command displays the details of a particular device.
@Component(name = "ShowDeviceCommand")
@Provides
@Instantiate(name = "show-device-command")
public class ShowDeviceInfoCommand extends AbstractCommand {
@Requires
private ContextManager simulationManager;
public ShowDeviceInfoCommand(){
addSignature(new Signature(new String[]{"deviceId"})); // Adding a signatue with one parameter named deviceId
}
/**
* Get the name of the Script and command gogo.
*
* @return The command name.
*/
@Override
public String getName() {
return "show-device";
}
@Override
public Object execute(InputStream in, PrintStream out, JSONObject param, Signature signature) throws Exception {
String[] params = signature.getParameters();
String deviceId = param.getString(params[0]);
out.println("Properties: ");
LocatedDevice device = simulationManager.getDevice(deviceId);
if (device==null) {
throw new IllegalArgumentException("Device ("+ deviceId +") does not exist");
}
Set<String> properties = device.getProperties();
for (String property : properties) {
out.println("Property: " + property + " - Value: " +device.getPropertyValue(property));
}
return null;
}
@Override
public String getDescription(){
return "Shows the information of a device.\n\t" + super.getDescription();
}
}
As said before, iCasa Commands has two usages, the first one to be called from the shell, and the other one to be used from an iCasa Script.
To see the parameter order in the shell, we can call the icasa:help command, which will show the commands description, as well as the parameters list.
g!icasa:help
icasa:show-zones
Shows the list of zones.
Parameters:
()
icasa:move-person
Move a person to a new X,Y position.
Parameters:
( personId newX newY )
( personId newX newY newZ )
g!
So, for example, to move a person, we have two signatures with three and four parameters respectively:
g! move-person Jean 40 40
and
g! move-person Jean 50 50 50
iCasa provides a mechanism to execute scripts. This is presented in the script section. But also, the script language can be extended by using commands. So, new commands could be executed in a given script. The command name (the string returned in the getName() method) must be represented by an XML tag. And the parameters are represented by the attributes contained in those tags.
For example, it is possible to move a person by putting in the script the following:
<move-person personId="Jean" newX="40" newY="40"/>
or
<move-person personId="Jean" newX="50" newY="50" newZ="50"/>
You can use maven tool to build a command project. Two iCasa maven artifacts are necessary to build your project, the first one context.api defines the interfaces used in the Command model, the context.impl provides the AbstractCommand class.
Artifacs :
Context API - iCasa Command interfaces
<groupId>fr.liglab.adele.icasa</groupId>
<artifactId>context.api</artifactId>
<version>1.1.2-SNAPSHOT</version>
Context Impl - iCasa Command abstract class
<groupId>fr.liglab.adele.icasa</groupId>
<artifactId>context.impl</artifactId>
<version>1.1.2-SNAPSHOT</version>
Repositories :
<repositories>
<repository>
<snapshots>
<enabled>false</enabled>
</snapshots>
<id>adele-central-release</id>
<name>adele-repos</name>
<url>http://repository-icasa.forge.cloudbees.com/release</url>
</repository>
<repository>
<snapshots />
<id>snapshots</id>
<name>adele-central-snapshot</name>
<url>http://repository-icasa.forge.cloudbees.com/snapshot</url>
</repository>
</repositories>
This is an extract of a maven project using the needed dependencies to build iCasa Commands. Also it shows dependencies for OSGi and iPOJO bundles, as well as their plugins configuration.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<!-- Project coordinates -->
<artifactId>myCommand.project</artifactId>
<packaging>bundle</packaging>
<version>1.0.0-SNAPSHOT</version>
<!-- Project repositories -->
<repositories>
<repository>
<snapshots>
<enabled>false</enabled>
</snapshots>
<id>adele-central-release</id>
<name>adele-repos</name>
<url>http://repository-icasa.forge.cloudbees.com/release</url>
</repository>
<repository>
<snapshots />
<id>snapshots</id>
<name>adele-central-snapshot</name>
<url>http://repository-icasa.forge.cloudbees.com/snapshot</url>
</repository>
</repositories>
<!-- Project dependencies -->
<dependencies>
<dependency>
<groupId>org.osgi</groupId>
<artifactId>org.osgi.core</artifactId>
<version>4.2.0</version>
</dependency>
<dependency>
<groupId>org.apache.felix</groupId>
<artifactId>org.apache.felix.ipojo</artifactId>
<version>1.10.1</version>
</dependency>
<dependency>
<groupId>org.apache.felix</groupId>
<artifactId>org.apache.felix.ipojo.annotations</artifactId>
<version>1.10.1</version>
</dependency>
<dependency>
<groupId>fr.liglab.adele.icasa</groupId>
<artifactId>context.api</artifactId>
<version>1.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>fr.liglab.adele.icasa</groupId>
<artifactId>context.impl</artifactId>
<version>1.0.1-SNAPSHOT</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<version>2.3.7</version>
<configuration>
<instructions>
<Private-Package>fr.liglab.adele.icasa.zones.command.impl</Private-Package>
</instructions>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-ipojo-plugin</artifactId>
<version>1.8.6</version>
</plugin>
</plugins>
</build>