Getting Started with ROSjs

From Brown University Robotics

(Difference between revisions)
Jump to: navigation, search
m
m (Introduction)
 
Line 1: Line 1:
=Introduction=
=Introduction=
-
 
-
Do you want to try your hand at programming robotics, but only know Javascript? Are you a rockstar web-developer looking to leverage your leet skills in a domain where you can cause actual damage to life, limb, and personal property? Do you have a moral failing that causes you to want to use Javascript as much as possible? ROSjs is for you! ROSjs is a Javascript binding for WillowGarage's Robot Operating System (ROS): a robot middle-ware system that provides access to a wide range of robots (such as Willow's own PR2, or iRobot's Create) and autonomous capabilities (such as blob-finding, and mapping).
 
ROS is a large and sophisticated research tool used and developed by hundreds (if not thousands) of roboticist world-wide and ROSjs exposes almost all of its capabilities out-of-the-box. Obviously, it will take more than a few days to master the skills necessary to take over the world via a robot army, but it's surprisingly easy to get a robot running around driven by logic running ''right in your browser''. In fact, that's what this tutorial is all about. We assume at least a passing familiarity with Javascript and it's idioms (such as callbacks) but no ROS or robot specific knowledge. If you have access to an iRobot Create and a web-browser, you should be able to follow along.
ROS is a large and sophisticated research tool used and developed by hundreds (if not thousands) of roboticist world-wide and ROSjs exposes almost all of its capabilities out-of-the-box. Obviously, it will take more than a few days to master the skills necessary to take over the world via a robot army, but it's surprisingly easy to get a robot running around driven by logic running ''right in your browser''. In fact, that's what this tutorial is all about. We assume at least a passing familiarity with Javascript and it's idioms (such as callbacks) but no ROS or robot specific knowledge. If you have access to an iRobot Create and a web-browser, you should be able to follow along.

Current revision as of 20:51, 5 August 2010

Contents

Introduction

ROS is a large and sophisticated research tool used and developed by hundreds (if not thousands) of roboticist world-wide and ROSjs exposes almost all of its capabilities out-of-the-box. Obviously, it will take more than a few days to master the skills necessary to take over the world via a robot army, but it's surprisingly easy to get a robot running around driven by logic running right in your browser. In fact, that's what this tutorial is all about. We assume at least a passing familiarity with Javascript and it's idioms (such as callbacks) but no ROS or robot specific knowledge. If you have access to an iRobot Create and a web-browser, you should be able to follow along.

A Viciously Short Primer on ROS

Background

Before you can make use of ROSjs, there are a few things you need to know about ROS itself. Besides the practical stuff like installation, running, etc. (which we'll get to), it's important to know how ROS is architected and how it exposes all of the capabilities it provides.

If you're familiar with ROS, feel free to skim or skimp this section. Most of the material will be covered in more depth in the #Programming ROS from ROSjs section. On the flipside: if you're very unfamiliar with ROS, you would do well checking out the great tutorials and other documentation on ROS.org

Topics and Services

ROS exposes capabilities in one of two ways, as topics or as services. services should be very familiar to you if you've programmed before. They are very similar to Javascript's function call. services take arguments and return a response. One important difference from more vanilla functions you may have used in the past is that services always respond with (return) an object. As in most programming languages, this object may have fields and in this way a service may return almost arbitrarily complicated data.

Functions are often the dominant way of implementing logic in most programming languages, but this is not the case with ROS services (and is even less the case when using ROSjs). This is for two reasons: 1) ROS is network based (more on this later), so a service "call" often requires data go to ROS and back 2) service calls block forcing code to pause while it waits for ROS to respond.

topics are streams of objects more akin to Javascript events. You register a handler function (much as you would a listener in Javascript) and whenever a new topic object is available, the handler is called with that object as it's argument. Completing the analogy with Javascript events: just as you can "generate" Javascript events, you can "publish" topic objects which will then be processed by all of the handlers that have subscribed to them.

