Writing plugins for ORO

Adding new plugins to ORO is a piece of cake. Let's prove it.

In this tutorial, will go through all the steps to create a new plugin in Java that add some (very naive) natural language processing capabilities to ORO.

We must go through 3 steps;

  1. Generate a template for our new plugin
  2. Add a new RPC method to ORO. We will call it processNL. Implement some processing by accessing the robot's cognitive model.

  3. Create a new JAR package for our plugin, install it and restart the server.

Prerequisites

In all this documentation, $PREFIX will refer to the path where the oro-server source has been checked out. This directory should contain files like a Makefile, LICENSE, src/, etc.

Generation of a new plugin template

Most of the initial work to create a plugin is made automatically by the create_plugin_template.py Python script. You'll find it under $PREFIX/tools

Move to the directory where you want to generate your plugin sources, and invoke the template generator.

It should look like that (in bold what you enter):

> cd $PREFIX
> tools/create_plugin_template.py
Please enter the desired module name (should be a Java class name): NaturalLanguageModule
Please enter the desired package (for instance: org.laas.oro.test): oro.module.nl
I'm about to create a plugin template called oro.module.nl.NaturalLanguageModule
All the files will be put in ./src/oro/module/nl/
Is that alright (y/n)?y

The script will return. If you move to ./src/oro/module/nl/ you should find these files:

MANIFEST // the plugin manifest
NaturalLanguageModule.jardesc //Eclipse JAR description, useful to generate a JAR from Eclipse
NaturalLanguageModule.java //Template Java class
package.html //Template for the plugin documentation

To complete this first step, let open the MANIFEST in your favorite editor. It's almost complete (well, you can change the name here, if you want, for something more human-friendly). You just need to edit the two lines Bundle-Description and Bundle-Vendor.

For now, we don't need to modify the NaturalLanguageModule.jardesc but if you add dependencies, don't forget to add them there as well.

In future versions of the template generator, automatic generation of an Ant build file is likely to be added.

Editing of the main plugin Java class

Our module could actually already be compiled and used: it is already a valid plugin. But let's edit it a bit first.

