|| {{attachment:oro-server/oro-server.png}}|| = 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 1. Add a new RPC method to ORO. We will call it `processNL`. Implement some processing by accessing the robot's cognitive model. 1. 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. * `oro-server` > 0.7.2 (you may have to grab it from the source). See [[oro-server#installation| this page]] for detailed installation instructions. I will assume `oro-server` is set up and can be run from `$PREFIX` by simply invoking `oro-server`. * a working Python installation for the plugin template generator === 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: * Four different constructors, * The `foo(Set)` method, * and the `getServiceProvider()` method that implement the same method in the `IServiceProvider` interface. We need here to introduce some background on the way plugins work. Plugins, in ORO, must implement the [[http://homepages.laas.fr/slemaign/doc/oro-server/laas/openrobots/ontology/modules/IModule.html|IModule]] interface and can access '''two different objects''', the current instance of the knowledge storage through the [[http://homepages.laas.fr/slemaign/doc/oro-server/laas/openrobots/ontology/backends/IOntologyBackend.html|IOntologyBackend]] interface and the configuration file through a Java [[http://java.sun.com/javase/6/docs/api/java/util/Properties.html|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 [[http://homepages.laas.fr/slemaign/doc/oro-server/laas/openrobots/ontology/service/IServiceProvider.html|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 [[http://homepages.laas.fr/slemaign/doc/oro-server/laas/openrobots/ontology/service/RPCMethod.html|@RCPMethod]] annotation. To summarize, to expose new services, your plugin must: 1. Have some methods annotated with `@RCPMethod`, 1. 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: {{{#!highlight java /** * NaturalLanguageModule plugin for ORO */ package laas.openrobots.ontology.modules.naturallanguage; import laas.openrobots.ontology.backends.IOntologyBackend; import laas.openrobots.ontology.modules.IModule; import laas.openrobots.ontology.service.IServiceProvider; import laas.openrobots.ontology.service.RPCMethod; public class NaturalLanguageModule implements IModule, IServiceProvider { IOntologyBackend oro; /** * Initializes the plugin with the server instance of the knowledge storage. */ public NaturalLanguageModule(IOntologyBackend oro) { super(); this.oro = oro; } /** * try to parse a natural language sentence * * If it succeed and is understood, it will try execute the command or answer * the question, an will return an natural answer. * * @param sentence the sentence to parse, in natural language * @return an answer in natural language if the sentence was understood, * nothing else. */ @RPCMethod ( category = "natural language", desc = "try to parse a natural language sentence, and, if it succeed" + ", execute and answer something meaningful." ) public String processNL(String sentence) { String res = "I don't understand what you mean."; return res; } /* (non-Javadoc) * @see laas.openrobots.ontology.modules.IModule#getServiceProvider() */ @Override public IServiceProvider getServiceProvider() { return this; } } }}} === 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: {{{#!highlight java /** * NaturalLanguageModule plugin for ORO */ package laas.openrobots.ontology.modules.naturallanguage; import java.util.regex.Matcher; import java.util.regex.Pattern; import laas.openrobots.ontology.backends.IOntologyBackend; import laas.openrobots.ontology.exceptions.IllegalStatementException; import laas.openrobots.ontology.helpers.Logger; import laas.openrobots.ontology.helpers.VerboseLevel; import laas.openrobots.ontology.modules.IModule; import laas.openrobots.ontology.modules.memory.MemoryProfile; import laas.openrobots.ontology.service.IServiceProvider; import laas.openrobots.ontology.service.RPCMethod; public class NaturalLanguageModule implements IModule, IServiceProvider { IOntologyBackend oro; Pattern addPattern; Matcher addMatcher; /** * Initializes the plugin with the server instance of the knowledge storage. */ public NaturalLanguageModule(IOntologyBackend oro) { super(); this.oro = oro; addPattern = Pattern.compile( "[\\w,\\' ]*?(?:remember|add|learn)[\\w,\\' ]*?(?:that|:) ?([\\w ]+)", Pattern.CASE_INSENSITIVE); addMatcher = addPattern.matcher(""); } /** * try to parse a natural language sentence * * If it succeed and is understood, it will try execute the command or answer * the question, an will return an natural answer. * * @param sentence the sentence to parse, in natural language * @return an answer in natural language if the sentence was understood, * nothing else. */ @RPCMethod ( category = "natural language", desc = "try to parse a natural language sentence, and, if it succeed" + ", execute and answer something meaningful." ) public String processNL(String sentence) { String res; addMatcher.reset(sentence); if (addMatcher.matches()) try { String stmt = addMatcher.group(1); Logger.log("Processing a \"add\" command with statement [" + stmt + "]\n", VerboseLevel.VERBOSE); oro.add(oro.createStatement(stmt), MemoryProfile.DEFAULT, false); res = "Ok! I now know something new!"; } catch (IllegalStatementException e) { res = "I couldn't add what you said: I only understand sentence" + " of type "; } else res = "I don't understand what you mean."; return res; } /* (non-Javadoc) * @see laas.openrobots.ontology.modules.IModule#getServiceProvider() */ @Override public IServiceProvider getServiceProvider() { return this; } } }}} We compile in the constructor a new regular expression pattern that matches any sentence of the form `?? [remember|learn|add] ?? [that|:] `. 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 [[http://homepages.laas.fr/slemaign/doc/oro-server/laas/openrobots/ontology/helpers/Logger.html|Logger]] class. Then we use two different methods of the [[http://homepages.laas.fr/slemaign/doc/oro-server/laas/openrobots/ontology/backends/IOntologyBackend.html|IOntologyBackend]] interface: [[http://homepages.laas.fr/slemaign/doc/oro-server/laas/openrobots/ontology/backends/IOntologyBackend.html#createStatement(java.lang.String)|createStatement(String)]] creates an ontology statement from a string with three tokens separated by a space, and [[http://homepages.laas.fr/slemaign/doc/oro-server/laas/openrobots/ontology/backends/IOntologyBackend.html#add(com.hp.hpl.jena.rdf.model.Statement, laas.openrobots.ontology.modules.memory.MemoryProfile, boolean)|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 [[http://homepages.laas.fr/slemaign/doc/oro-server/laas/openrobots/ontology/backends/IOntologyBackend.html|IOntologyBackend]] interface documentation in the other, you should be able to improve it easily!