NOTE: rosoct requires Octave 3.0. If you use older Linux distributions (e.g. Feisty), you may have to manually download and install from ftp://ftp.octave.org/pub/octave/.
Introduction
Allows Octave users to communicate with the ROS network. Internally, rosoct wraps roscpp in a mex file called rosoct.mex. All API functions are in $(rosoct)/octave.
rosoct(cmd) - initializes the run-time, must be called before anything else. Possible commands are:
shutdown - disconnect from the ROS network and terminate
clear - clear all resources and topics advertised
nohook - launch rosoct without registering a hook to automatically call rosoct_worker
rosoct_X - public API calls that wrap the low-level calls and make using Octave simpler
success = rosoct_advertise(servname,@msg,queuesize)
success = rosoct_advertise_service(servname,@msg,@callback)
[sessionid, response] = rosoct_create_session(sessionname,request)
success = rosoct_publish(topicname, message)
response = rosoct_service_call(servicename, request)
response = rosoct_session_call(sessionid, servicename, req)
success = rosoct_subscribe(topicname,@msg,@callback,queuesize)
rosoct_X_ - low-level API calls. Some functions are wrapped directly by the public API so are now specified here.
paramvalue = rosoct_get_param(paramname)
topics = rosoct_get_topics(type) - type can e one of 'advertised' or 'published'
success = rosoct_msg_unsubscribe(topicname)
rosoct_set_param(paramname,paramvalue)
success = rosoct_terminate_session(sessionid)
success = rosoct_unadvertise(topicname)
success = rosoct_unadvertise_service(servicename)
success = rosoct_wait_for_service(servicename)
numprocessed = rosoct_worker() - worker function that handles all the pending service requests and message callbacks.
For every function in octave, just type help [functionname] to get a brief description of its parameters and how it is used.
Because all octave functions need to be added to the octave path at startup, each user of rosoct either has to add addpath(fullfile(getenv('ROS_ROOT'),'core','experimental','rosoct','octave'))
to their ~/.octaverc file, or has to make a system call to rospack to find the correct path. Here's an example of the latter:
[status,rosoctpath] = system('rospack find rosoct'); rosoctpath = strtrim(rosoctpath); addpath(fullfile(rosoctpath, 'octave'));
Messages/Services
The auto-generated octave file for every message can be found in $(pkg)/msg/oct/$(pkg), service files are the same. There are a couple of API calls that make adding these paths simple: * rosoct_findpackage(pkgname) * rosoct_add_msgs(pkgname) * rosoct_add_srvs(pkgname)
Every message filename is prefixed with its package name. For example a message type of MechanismState.msg in the robot_msgs package will be called robot_msgs_MechanismState.m. To instantiate a message just use:
msg = robot_msgs_MechanismState();
For services, you can instantiate both the request and response via:
[request,response] = openraveros_env_getbodies();
The response can be left out if only interested in the request. The request has a _create_response function that allows you to create a response directly.
In messages, primitive array types are handled as 1-dim octave matrices [], arrays of other messages or arrays of strings are handled as octave cell arrays. For example, accessing the position of the 2nd actuator in a MechanismState message will be
msg.actuators{2}.position
All the fields of arrays of messages with known size have been initialized in the message constructor, so they can be used directly. However if a new element is added for unknown array sizes, the constructor of that type must be called explicitly. For example:
msg.actuators = {}; for i = 1:numjoints msg.actuators{i} = robot_msgs_ActuatorState(); end
Simple Examples
There are many examples inside $(rosoct)/test that test the various features of rosoct. Before running any rosoct code you need to start the ROS master with roscore and in octave call
rosoct();
to have octave connect to the ROS network.
Advertising and publishing a topic
rosoct_add_msgs('rosoct'); suc = rosoct_advertise('chatter',@rosoct_String, 1); for i = 1:1000 msg = rosoct_String(); msg.data = sprintf('hellow world %d', i); suc = rosoct_publish('chatter',msg); if( ~suc ) error('failed to publish chatter'); end end
Subscribing to a topic
Notice how the callback function is inlined, you can also use a predefined function with @fn.
rosoct_subscribe('chatter', @rosoct_String, @(msg) display(sprintf('stringcb: %s', msg.data)), 1);
Advertising a service
rosoct_add_srvs('rosoct'); suc = rosoct_advertise_service('mytempserv',@rosoct_StringString,@stringservcb);
To specify a failure in the service request function, just return an empty response.
function res = stringservcb(req) res = req.create_response_(); if( strcmp(req.str,'dofail') ) % some arbitrary failure condition req = []; else res.str = [req.str '_a']; end
Calling a session
rosoct internally supports sessions introduced by the roscpp_sessions package. Here is an example of using sessions for the openraveros server. First start the server, then create a session via:
rosoct_add_srvs('openraveros'); req = openraveros_openrave_session(); req.viewer = 'qtcoin'; % attach a viewer to the session [localsessionid,res] = rosoct_create_session('openrave_session',req);
To load a simple scene do:
rosoct_add_msgs('openraveros'); req = openraveros_env_loadscene(); req.filename = 'data/lab1.env.xml'; res = rosoct_session_call(localsessionid,'env_loadscene',req);
The openraveros API simplifies the usage of all its session calls with an extra API. For example, the above load scene call can be made with one function orEnvLoadScene that transparently manages a session. The API is in $(openraveros)/octave
A vision example
Octave is really great for prototyping vision algorithms and sometimes it is easier to use the octave function directly in ROS than to rewrite everything in C++ or python. So here's a simple example on how to create a service that returns a canny edge image from the image_msgs/Image.msg message. ImageImageService is a service that takes an Image.msg and produces an Image.msg.
rosoct_add_msgs('image_msgs'); % rosoct_add_srvs('..') % add the package where ImageImageService resides suc = rosoct_advertise_service('mycannyserv',@ImageImageService,@cannyservcb);
function res = cannyservcb(req) res = req.create_response_(); % assume a monocular image width = req.byte_data.layout.dim{1}.size; height = req.byte_data.layout.dim{2}.size; I = reshape(req.byte_data.data,[width height])'; Iedge = edge(I,'canny'); res.layout = req.layout; res.data = Iedge(:);
With persistent services and shared memory, most of time taken to transfer the data could be eliminated allowing for Octave services to be called at 1000+Hz (persistent services themselves can be called at ~15000Hz).
Data recording
Here's a script to record the joint values of the robot along with all the link transformations using openraveros.
global data robotid data = {}; rosoct_add_msgs('robot_msgs'); % add robot messages addpath(fullfile(rosoct_findpackage('openraveros'),'octave')); % add the openraveros API robotid = orEnvCreateRobot('testrobot,'robots/pr2full.robot.xml'); % open the robot rosoct_subscribe('mechanism_state', @robot_msgs_MechanismState, @mechanismcb, 4);
function mechanismcb(msg) global robotid data jointvalues = cellfun(@(act) act.position, msg.actuator_states); orBodySetJointValues(robotid,jointvalues); links = orBodyGetLinks(robotid); data{end+1} = struct('jointvalues',jointvalues,'links',links);
TODO
- persistent services that are not sessions - return handle
- handle multiple subscribe calls to the same topic from the same octave instance - unsubscribe from topic using id