This chapter is not an introduction to using ROS from Urbi, see Listing 12 for a tutorial.
Urbi provides a set of tools to communicate with ROS (Robot Operating System). For more
information about ROS, please refer to http://www.ros.org. Urbi, acting as a ROS node, is able to
interact with the ROS world.
Requirements
You need to have installed ROS (possibly a recent version), and compiled all of the common ROS
tools (rxconsole, roscore, roscpp, …).
You also need to have a few environment variables set, normally provided with ROS installation:
ROS_ROOT, ROS_MASTER_URI and ROS_PACKAGE_PATH.
Usage
The classes are implemented as UObjects (see Listing IV): Ros, Ros.Topic, and Ros.Service.
This module is loaded automatically if ROS_ROOT is set in your environment. If roscore is not
launched, you will be warned and Urbi will check regularly for roscore.
If for any reason you need to load this module manually, use:
22.1 Ros
This object provides some handy tools to know the status of roscore, to list the different nodes,
topics, services, …It also serves as a namespace entry point for ROS entities, such as Ros.Topic and so
forth.
There is no construction, since this class only provides a set of tools related to ROS in general, or the
current node (which is unique per instance of Urbi).
- checkMaster
Whether roscore is accessible.
- name
The name of the current node associated with Urbi (as a string). The name of an Urbi
node is generally composed of ‘/urbi_+random ’ sequence.
- nodes
A Dictionary containing all the nodes known by roscore. Each key is a node name. Its
associated value is another Dictionary, with the following keys: publish, subscribe, services.
Each of these keys is associated with a list of topics or services.
"/rosout" in Ros.nodes;
Ros.name in Ros.nodes;
- Service
The Ros.Service class.
- services
A Dictionary containing all the services known by roscore. Each key is the name of a service,
and the associated value is a list of nodes that provide this service.
var services = Ros.services|
var name = Ros.name|;
assert
{
"/rosout/get_loggers" in services;
"/rosout/set_logger_level" in services;
(name + "/get_loggers") in services;
(name + "/set_logger_level") in services;
};
- Topic
The Ros.Topic class.
- topics
A Dictionary containing all the topics advertised to roscore. Each key is the name of a topic,
and the associated value is another Dictionary, with the following keys: subscribers,
publishers, type.
var topics = Ros.topics|;
topics.keys;
[03316144] ["/rosout_agg"]
// The actual value of the "type" field depends on the ROS version
// (the location changed, but the type is the same):
// - "roslib/Log" for CTurtle.
// - "rosgraph_msgs/Log" for Diamondback and later.
topics["/rosout_agg"];
[03325634] ["publishers" => ["/rosout"], \
"subscribers" => [], \
"type" => "rosgraph_msgs/Log"]
22.2 Ros.Topic
This UObject provides a handy way to communicate through ROS topics, by subscribing to
existent topics or advertising to them.
To create a new topic, call Ros.topic.new with a string (the name of the topic you want to subscribe
to / advertise on).
The topic name can only contain alphanumerical characters, ‘/’ and ‘_’, and cannot be empty. If
the topic name is invalid, an exception is thrown and the topic is not created.
Until you decide what you want to do with your topic (subscribe or advertise), you are free to
call init to change its name.
Some slots on this UObject have no interest once the type of instance is determined. For example, you
cannot call unsubscribe if you advertise, and in the same way you cannot call publish if you
subscribed to a topic.
- name
The name of the topic provided to init.
Ros.Topic.new("/test").name == "/test";
- structure
Once advertise or subscribe has been called, this slot contains a template of the message type,
with default values for each type (empty strings, zeros, …). This template is a Dictionary.
var logTopic = Ros.Topic.new("/rosout_agg")|;
logTopic.subscribe|;
assert
{
logTopic.structure.keys
== ["file", "function", "header", "level", "line", "msg", "name", "topics"];
};
- subscriberCount
The number of subscribers for the topic given in init; 0 if the topic is not registered.
- onMessage
Event triggered when a new message is received from a subscribed channel, the payload
of this event contains the message.
var t = Ros.Topic.new("/test")|;
at (t.onMessage?(var m))
echo(m);
t.subscribe;
- subscribe
Subscribe to the provided topic. Throw an exception if it doesn’t exist, or if the type advertised
by ROS for this topic does not exist. From the call of this method, a direct connection is
made between the advertiser and the subscriber which starts deserializing the received
messages.
- unsubscribe
Unsubscribe from a previously subscribed channel, and set the state of the instance as if init
was called but not subscribe.
- ’<<’(message)
An alias for publish.
var stringPub = Ros.Topic.new("/mytest")|;
stringPub.advertise("std_msgs/String");
stringPub << ["data" => "Hello world!"];
- advertise(type)
Tells ROS that this node will advertise on the topic given in init, with the type type. type
must be a valid ROS type, such as ‘roslib/Log’. If type is an empty string, the method
will try to check whether a node is already advertising on this topic, and its type.
var stringPub = Ros.Topic.new("/mytest")|;
stringPub.advertise("std_msgs/String");
stringPub.structure;
[00670809] ["data" => ""]
- onConnect(name)
Event triggered when the current instance is advertising on a topic and a node subscribes to this
topic. The payload name is the name of the node that subscribed. See also onDisconnect.
var p = Ros.Topic.new("/test/publisher")|;
at sync (p.onConnect?(var n))
echo("%s has subscribed to %s" % [n, p.name]);
// The structure is not defined, yet.
assert (p.structure.isVoid);
p.advertise("std_msgs/String");
// The structure is ready to be used.
assert (p.structure == ["data" => ""]);
var s = Ros.Topic.new("/test/publisher")|;
Subscription and unsubscription are asynchronous. To make them synchronous, we waituntil
the connection event is sent. The first idea:
s.subscribe;
waituntil (p.onConnect?);
is wrong: the event (fired in the first line) might be accomplished before the second line is even
started. In that case, Urbi will remain suspended, waiting for an already passed event.
Rather, we will “waituntil” in background before subscribing, and rely on the fact that
scopes “wait” for all the background statements to be finished (Section 20.1.7.2):
{
waituntil (p.onConnect?),
s.subscribe;
};
[00077308] *** /urbi_1317980847216045000 has subscribed to /test/publisher
- onDisconnect(name)
Event triggered when the current instance is advertising on a topic, and a node unsubscribes
from this topic. The payload name is the name of the node that unsubscribed. See also
onConnect. The following example is a continuation of the previous one.
at (p.onDisconnect?(var n))
echo("%s has unsubscribed to %s" % [n, p.name]);
{
waituntil (p.onDisconnect?),
s.unsubscribe;
};
[00077308] *** /urbi_1317980847216045000 has unsubscribed to /test/publisher
- publish(message)
Publish message to the current topic. This method is usable only if advertise was called.
The message must follow the same structure as the one in the slot structure, or
it will throw an exception telling which key is missing / wrong in the dictionary.
var stringPub = Ros.Topic.new("/mytest")|;
stringPub.advertise("std_msgs/String");
stringPub.publish(["data" => "Hello world!"]);
- unadvertise
Tells ROS that we stop publishing on this topic. The UObject then gets back to a state where it
could call init, subscribe or advertise.
This is a typical example of the creation of a publisher, a subscriber, and message transmission
between both of them.
First we need to declare our Publisher, the topic name is ‘/example’, and the type of message that
will be sent on this topic is ‘std_msgs/String’. This type contains a single field called ‘data’, holding
a string. We also set up handlers for onConnect and onDisconnect to be noticed when someone
subscribes to us.
var publisher = Ros.Topic.new("/example")|
at (publisher.onConnect?(var name))
echo(name[0,5] + " is now listening on " + publisher.name);
at (publisher.onDisconnect?(var name))
echo(name[0,5] + " is no longer listening on " + publisher.name);
publisher.advertise("std_msgs/String");
Then we subscribe to the freshly created topic, and for each message, we display the ‘data’ section
(which is the content of the message). Thanks to the previous at above, a message is displayed at
subscription time.
var subscriber = Ros.Topic.new("/example")|
at (subscriber.onMessage?(var m))
echo(m["data"]);
subscriber.subscribe;
// Let the "is now listening" message arrive.
sleep(200ms);
[00026580] *** /urbi is now listening on /example
The structure for the messages are, of course, equal between the subscriber and the
publisher.
subscriber.structure == publisher.structure;
Now we can send a message, and get it back through the at in the section above. To do this we
first copy the template structure and then fill the field ‘data’ with our message.
var message = publisher.structure.new;
[00098963] ["data" => ""]
message["data"] = "Hello world!"|;
// publish the message.
publisher << message;
// Leave some time to asynchronous communications before shutting down.
sleep(200ms);
[00098964] *** Hello world!
subscriber.unsubscribe;
// Let the "is no longer" message arrive.
sleep(200ms);
[00252566] *** /urbi is no longer listening on /example
22.3 Ros.Service
This UObject provides a handy way to call services provided by other ROS nodes.
To create a new instance of this object, call Ros.Service.new with a string representing which
service you want to use, and a Boolean stating whether the connection between you and the
service provider should be kept opened (pass true for better performances on multiple
requests).
The service name can only contain alphanumerical characters, ‘/’, ‘_’, and cannot be empty. If the
service name is invalid, an exception is thrown, and the object is not created.
Then if the service does not exist, an other exception is thrown. Since the initialization is
asynchronous internally, you need to wait for service.initialized to be true to be able to call
request.
- initialized
Boolean. Whether the method request is ready to be called, and whether resStruct and
reqStruct are filled.
var logService = Ros.Service.new("/rosout/get_loggers", false)|;
waituntil(logService.initialized);
- name
The name of the current service.
logService.name;
[00036689] "/rosout/get_loggers"
- reqStruct
Get the template of the request message type, with default values for each type (empty strings,
zeros, …). This template is made of dictionaries.
logService.reqStruct;
[00029399] [ => ]
- request(req)
Synchronous call to the service, providing req as your request (following the structure of
reqStruct). The returned value is a Dictionary following the structure of resStruct,
containing the response to your request.
for (var item in logService.request([=>])["loggers"])
echo(item);
[00034567] *** ["level" => "INFO", "name" => "ros"]
[00034571] *** ["level" => "INFO", "name" => "ros.roscpp"]
[00034575] *** ["level" => "INFO", "name" => "ros.roscpp.roscpp_internal"]
[00034586] *** ["level" => "WARN", "name" => "ros.roscpp.superdebug"]
- resStruct
Get the template of the response message type, with default values for each type (empty strings,
zeros, …). This template is made of dictionaries.
logService.resStruct;
[00029399] ["loggers" => []]