This is probably all getting a bit abstract, so let's look at a concrete example of using both a topic and a service.

   ros.addHandler('/sensorPacket',function(msg) {
       if (msg.bumpLeft || msg.bumpRight) {
           alert('bump');    
       }
   });
   ros.callService('/rosjs/subscribe','["/sensorPacket",0]',function(rsp) {
       alert('subscribed to /sensorPacket');
   });


Don't worry if you find much of this code confusing, the point here is to get more familiar with the concepts of topics and services. We will cover everything in more detail as we go along. This above snippet has examples of both. Consider:

   ros.addHandler('/sensorPacket',function(msg) {
       if (msg.bumpLeft || msg.bumpRight) {
           alert('bump');    
       }
   });

We'll try not to belabor the parallels to Javascript's addEventListener. Here a handler function is associated with the '/sensorPacket' topic. Notice that /sensorPacket is proceeded by a slash. Now whenever the ROSjs environment receives an object from the /sensorPacket topic it will be passed (as an argument named msg) to the anonymous function created here. How does the ROSjs environment tell ROS that it's interested in /sensorPacket objects? Through a service call:

   ros.callService('/rosjs/subscribe','["/sensorPacket",0]',function(rsp) {
       alert('got response: ' + rsp);
   });

The service we are calling is named /rosjs/subscribe (as above, the slashes are important). We give /rosjs/subscribe its arguments in the form of a JSON object. If you are unfamiliar with JSON, you can read more about it. Here, we are manually creating a JSON object, but we could just as easily have used a JSON library to help us. The arguments are the name of the topic that we want to subscribe to: /sensorPacket and the minimum delay (in milliseconds) between topic objects that we can tolerate (in this case we don't really care). This argument can never be used to make topic objects stream in faster than they would have, but it can be used to slow them down.

You might think that because service calls block, that service responses would be in the form of a return value from the ros.callService function. However, to allow for maximum flexibility (and in keeping with the idioms of Javascript), service responses are handled by a callback as shown. This callback is mandatory and must be a valid (callable) function. Thus it's a common ROSjs idiom to define a nop function

   function nop() {};

and to use it with service calls you don't particularly care about the response to

   ros.callService('/soundOf','["tree falling in woods"],nop);

Notice that the /soundOf service expects only one argument, while /rosjs/subscribe expects two.

Besides being able to subscribe to a stream of objects associated with a particular topic, ROSjs can also create them. This is referred to as "publishing" to a topic.

   //code that assigns the proper value to x and y ...
   ros.publish('/mouse','mouse_msgs/CursorPosition','{"x":' + x + ',"y":' + y + '}');

The publish function takes three arguments: 1) the topic to publish to 2) the type of the object to publish (again, we'll cover this more in-depth later) 3) the JSON form of the object to publish. Notice that once again, for the sake of making these examples self-contained we have manually created a JSON object. In real code, it would be far easier and less error prone to depend on a JSON library. Note that while the service name is proceeded by a slash, the type is not. You can try it the other way, but it won't work.

Nodes and Types

At its heart, ROS is really a Remote Procedure Call (RPC) and data-sharing mechanism. ROS provides a server program, roscore, that acts a central registry for (amongst other things) topics and services. This facilitates ROS-compatible programs, called nodes, communicating with each other through named channels (topics and services). Of course, just having a /topic or /service is not enough to facilitate communication. There must me some kind of hope that the programs will understand one another. This is where types come in. In ROS, an object's structure (the organization of its fields, and the kinds of values those fields can take on) is called its type. ROS nodes can be confident in their ability to process the objects published by a particular topic because each topic is associated with a specific type. In our earlier example, we published to the /mouse topic. In this case the /mouse topic is associated with the mouse_msgs/CursorPosition type. If a node knew how to examine mouse_msgs/CursorPosition objects, it could confidently subscribe to /mouse knowing there would be no surprises in the structure of the data it receives.

While it would be next to impossible to make use of ROS without some knowledge of the types involved, with ROSjs the only time you need to explicitly handle ROS types is when publishing.

Dive into ROSjs

Enough talk about abstractions and types and whatnot. Let's move a robot from our web-browser! To get started there are a few things you'll need:

Prerequisites

ROSjs is not specific to the materials listed here, but we do use them as our example.

1) ROS
see this page for installation instructions
2) An iRobot Create
we've written this tutorial with this robot in mind, but with slight adaptation, but the ROSjs examples themselves should work with any that respond to Twist msgs on /cmd_vel
3) svn
4) A websocket compatible browser, such as the latest Chrome, Safari, or Firefox (others may work just fine too).
you can see if your browser supports websockets here.

