|
Interfacing with ORO
ORO provides an extended set of interfaces to ease its integration with other components.
The main communication channel with oro-server is a simple socket interface. The protocole is very simple, and detailled below.
However several bridges and wrapper have been developped for convenience. Two categories exist: bindings for specific languages and modules for some popular robotics middleware or integration framework.
If needed, new ones could be easily added (you just need to be able to access sockets, and basically every language can do that).
Summary
Contents
Raw sockets, debugging with Telnet
The underlying socket protocol is ASCII-based (ie, you can connect to the server with telnet to test everything). The examples, below, reproduce inputs on a Telnet session.
Requests
The general structure of a request to the server is:
method_name ↵ [parameter1] ↵ [parameter2] ↵ [...] ↵ #end# ↵
parameters can be either:
- strings (quotes are not necessary, and are removed if present),
- integers (strings made only of numbers),
- floats (strings made of numbers with a dot somewhere),
booleans (strings equal to true or false, case insensitive),
(ordered) lists or (unordered) sets of strings, with this structure: [val1, val2, ..., valn]. If strings contain commas, they must be (single or doubled) quoted.
map of (key, value) pairs of strings, with this structure: {key1:val1, key2:val2, ...}. If strings (be keys or values) contain commas, they must be (single or doubled) quoted.
Please note that collections of collections are not supported.
Responses
The server response has this structure if the request succeeded:
ok [return_value] #end#
And this structure in case of failure:
error [name of the exception, if available] [human-readable error message - that *may* spend over several lines] #end#
Some examples
You can test this example by directly connecting to the server with a tool like telnet.
Retrieving a human-friendly list of available methods on the server
> help
Retrieving a machine-friendly list of available methods on the server
> listMethods > #end#
Adding facts to the knowledge base
> add > [human rdf:type Human, human rdfs:label "Ramses", myself sees Ramses] > #end#
Retrieving facts
This command should return the list of humans the robot currently sees.
> find > [?humans rdf:type Human, myself sees ?humans] > #end#
More complete example
This Telnet recorded session demonstrates a more complete interaction with the server. It exhibits as well some of the inference abilities (it assumes the server has been started with the common_sense.oro.owl ontology).
add [kitchen_table rdf:type Table, fork1 rdf:type Fork, glass1 rdf:type Glass] //we add some objects #end# ok #end# add [fork1 isOn kitchen_table] //we assert that the object "fork1" is on the table #end# ok #end# find o [?o isAt kitchen_table] //we try to retrieve the object co-located with the table #end# ok ["fork1","kitchen_table"] //the server infers that "isOn" implies "isAt" and returns, as expected, the fork #end# registerEvent NEW_INSTANCE ON_TRUE o [?o isAt kitchen_table, ?o rdf:type Artifact] //we register an event that should be triggered when some artifact "isAt" the table #end# ok 78445b39-5cac-49a2-a970-623bbcb09c27 //the server register the event and returns and unique identifier for it #end# //note that no event has been yet triggered. That's because the server doesn't know that a Fork is an Artifact add [Fork rdfs:subClassOf Tableware,Glass rdfs:subClassOf Tableware] //we now add that forks and glasses are tableware (and the server already knows from the common sens ontology that tableware are kind of artifacts) #end# ok #end# event 78445b39-5cac-49a2-a970-623bbcb09c27 //...so the event is now triggered ["fork1"] #end# add [glass1 isOn kitchen_table] #end# ok #end# event 78445b39-5cac-49a2-a970-623bbcb09c27 //and if you add as well a glass on the table, a new event will also be triggered ["glass1"] #end#
C++ bindings
C++ bindings for ORO can be found in the liboro library.
Installation
Via robotpkg
The supported way to install liboro is through robotpkg:
Robotpkg is a package management system for robotics module we use at the LAAS. It handles dependencies and compilation automatically. If you don't know it, have a look here.
Once installed, you can add the liboro package very easily:
> cd $ROBOTPKG_BASE/knowledge/liboro > make update
- You're done!
From the sources
You can grab a snapshot of the sources on the public FTP: ftp://softs.laas.fr/pub/openrobots/liboro/
Or, to get the latest version of liboro, you can check-out the sources with GIT:
> git clone git://trac.laas.fr/robots/liboro
liboro uses CMake (>= 2.6) to compile, and depends only on boost (>= 1.34).
You can compile it with:
> cd liboro > cmake . > make > make install
API coverage
As of version 0.7.5, the liboro C++ bindings cover the following part of the oro-server API:
all basic functionalities (add, remove, check, checkConsistency...)
queries (find, query,...)
basic functionalities for multiple agents modeling (addForAgent, findForAgent...)
- events registration, events callback
Usage & library reference
The liboro reference is available as Doxygen comments in the source code. It is available on-line as well.
Some of methods are available as a high-level object-oriented abstraction. For instance:
Complete example
This example can be found in the source code: $PREFIX/oro-apps-src/oro_test.cpp.
1 #include <iostream>
2 #include <iterator>
3 #include <set>
4
5 #include "liboro/oro.h"
6 #include "liboro/oro_library.h"
7 #include "liboro/socket_connector.h"
8
9 using namespace std;
10 using namespace oro;
11
12 class EventCallback : public OroEventObserver {
13 void operator()(const OroEvent& evt) {
14 cout << "Event triggered!" << endl << "Event content: " << endl;
15 set<Concept> evt_content = boost::get<set<Concept> >(evt.content);
16 copy(evt_content.begin(), evt_content.end(), ostream_iterator<Concept>(cout, "\n"));
17 }
18 };
19
20 int main(void) {
21 set<Concept> result;
22 set<string> partial_stmts;
23
24 //liboro currently relies on sockets for the RPCs with the server.
25 //we imagine here that the server runs on the same machine, on port 6969.
26 SocketConnector connector("localhost", "6969");
27
28 //The "oro" object is here built as a singleton.
29 //This actually connects the application to the ontology server.
30 Ontology *oro = Ontology::createWithConnector(connector);
31
32 //First, create some instances (ie, objects).
33
34 //a new instance of Agent has been created. It is named "Nice Robot" and its
35 // type (or "class") is set to be a Robot (which is a subconcept of Agent).
36 Agent robot1 = Agent::create("Nice Robot", Classes::Robot);
37 //another agent...
38
39 Agent human = Agent::create("Young PhD", Classes::Human);
40
41 //Let's try a first, simple query
42
43 partial_stmts.insert("?mysterious rdf:type Agent");
44 oro->find("mysterious", partial_stmts, result);
45
46 //display the results on std_out. It should display two ID (that are the
47
48 //internal unique identifiers for "Nice Robot" and "Young PhD") + "myself"
49 //which is always defined and refers to the robot itself.
50 copy(result.begin(), result.end(), ostream_iterator<Concept>(cout, "\n"));
51
52 partial_stmts.clear();
53 result.clear();
54
55 //here, an object is created. No name (or "label") has been set up, but the
56 //class is refined: it's not only an object, but more precisely a table.
57
58 Object table = Object::create(Classes::Table);
59 //here, an unknown object has been identified, without any more infos.
60 Object unknown_object = Object::create();
61
62 //if no setter is available for a given property, then direct assertion can
63 //be made. The list of existing properties and classes come from the oro
64 //ontology itself (from which oro_library.h/cpp is automatically generated)
65 unknown_object.assertThat(Properties::isOnTopOf, table);
66
67 //We can as well access the ontology at a lower level
68 oro->add(Statement("oro:isOnTopOf rdfs:subClassOf oro:isAt"));
69
70 //"myself" is a special, unique instance representing the robot itself.
71
72 //This instance is built from the already existing identifier "myself".
73 //Hence the constructor of the Agent class can be directly called.
74 Agent myself("myself");
75
76 myself.sees(unknown_object);
77 myself.sees(human);
78
79 //Then, try to find back the unknown object...
80 partial_stmts.insert("?mysterious oro:isAt ?table");
81 partial_stmts.insert("?table rdf:type oro:Table");
82 partial_stmts.insert("oro:myself oro:sees ?mysterious");
83
84 oro->find("mysterious", partial_stmts, result);
85
86 copy(result.begin(), result.end(), ostream_iterator<Concept>(cout, "\n"));
87
88 /** EVENTS **/
89 EventCallback ec;
90 Classes::Human.onNewInstance(ec);
91 Agent superman = Agent::create("Superman", Classes::Human);
92
93 cout << "Sleeping for 1 sec..." << endl;
94 sleep(1);
95
96 set<string> event_pattern;
97 Property flyingProp = Property("isFlying");
98
99 event_pattern.insert( superman.id() + " isFlying true");
100 oro->registerEvent(ec, FACT_CHECKING, ON_TRUE_ONE_SHOT, event_pattern, "");
101
102 superman.assertThat(flyingProp, "true");
103
104 cout << "Sleeping for 1 sec..." << endl;
105 sleep(1);
106
107 return 0;
108 }
Python bindings
Python bindings for ORO can be found in the pyoro library.
Installation
Via robotpkg
The supported way to install pyoyo is through robotpkg:
Robotpkg is a package management system for robotics module we use at the LAAS. It handles dependencies and compilation automatically. If you don't know it, have a look here.
Once installed, you can add the pyoro package very easily:
> cd $ROBOTPKG_BASE/knowledge/py-oro > make update
- You're done!
From the sources
To get the latest version of pyoro, you can check-out the sources with GIT:
> git clone git://trac.laas.fr/robots/pyoro
To install pyoro on your system, simply run with root privileges
> python setup.py install
Usage & library reference
The connection to an oro-server instance is initialized this way:
The pyoro binding dynamically creates at startup Python method for every available RPC method (as listed at the bottom of this page). They can be directly called through the Oro object:
1 oro.<method name>(<param1>, <param2>, <...>))
- Besides the raw API, several more "Python friendly" shortcuts are available:
dict-like accessor
For instance, the following code snippet would iterate over all concepts asserted of inferred to be humans:
- TO BE CONTINUED!
API coverage
As of version 0.8.0, the pyoro Python bindings cover all the oro-server API, including events.
Simple example
This example is very incomplete. Please refer to the list of RPC methods below for the list of all possible RPC calls.
1 import time
2 from pyoro import Oro, OroServerError
3
4 def onevent(evt):
5 print("God save the queen! " + evt + " killed Bond!")
6
7 try:
8 oro = Oro()
9
10 oro += ["Spy rdfs:subclassOf Human",·
11 "bond rdf:type Spy",·
12 "bond rdfs:label \"Bond, James Bond\""]
13
14 if "bond rdf:type Human" in oro:
15 print("Alright, Bond is a human")
16
17 oro += "hrp2 rdf:type Robot"
18
19 for ag in oro["* rdf:type Agent"]:
20 print("Agent " + ag + " is here.")
21
22 oro.subscribe(["?a kills bond"], onevent)
23 oro += "hrp2 kills bond"
24
25 time.sleep(1)
26
27 except OroServerError as ose:
28 print('Oups! An error occured!')
29 print(ose)
30
31 finally:
32 oro.close()
TCL bindings
TCL bindings for ORO can be found in the oro-tcl library. The library source has a examples/ directory that should help to start.
Installation
From the sources
To get the latest version of oro-tcl, you can check-out the sources with GIT:
> git clone git://trac.laas.fr/robots/oro-tcl
Simple usage example
Bindings with robotic middlewares
ROS bindings
The ROS bindings are available as a Python ROS node. This node relies on the Python bindings to communicate with the server.
The code is available in the TUM ROS repository or can be provided on demand.
At start-up, the ROS node will fetch from ORO the list of available methods and will create a new ROS service for each of them (like oro/add or oro/findForAgent - names are case-sensitive).
All services take as parameter one array of parameters. This array may contain 0, 1 or more strings. The arity of methods varies for each service. Please refer the the list of RPC methods (and please note as well that some methods are polymorphic: the server will rely on the number and the type of the parameters you provide to select the right one).
The elements of the parameters array are always strings. But some (actually, a lot!) of ORO methods take an array as parameter. For instance, add expect an array of statements to add to the ontology. A statement is a string of this kind: "subject predicate object" (for instance, "robot likes disco_music"). In JSON, we can write ["robot likes disco_music", "disco_music rdf:type Music"] to represent an array of string.
We need to serialize this array of string in one single string, since ROS expect only strings as parameter. So we need to double escape our strings, and send "[\"robot likes disco_music\", \"disco_music rdf:type Music\"]"
At the end, we would call the ROS service in Python, like that:
Below a sample Python program that ask for ORO methods with parameters and query the knowledge base through ROS:
1 import roslib; roslib.load_manifest('oro_ros')
2
3 import sys
4
5 import rospy
6 from oro_ros.srv import *
7
8 def oro_ros_client(query, params):
9 rospy.wait_for_service('oro/' + query)
10
11 try:
12 query_oro_server = rospy.ServiceProxy('oro/' + query, OroServerQuery)
13 resp1 = query_oro_server(params)
14 return resp1.res
15
16 except rospy.ServiceException, e:
17 print "Service call failed: %s"%e
18
19 def usage():
20 return "%s query [param1, param2, ...]"%sys.argv[0]
21
22 if __name__ == "__main__":
23
24 if len(sys.argv) == 1:
25 print usage()
26
27 sys.exit(1)
28
29 query = sys.argv[1]
30
31 params = sys.argv[2:]
32
33 print "%s %s => %s"%(query, params, oro_ros_client(query, params))
YARP bindings
[AVAILABLE, BUT DOCUMENTATION TO DO... if needed, drop me a mail]
OpenPRS bindings
OpenPRS is an open source version of PRS (Procedural Reasoning Systems). More infos here.
Installation
To get the latest version of the oro-oprs connector, you can check-out the sources with GIT:
> git clone http://trac.laas.fr/git/robots/oro-oprs-connector.git
oro-oprs-connector uses CMake (>= 2.6) to compile.
It depends on boost (>= 1.37), `liboro` and OpenPRS.
You can compile it with:
> cd oro-oprs-connector > cmake . > make > make install
It creates a executable, oro-oprs, that must be started with 6 parameters. For instance:
> oro-oprs localhost 6969 localhost 3300 OPRS-MODULE ONTO_OPRS_INTERFACE
where localhost:6969 is the host and port of ORO server and localhost:3300 is the host of the OPRS message passer.
Usage
The oro-oprs usage documentation is available as Doxygen comments in the source code. It is available on-line.
Index of available methods
(Last updated on 2010-10-19 02:24:26)
- administration
makeHtmlDoc(): returns a list of available methods in HTML format for inclusion in documentation.
listMethods(): returns the list of available methods with their signatures and short descriptions as a map.
listSimpleMethods(): returns a raw list of available methods.
stats(): returns some statistics on the server
reset(): Reload the base ontologies, discarding all inserted of removed statements, in every models
save(): exports the current ontology model to an OWL file. The file will be saved to the current directory with an automaticallygenerated name.
save(String): exports the current ontology model to an OWL file. The provided path must be writable by the server.
- agent
getInfosForAgent(String, String): returns the set of asserted and inferred statements whose the given node is part of. It represents the usages of a resource.
- agents
safeAddForAgent(String, Set): try to add news statements to a specific agent model in long term memory, if they don't lead to inconsistencies (return false if at least one stmt wasn't added).
safeAddForAgent(String, Set, String): try to add news statements to a specific agent model with a specific memory profile, if they don't lead to inconsistencies (return false if at least one stmt wasn't added).
discriminateForAgent(String, Set): returns a list of properties that helps to differentiate individuals for a specific agent.
findForAgent(String, String, Set): tries to identify a resource given a set of partially defined statements in an specific agent model.
findForAgent(String, String, Set, Set): tries to identify a resource given a set of partially defined statements and restrictions in an specific agent model.
listAgents(): Returns the set of agents I'm aware of (ie, for whom I have a cognitive model).
lookupForAgent(String, String): lookup a concept in a specific agent model.
addForAgent(String, Set): adds one or several statements (triplets S-P-O) to a specific agent model, in long term memory.
addForAgent(String, Set, String): adds one or several statements (triplets S-P-O) to a specific agent model associated with a memory profile.
clearForAgent(String, Set): removes statements from a specific matching any pattern in the given set.
removeForAgent(String, Set): removes one or several statements (triplets S-P-O) from a specific agent model, in long term memory.
save(String, String): exports the cognitive model of a given agent to an OWL file. The provided path must be writable by the server.
updateForAgent(String, Set): updates one or several statements (triplets S-P-O) in a specific agent model, in long term memory.
- base
safeAdd(Set): try to add news statements in long term memory, if they don't lead to inconsistencies (return false if at least one stmt wasn't added).
safeAdd(Set, String): try to add news statements with a specific memory profile, if they don't lead to inconsistencies (return false if at least one stmt wasn't added).
check(Set): checks that one or several statements are asserted or can be inferred from the ontology
checkConsistency(): checks that the ontology is semantically consistent
help(): returns a human-friendly list of available methods with their signatures and short descriptions.
getLabel(String): return the label of a concept, if available.
lookup(String): try to identify a concept from its id or label, and return it, along with its type (class, instance, object_property, datatype_property).
lookup(String, String): try to identify a concept from its id or label and its type (class, instance, object_property, datatype_property).
add(Set): adds one or several statements (triplets S-P-O) to the robot model, in long term memory.
add(Set, String): adds one or several statements (triplets S-P-O) to the robot model associated with a memory profile.
clear(Set): removes statements matching any pattern in the given set
remove(Set): removes one or several statements (triplets S-P-O) from the ontology.
update(Set): update the value of a functional property.
- concept comparison
discriminate(Set): returns a list of properties that helps to differentiate individuals.
getDifferences(String, String): given two concepts, return the list of relevant differences (types, properties...) between these concepts.
getSimilarities(String, String): given two concepts, return the list of relevant similarities (types, properties...) between these concepts.
- events
registerEvent(String, String, String, List): registers an event. Expected parameters are: type, triggering type, variable, event pattern.
registerEvent(String, String, List): registers an event. Expected parameters are: type, triggering type, event pattern.
- querying
find(String, Set): tries to identify a resource given a set of partially defined statements about this resource.
find(String, Set, Set): tries to identify a resource given a set of partially defined statements plus restrictions about this resource.
getInfos(String): returns the set of asserted and inferred statements whose the given node is part of. It represents the usages of a resource.
query(String, String): performs one SPARQL query on the ontology
getResourceDetails(String): returns a serialized ResourceDescription object that describe all the links of this resource with others resources (sub and superclasses, instances, properties, etc.).
getResourceDetails(String, String): returns a serialized ResourceDescription object that describe all the links of this resource with others resources (sub and superclasses, instances, properties, etc.). The second parameter specify the desired language (following RFC4646).
- taxonomy
getClassesOf(String): returns a map of {class name, label} (or {class name, class name without namespace} is no label is available) of asserted and inferred classes of a given individual.
getDirectClassesOf(String): returns a map of {class name, label} (or {class name, class name without namespace} is no label is available) of asserted and inferred direct classes of a given individual.
getDirectInstancesOf(String): returns a map of {instance name, label} (or {instance name, instance name without namespace} is no label is available) of asserted and inferred direct instances of a given class.
getDirectSubclassesOf(String): returns a map of {class name, label} (or {class name, class name without namespace} is no label is available) of all asserted and inferred direct subclasses of a given class.
getDirectSuperclassesOf(String): returns a map of {class name, label} (or {class name, class name without namespace} is no label is available) of all asserted and inferred direct superclasses of a given class.
getInstancesOf(String): returns a map of {instance name, label} (or {instance name, instance name without namespace} is no label is available) of asserted and inferred instances of a given class.
getSubclassesOf(String): returns a map of {class name, label} (or {class name, class name without namespace} is no label is available) of all asserted and inferred subclasses of a given class.
getSuperclassesOf(String): returns a map of {class name, label} (or {class name, class name without namespace} is no label is available) of all asserted and inferred superclasses of a given class.