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.

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.

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:

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.

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.

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.

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).

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

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.

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.

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.