Setting up your ROSjs environment

We'll assume for the moment that you already have ROS installed, and that it is installed in /opt/ros/cturtle/ . If you have a newer (or older) version of ROS or a non-binary version, make appropriate adjustments to the paths as we go along. Let's assume you are staring at a bash prompt on a Linux machine.

Source the appropriate ROS environment:

   source /opt/ros/cturtle/setup.sh

now make a directory to house ROSjs and the iRobot Create

   mkdir diveIn

add this directory to your ROS_PACKAGE_PATH:

   export ROS_PACKAGE_PATH=$ROS_PACKAGE_PATH:/absolute/path/to/diveIn

now enter the diveIn directory and fetch both ROSjs and the irobot_create_2_1 driver

   cd diveIn
   svn co https://brown-ros-pkg.googlecode.com/svn/trunk/experimental/ROSjs ROSjs
   svn co https://brown-ros-pkg.googlecode.com/svn/trunk/unstable/irobot_create_2_1 irobot_create_2_1

make the irobot_create_2_1 driver (you may have to type this command twice, sometimes ROS has a hard time finding freshly added packages)

   rosmake irobot_create_2_1

If the make fails, it is most likely due to the lack of some prerequisite ROS package. Depending on how you installed ROS, you may need to apt-get the packages indicated by the error messages, or download and make them yourself.

Once irobot_create_2_1 builds, attach the robot to /dev/ttyUSB0 and start up the driver

   rosrun irobot_create_2_1 driver.py &

If your robot is attached to a port other than /dev/ttyUSB0, you'll need to set a rosparam before using rosrun

   rosparam set /brown/irobot_create_2_1/port /dev/otherPort

Finally, you're ready to run ROSjs

   cd ROSjs
   ./ROSjs

If you don't get an error from ROSjs, you're ready to write your first ROSjs enabled webpage.

About ROSjs webpages

ROSjs listens for websocket connections on port 9090 of the machine it's started on. Since ROSjs uses websockets and not AJAX, the machine that serves the HTML content does not necessarily need to be the same machine that ROSjs is running on. With ROSjs, a direct connection is made from the web-browser viewing the page and the machine running ROSjs. If the browsing machine can reach 9090 on the machine running ROSjs, all is well. If it can't... well... not much will happen. Make routing arrangements accordingly.

One advantage of this flexibility is that you don't need a web-server to start experimenting with ROSjs, you can view a locally hosted file and everything will work just as it would if the page were served from Apache, IIS, or what-have-you.

Your First ROSjs webpage

We'll start with the following HTML skeleton

   <html>
     <head>
       <script type="text/javascript" src="http://brown-ros-pkg.googlecode.com/svn/trunk/experimental/ROSjs/ROS.min.js"></script>
       <script type="text/javascript">
   function main() {
       /* insert code here */
   }
       </script>
     </head>
   <body onload="main()">
   </body>
   </html>

The first script tag imports the latest minified version of ROSjs. The second is where we'll define the main function called by the body onload.

The Create's basic motion is controlled by two parameters a forward velocity, x, and a angular velocity z. We'll start by defining two variables for these parameters.

   var x = 0;
   var z = 0;

Now to create a ROSjs object. This object will be our gateway to ROS services and topics. To initialize it, we'll need the address or hostname of the machine running ROSjs.

   var ros = new ROS("ws://hostname:9090");

