ROS (Robot Operating System) is currently the most popular platform for robot development. On ROS, the de facto standard for Java development is a package called rosjava.

I have created an experimental alternative to rosjava that I call Jyroscope. Jyroscope makes it easier to write ROS code using Java.

  1. It uses dependency injection to wire up publishers and subscribers.
  2. It does not have any external dependencies.
  3. It does not require any particular build system. It works with javac or any IDE. It can be embedded in another project by copying the source code.
  4. It does not require any message generation. Mappings between Java and ROS messages are resolved at runtime.
  5. It uses Java-to-ROS and ROS-to-Java translators that are compiled at runtime to maintain efficiency and speed.
  6. It implements TCPROS protocols directly so it works with any version of ROS (until ROS switches to DDS).
  7. It is free to use however you wish -- it is under a Creative Commons Zero / Public Domain license.

I’ve been using it for a number of small projects but the code is very much experimental:

  1. It is only a prototype / technology-concept.
  2. It is not documented or tested.
  3. It only supports publishing and subscribing.
  4. It must be run on a full Java 8 JDK (not just a JRE).
  5. It does not implement future ROS 2.0 DDS-based protocols.

You can download the full source from Github

In the remainder of this article, I will demonstrate how to use Jyroscope.

Initialization

I assume that you have a ROS master running at http://localhost:11311/.

These three lines can be used to initialize Jyroscope:

Jyroscope.addMsgSearchPath(Paths.get("msgs").toAbsolutePath().toString());
Jyroscope jyroscope = new Jyroscope();
jyroscope.addRemoteMaster("ros", "http://localhost:11311", "localhost", "/jy");

This code might typically appear in an application’s main method.

Let’s break it down line by line:

The first line tells ROS where to find message definitions. The github repository contains a copy of ROS message files. However, you could simply use the path to your ROS installation folder instead. A message such as std_msgs/String is resolved to <search path>/std_msgs/msg/String.msg.

The second line creates the Jyroscope context.

The third line adds a ROS master to the context:

  1. "ros" is a prefix use to refer to this ROS master (Jyroscope supports multi-master development)
  2. "http://localhost:11311" is the ROS master
  3. "localhost" is the local address. If the ROS master is running remotely, then you should use the IP address of the client. For example, the client may be "10.0.0.3" if the master is "http://10.0.0.1:11311".
  4. "/jy" is the name that Jyroscope uses to refer to itself when contacting the ROS master

Subscribing

Subscribing is very easy.

The following code will subscribe to the topic named "/string" and receive std_msgs/String messages:

import com.jyroscope.annotations.*;

public class DemoSubscriber {

    @Subscribe("ros:/string")
    public void receive(StringMessage message) {
        System.out.println(message.data);
    }

}

Note that there are no interfaces to implement! There are no callbacks to register! Jyroscope simply uses the @Subscribe annotations to configure subscribers!

The subscribing method may have any name. It just needs to accept a single argument corresponding to the message type.

This code uses the "ros:" prefix. This prefix refers to the first argument declared when Jyroscope was initialized (i.e., the third line from initialization earlier):

jyroscope.addRemoteMaster("ros", "http://localhost:11311", "localhost", "/jy");

Message Definition

In the subscription example, a StringMessage class is used. That class is user-defined.

Here’s one way to implement it:

import com.jyroscope.annotations.*;

@Message("std_msgs/String")
public class StringMessage {

    public String data;

}

Note the use of a single @Message annotation. The annotation declares the ROS message type to use for the Java-to-ROS mapping.

Jyroscope supports JavaBeans-style properties as well as public fields. This code will have the same effect:

import com.jyroscope.annotations.*;

@Message("std_msgs/String")
public class StringMessage {

    private String data;

    public String getData() {
        return data;
    }

    public void setData(String value) {
        this.data = value;
    }

}

At runtime, Jyroscope compiles an internal class that translates between the ROS binary message message format and Java objects. The class is compiled and loaded so that translation is run efficiently. Reflection only happens once!

Publishing

Publishing is also achieved using annotations:

import com.jyroscope.*;
import com.jyroscope.annotations.*;

public class DemoPublisher {

    @Publish("ros:/string")
    public Publisher<StringMessage> publisher;

}

When the class is created, Jyroscope will automatically create a ROS publisher. Jyroscope will “inject” publishers into fields annotated with @Publish.

The publisher can then be used directly:

public void sendMessage() {
    StringMessage message = new StringMessage();
    message.data = "Hello, World!";
    publisher.handle(message);
}

Instantiating Nodes

Because Jyroscope performs dependency injection, nodes cannot be instantiated directly.

Instead, nodes should be created by Jyroscope using jyroscope.create:

// Set up Jyroscope (as before)
Jyroscope.addMsgSearchPath(Paths.get("msgs").toAbsolutePath().toString());
Jyroscope jyroscope = new Jyroscope();
jyroscope.addRemoteMaster("ros", "http://localhost:11311", "localhost", "/jy");

// Create a new node
jyroscope.create(DemoPublisher.class);

Nodes can be instantiated directly, with new. However, in such cases, it should be followed by jyroscope.inject:

jyroscope.inject(new DemoPublisher());

Other Features

Jyroscope incorporates a number of other features to make ROS development easy:

  1. @Init allows for custom node initialization after dependency injection (the no-arg method is called after the @Publish and @Subscribe annotations are processed).
  2. @Name can be applied to fields and JavaBean properties to override the default Java-to-ROS name conversion (e.g., @Name("frame_id") public String frameId;).
  3. @Hide can be used to hide fields or properties from Java-to-ROS translation.
  4. @Repeat can be used on a no-arg method to set up application loops. For example, a method annotated with @Repeat(interval = 2000) will be invoked every two seconds.

In addition to supporting multi-master development, Jyroscope includes an internal master. The internal master does not support the ROS protocol. It is pure Java and involves no network overhead so it is very fast. To add an internal master, use the following command:

    jyroscope.addLocalMaster("local");

The first argument is the prefix. To subscribe or publish messages on the local master, the prefix is used before a topic name: e.g., @Subscribe("local:/string").

Conclusion

Jyroscope is not a production-ready platform for ROS development. Instead, it is a technology demo. I intend it to be a suggestion for an alternative approach to ROS development.

I am currently using Jyroscope for simple integration projects between Windows and Ubuntu systems. This is because Jyroscope provides a cross-platform development environment with almost no configuration or installation.

Much work remains to make this production ready. However, I’m not sure if there is any value in doing so. I do fix bugs when I encounter them. The code still contains many “//TODO” notes.

I’ve released this code prematurely to help shape the future of rosjava and perhaps also gauge interest in Jyroscope itself. If there is genuine interest in Jyroscope, then I’ll continue working on it. I’ll clean up the code up more agressively and see what can be done to make it more robust and easier for others to contribute.

Your comments and ideas would be very welcome!

Published 12 December 2015 by Benjamin Johnston.