Open NaturalLanguageModule.java in your favorite editor (I'm using here Eclipse).

The class is already filled with several methods. Precisely:

We need here to introduce some background on the way plugins work.

Plugins, in ORO, must implement the IModule interface and can access two different objects, the current instance of the knowledge storage through the IOntologyBackend interface and the configuration file through a Java Properties object.

In return they may (or not) expose new RPC methods (called services) to the outside world. If they do, they must provide an object that implements the IServiceProvider interface (or implement this interface themselves). This object is returned by the aforementioned IModule::getServiceProvider() method.

How to tell which are the methods (ie, the services) we want to expose? The foo method in your plugin template is an example of such a service: we rely on Java annotations to mark RPC methods. A method you want to expose must have a @RCPMethod annotation.

To summarize, to expose new services, your plugin must:

  1. Have some methods annotated with @RCPMethod,

  2. Return to the server runtime the object that offer these methods in the getServiceProvider()

So now, why four constructors?

You actually only need to keep one of them, depending on what your plugin need. Most probably, you'll need to access to the server global instance of an IOntologyBackend. You may need as well to access the configuration file. Keep the constructor that is the most convenient to you.

Let's edit a bit our template:

   1 /**
   2  * NaturalLanguageModule plugin for ORO
   3  */
   4 package laas.openrobots.ontology.modules.naturallanguage;
   5 
   6 import laas.openrobots.ontology.backends.IOntologyBackend;
   7 import laas.openrobots.ontology.modules.IModule;
   8 import laas.openrobots.ontology.service.IServiceProvider;
   9 import laas.openrobots.ontology.service.RPCMethod;
  10 
  11 public class NaturalLanguageModule implements IModule, IServiceProvider {
  12 
  13         IOntologyBackend oro;
  14 
  15         /**
  16          * Initializes the plugin with the server instance of the knowledge storage.
  17          */
  18         public NaturalLanguageModule(IOntologyBackend oro) {
  19                 super();
  20                 this.oro = oro;
  21         }
  22 
  23         /**
  24          * try to parse a natural language sentence
  25          *
  26          * If it succeed and is understood, it will try execute the command or answer
  27          * the question, an will return an natural answer.
  28          *
  29          * @param sentence the sentence to parse, in natural language
  30          * @return an answer in natural language if the sentence was understood,
  31          * nothing else.
  32          */
  33         @RPCMethod (
  34                         category = "natural language",
  35                         desc = "try to parse a natural language sentence, and, if it succeed" +
  36                                         ", execute and answer something meaningful."
  37                         )
  38         public String processNL(String sentence) {
  39                 String res = "I don't understand what you mean.";
  40                 return res;
  41         }
  42 
  43         /* (non-Javadoc)
  44          * @see laas.openrobots.ontology.modules.IModule#getServiceProvider()
  45          */
  46         @Override
  47         public IServiceProvider getServiceProvider() {
  48                 return this;
  49         }
  50 }

Installing and testing the plugin

Our module doesn't do anything useful, but we can already check it works.

Let's compile and build a JAR package for our module. From Eclipse Package Explorer, simply right click on the NaturalLanguageModule.jardesc file and select Create JAR.

If the compilation is successful, Eclipse create a NaturalLanguageModule.jar in $PREFIX/plugins. Here we are. By default, oro-server is configured to check for plugins in this folder (you can change it in the oro-server configuration you will find in $PREFIX/etc/oro-server/oro.conf).

Start oro-server from the command line. You should see a new entry in the list of methods:

        [NATURAL LANGUAGE]
        - processNL(String): try to parse a natural language sentence, and, if it succeed, execute and answer something meaningful.

To check it actually works, we can use telnet (in bold what you enter):

> telnet localhost 6969
Trying ::1...
Connected to localhost.
Escape character is '^]'.
processNL
Hello
#end#
ok
I don't understand what you mean.
#end#

Interacting with the server

It works. Now we can edit our processNL method to do something a bit more useful: we can for instance parse the user input to find a command to would instruct the server to learn a new statement:

   1 /**
   2  * NaturalLanguageModule plugin for ORO
   3  */
   4 package laas.openrobots.ontology.modules.naturallanguage;
   5 
   6 import java.util.regex.Matcher;
   7 import java.util.regex.Pattern;
   8 
   9 import laas.openrobots.ontology.backends.IOntologyBackend;
  10 import laas.openrobots.ontology.exceptions.IllegalStatementException;
  11 import laas.openrobots.ontology.helpers.Logger;
  12 import laas.openrobots.ontology.helpers.VerboseLevel;
  13 import laas.openrobots.ontology.modules.IModule;
  14 import laas.openrobots.ontology.modules.memory.MemoryProfile;
  15 import laas.openrobots.ontology.service.IServiceProvider;
  16 import laas.openrobots.ontology.service.RPCMethod;
  17 
  18 public class NaturalLanguageModule implements IModule, IServiceProvider {
  19 
  20         IOntologyBackend oro;
  21 
  22         Pattern addPattern;
  23         Matcher addMatcher;
  24 
  25         /**
  26          * Initializes the plugin with the server instance of the knowledge storage.
  27          */
  28         public NaturalLanguageModule(IOntologyBackend oro) {
  29                 super();
  30                 this.oro = oro;
  31 
  32                 addPattern = Pattern.compile(
  33                                 "[\\w,\\' ]*?(?:remember|add|learn)[\\w,\\' ]*?(?:that|:) ?([\\w ]+)",
  34                                 Pattern.CASE_INSENSITIVE);
  35 
  36                 addMatcher = addPattern.matcher("");
  37         }
  38 
  39         /**
  40          * try to parse a natural language sentence
  41          *
  42          * If it succeed and is understood, it will try execute the command or answer
  43          * the question, an will return an natural answer.
  44          *
  45          * @param sentence the sentence to parse, in natural language
  46          * @return an answer in natural language if the sentence was understood,
  47          * nothing else.
  48          */
  49         @RPCMethod (
  50                         category = "natural language",
  51                         desc = "try to parse a natural language sentence, and, if it succeed" +
  52                                         ", execute and answer something meaningful."
  53                         )
  54         public String processNL(String sentence) {
  55 
  56                 String res;
  57 
  58                 addMatcher.reset(sentence);
  59 
  60                 if (addMatcher.matches())
  61                         try {
  62                                 String stmt = addMatcher.group(1);
  63                                 Logger.log("Processing a \"add\" command with statement [" +
  64                                                 stmt + "]\n", VerboseLevel.VERBOSE);
  65 
  66                                 oro.add(oro.createStatement(stmt),
  67                                                 MemoryProfile.DEFAULT,
  68                                                 false);
  69 
  70                                 res = "Ok! I now know something new!";
  71                         } catch (IllegalStatementException e) {
  72                                 res = "I couldn't add what you said: I only understand sentence" +
  73                                         " of type <subject> <predicate> <object>";
  74                         }
  75                 else
  76                         res = "I don't understand what you mean.";
  77 
  78                 return res;
  79         }
  80 
  81         /* (non-Javadoc)
  82          * @see laas.openrobots.ontology.modules.IModule#getServiceProvider()
  83          */
  84         @Override
  85         public IServiceProvider getServiceProvider() {
  86                 return this;
  87         }
  88 }

We compile in the constructor a new regular expression pattern that matches any sentence of the form ?? [remember|learn|add] ?? [that|:] <subject> <object> <predicate>.

In the processNL method, we apply this regex to the user input and capture the statement at the end.

We first report this statement in the server log thanks the Logger class.

Then we use two different methods of the IOntologyBackend interface: createStatement(String) creates an ontology statement from a string with three tokens separated by a space, and add(Statement, MemoryProfile, boolean) adds the statement to the knowledge base. Easy.

We eventually return a nice string to the user, telling if the operation was successful or not.

We are now done with this tutorial. Of course, our Natural Language Processing module is (very) far from complete, but with this example in one hand and the IOntologyBackend interface documentation in the other, you should be able to improve it easily!