To use this ROSjs object, we need to register three callbacks: one for when a server connection becomes closed, one for when errors occur, and one for when a connection is successfully opened.

     ros.setOnClose(function (e) {
       document.write('connection closed<br/>');
     });
     ros.setOnError(function (e) {
       document.write('error!<br/>');
     });
     ros.setOnOpen(function (e) {
       document.write('connected to ROS<br/>');
     });

Here's what our webpage looks like so far:

   <html>
     <head>
       <script type="text/javascript" src="http://brown-ros-pkg.googlecode.com/svn/trunk/experimental/ROSjs/ROS.min.js"></script>
       <script type="text/javascript">
       </script>
     </head>
   <body onload="main()">
   function main() {
     var x = 0;
     var z = 0;
     var ros = new ROS("ws://hostname:9090");
     ros.setOnClose(function (e) {
       document.write('connection closed<br/>');
     });
     ros.setOnError(function (e) {
       document.write('error!<br/>');
     });
     ros.setOnOpen(function (e) {
       document.write('connected to ROS<br/>');
     });
   </body>
   </html>

Since we need a connection to ROS to do pretty much anything at all, most of the action will happen inside the OnOpen callback.

First let's define a local function that will set the Create's linear and angular velocity based on the values of x and z.

   function pub() {
     ros.publish('/cmd_vel', 'geometry_msgs/Twist', '{"linear":{"x":' + x + ',"y":0,"z":0}, "angular":{"x":0,"y":0,"z":' + z + '}}');
   }

As with our earlier examples, we are hand-crafting a JSON object. This is fine for this toy example, but in general you will want to make use of a real JSON library.

So now by calling pub, we can set the robot's motion to that of x and z. Not very interesting if x and z remain 0. Here's where Javascript's and the browser's extensive even handling come into play. We'll define a function that provides some basic key controls.

   function handleKey(code, down) {
       var scale = 0;
       if (down == true) {
         scale = 1;
       }
       switch (code) {
       case 37:
         //left
         z = 1 * scale;
         break;
       case 38:
         //up
         x = .5 * scale;
         break;
       case 39:
         //right 
         z = -1 * scale;
         break;
       case 40:
         //down
         x = -.5 * scale;
         break;
       }
       pub();
     }

Now we simply register this function with the as a key event handler for the webpage body.

   document.addEventListener('keydown', function (e) {
     handleKey(e.keyCode, true);
   }, true);
   document.addEventListener('keyup', function (e) {
     handleKey(e.keyCode, false);
   }, true);

The complete webpage looks like this:

   <html>
     <head>
       <script type="text/javascript" src="http://brown-ros-pkg.googlecode.com/svn/trunk/experimental/ROSjs/ROS.min.js"></script>
       <script type="text/javascript">
   function main() {
     var x = 0;
     var z = 0;
     var ros = new ROS("ws://hostname:9090");
     ros.setOnClose(function (e) {
       document.write('connection closed<br/>');
     });
     ros.setOnError(function (e) {
       document.write('error!<br/>');
     });
     ros.setOnOpen(function (e) {
       document.write('connected to ROS<br/>');
       function pub() {
         ros.publish('/cmd_vel', 'geometry_msgs/Twist', '{"linear":{"x":' + x + ',"y":0,"z":0}, "angular":{"x":0,"y":0,"z":' + z + '}}');
       }
       function handleKey(code, down) {
         var scale = 0;
         if (down == true) {
           scale = 1;
         }
         switch (code) {
         case 37:
           //left
           z = 1 * scale;
           break;
         case 38:
           //up
           x = .5 * scale;
           break;
         case 39:
           //right 
           z = -1 * scale;
           break;
         case 40:
           //down
           x = -.5 * scale;
           break;
         }
         pub();
       }
       document.addEventListener('keydown', function (e) {
         handleKey(e.keyCode, true);
       }, true);
       document.addEventListener('keyup', function (e) {
         handleKey(e.keyCode, false);
       }, true);
     });
   }
       </script>
     </head>
   <body onload="main()">
   </body>
   </html>

That's really it. Change hostname to the name or IP of the machine running ROSjs, and you're good to go. Load the page in a websocket supporting browser (you can check for compatibility here). Remote teleop in less than 60 lines of HTML!

Teleop is great and all, but what about actual control? Here is a more realistic example that implements wall following.

   <html>
   <head>
     <script type="text/javascript" src="http://brown-ros-pkg.googlecode.com/svn/trunk/experimental/ROSjs/ROS.min.js"></script>
     <script type="text/javascript">
   function nop() {}
   /* console for logging */
   var console = null;
   /* state */
   var active = false;
   var wall = false;
   var bump = false;
   function log(msg) {
       console.innerHTML = console.innerHTML + msg + "<br/>";
   }
   function init() {
       function waitForDOM() {
           var cnsl = document.getElementById('console');
           if (cnsl == null) {
               setTimeout(waitForDOM, 100);
           } else {
               console = cnsl;
               setTimeout(main, 0);
           }
       }
       setTimeout(waitForDOM, 100);
   }
   function main() {
       log('console initialized');
       var connectInfo = document.location.toString();
       log('url: ' + connectInfo);
       var addressMatches = connectInfo.match(/address=([^&]*)/);
       if (addressMatches == null) {
           log('Problem extracting address!');
           return;
       }
       var address = addressMatches[1];
       log('address: ' + address);
       var portMatches = connectInfo.match(/.*&port=([^&]*)/);
       if (portMatches == null) {
           log('Problem extracting port!');
           return;
       }
       var port = portMatches[1];
       log('port: ' + port);
       log('creating ROSProxy object...');
       var ros = null;
       try {
           ros = new ROS('ws://' + address + ':' + port);
       } catch (err) {
           log('Problem creating proxy object!');
           return;
       }
       log('created');
       log('connecting to ' + address + ' on port ' + port + '...');
       ros.setOnClose(function (e) {
           log('connection closed');
       });
       ros.setOnError(function (e) {
           log('network error!');
       });
       ros.setOnOpen(function (e) {
           log('connected');
           log('initializing ROSProxy...');
           try {
               ros.callService('/rosjs/topics', '[]', nop);
           } catch (error) {
               log('Problem initializing ROSProxy!');
               return;
           }
           log('initialized');
           log('registering handler for sensorPacket...');
           try {
               ros.addHandler('/sensorPacket', function (msg) {
                   bump = false;
                   if (msg.bumpLeft || msg.bumpRight) {
                       bump = true;
                   }
                   wall = msg.wall;
                   if (msg.advance) {
                       active = false;
                   }
                   if (msg.play) {
                       active = true;
                   }
               });
           } catch (error) {
               log('Problem registering handler!');
               return;
           }
           log('registered');
           log('subscribing to sensorPacket...');
           try {
               ros.callService('/rosjs/subscribe', '["/sensorPacket",0]', nop);
           } catch (error) {
               log('Problem subscribing!');
           }
           log('subscribed');
           log('setting closed loop control policy...');
           var aligned = true;
           var turned = 0;
           var target = 150;
           function twistMsg(x, z) {
               return '{"linear":{"x":' + x + ',"y":0,"z":0},"angular":{"x":0,"y":0,"z":' + z + '}}';
           }
           setInterval(function () {
               var x = 0;
               var z = 0;
               if (wall) aligned = true;
               if (bump) aligned = false;
               if (!aligned) {
                   if (active) {
                       z = .5;
                       turned = turned + 1;
                   }
               } else {
                   x = .25;
                   turned = 0;
                   target = 150 + Math.floor(Math.random() * 150);
                   if (!wall) z = -.5;
               }
               if (turned > target) aligned = true;
               if (!active) {
                   x = 0;
                   z = 0;
               }
               ros.publish('/cmd_vel', 'geometry_msgs/Twist', twistMsg(x, z));
           }, 100);
           log('running');
       });
   }
       </script>
     </head>
   <body onload="init()">
   <div id="console"></div>
   </body>
   </html>

To use this controller, place it in an html file as before. Once you've loaded it in your browser you'll need to give the script the host and port that it should connect to. You do this from the URL itself. For example, if your browser displays this address:

file:///Users/aka1/Desktop/test.html

You would tell it to connect to 9090 on hostname by extending the URL like so:

file:///Users/aka1/Desktop/test.html?address=maria&port=9090

Once the page is reporting that the controller is running, you can start it by pushing the play button on the Create. You can pause it with the advance button. Closed loop control from a web-browser!

Advanced ROSjs webpages

For the sake of clarity, our examples have done a number of things you wouldn't want to do when doing "real" work. To preserve your sanity, we highly recommend that you use a Javascript framework. ROSjs should be compatible with your favorite. Being from Brown, we like Flapjax.

Also almost essential is a JSON library. ROSjs takes service call arguments and objects to publish as JSON objects, and a library will help make generating these much simpler.

Programming ROS from ROSjs

Including the ROSjs Library

To make use of ROSjs, you must somehow include the ROSjs library in your Javascript. As shown in our previous examples, one of the easiest ways to do this is through a script tag that sources the latest version on-line.

<script type="text/javascript" src="http://brown-ros-pkg.googlecode.com/svn/trunk/experimental/ROSjs/ROS.min.js"></script>

Creating a ROSjs Object

To use ROSjs, you instantiate a ROSjs object. The constructor takes a single argument, a websocket URL.

var ros = new ROS('ws://hostname:port');

We now have a ROSjs object referenced by the the variable ros. To make real use of ROSjs, you must also register handlers for the closing, error, and connection opening events. This is done through three functions: setOnClose, setOnError, and setOnOpen. The functions take a single argument: a handler function (taking in a single argument).

   ros.setOnClose(function (e) {
       //pass
   });
   ros.setOnError(function (e) {
       //pass
   });
   ros.setOnOpen(function(e) {
       //pass
   });

The setOnOpen handler is where the action is, and can be thought of as the entry point for ROSjs programs.

Service Calls

ROSjs provides access to underlying ROS services and topics. It also provides some unique services of its own. These services are meant to help you manage a ROS environment from within ROSjs and provide many of the same services that the ROS command line tools do. These services are accessed exactly like "real" ROS services, so we will cover making service calls first.

A ROSjs service call takes the following form:

   ros.callService('/service', JSONArg, function(resp) {
       //handle response
   });

where '/service' is a string containing the service name, JSONArg is a JSON list of arguments, and the handler function is a way of handling any response the service returns. All three arguments are required, even the handler. If you don't take about the response, you will still need to supply a handler that does nothing.

Another important point is that the JSONArg list is always a list. If you are passing no arguments, you should pass an empty list. If you are passing in a single argument, you should still wrap it in a list.

ROSjs Services

ROSjs provides a number of services itself, they are accessed exactly as you would a ROS service.

/rosjs/topics
This service returns a list of the currently available topics. It takes an empty argument list.
/rosjs/services
This service returns a list of the currently available services. It takes an empty argument list.
/rosjs/subscribe
Use this service to subscribe to topics that you've already registered a handler for ( see #Topic Handlers)
/rosjs/typeStringFromTopic
Provided at least one message has already been published to a topic, this service will return the type associated with that topic. The service takes a list with a single entry: the topic string of interest.
/rosjs/typeStringFromService
This service is the equivalent of /rosjs/typeStringFromTopic, but for services.
/rosjs/classFromTopic
Takes a list with a single string element denoting the topic of interest. It returns a JSON object representing the structure of the type associated with that topic.
/rosjs/classesFromService
The equivalent of /rosjs/classFromTopic, but for services.
/rosjs/msgClassFromTypeString
Takes a list whose single element is a type-string. Returns a JSON version of the appropriate message class.
/rosjs/reqClassFromTypeString, /rosjs/rspClassFromTypeString
Services types have two classes associated with them, a request object and a return object. These two services will take a list with a type-string entry and return the appropriate JSON equivalent.

Service Performance

As in ROS, service calls block. In ROSjs, there is an additional performance disadvantage. Calls to services through ROSjs block not just the calling code, but the network connection. This means that, amongst other things, while a service call is being processed no new topic objects will be received. Use services sparingly and try to use them in the "initialization" portion of your code.

Topic Handlers

Topics are handled in ROSjs via the registration of handler callbacks. First you register a callback with the local ROSjs object. You then use a service call to announce to the ROSjs server your interest in that topic.

   var bump = false;
   ros.addHandler('/sensorPacket',function(msg) {
       if (msg.bumpLeft || msg.bumpRight) {
           bump = true;    
       } else {
           bump = false;
       }
   });
   ros.callService('/rosjs/subscribe','["/sensorPacket",0]',function(e) {});

Notice that since we aren't particularly interested in the result of the call to /rosjs/subscribe, we pass an empty handler. When a new topic object is received it will be passed as an argument to the topic handler function. For details about the format of this object see #Types and JSON.

/rosjs/subscribe arguments

Besides the topic name, you may have noticed the 0 argument in the above example. This is the minimum amount of time the ROSjs server should wait between streaming objects of this type. If you wanted to receive a maximum of ten /sensorPacket objects per second, you could have used this call instead.

ros.callService('/rosjs/subscribe','["/sensorPacket",100]',function(rsp) {});

This instructs the server to wait 100 milliseconds between objects.

The pause factor argument is mandatory, but there is another---optional---argument set you can use when dealing with topics that contain images. By default, ROSjs detects standard ROS images inside objects and changes them into data URI's. If you would like to control the the way in which this occurs, you should make the relevant subscription call appear as follows.

ros.callService('/rosjs/subscribe',json(['/imagetopic',0,'jpeg',128,96,100]),function(rsp) {});

This call request that the images be changed into 128x96 jpeg images at 100 percent quality. You can request images of any size and quality (keep bandwidth in mind though). However, ROSjs only supports two encodings: png and jpeg.

Publishing

Publishing with ROSjs is fairly straight-forward.

   ros.publish('/topic','type-string',JSONobj);

The tricky bit is knowing the appropriate type-string and making sure that the JSON encoded object you pass as the last argument matches the expected structure of this type. Passing any other kind of structure will result in undefined behavior. For more details, see the next section. Again, remember that while topic and service names are proceeded by a slash, types are not. Types may well contain slashes, but they do not begin with them.

Types and JSON

When ROS objects are passed to topic handlers, they are transformed into JSON objects with identical structure. Objects containing images are an important exception. Rather than pass the image data verbatim, fields that would contain an image are changed into sub-objects that contain a uri field. This field contains a Javascript compatible data-uri version of the image. In general you should not use image based topics with ROSjs, you can get much better performance by visualizing data directly in the browser via Canvas or WebGL.

Keep in mind that this image->data-uri transformation only applies to reception. If for some reason you want to pass an image object up to the server, you will need to follow the same binary encoding rules as does python.

You can discover the type-strings and structures of services and topics you're interested in via the supplied ROSjs services or via the command line utilities included with ROS. For the most part, you can ignore types, with the important exception of publishing.

ROSjs uses a type-discovery mechanism that requires that at least one topic object has been published to a topic before ROSjs can subscribe to it. For this reason, if you use ROSjs with a node such as launch (at the brown ros pkg), you may need to introduce delays into your program so that there is time for at least one topic to be published. This restriction does not apply to publishing (which is why you have to provide a type-string when publishing).

Next Steps

ROSjs is in a very early stage of development. We hope that you find the idea of using Javascript and other web-technologies as exciting as we do and help issue bug reports, documentation corrections, and suggestions to help us make it as great as possible.

_The RLAB Team

This page was last modified on 5 August 2010, at 20:51. This page has been accessed 1,973 times.