Skip to content
Till Hofmann edited this page Jul 12, 2022 · 5 revisions

Refbox Communication with Java

The Referee Box (refbox) controls, monitors, and evaluates the game. In order to communicate with the refbox, for example to receive the current game state, you need to use the protobuf_comm library. This tutorial explains how to get, import and use this library to communicate with the refbox from Java.

Setup

This tutorial explains how to import the library into Eclipse. You can do this in two different ways, either add the compiled jar file to the Build Path of your project or import the source code as a new project. If you import the source code, you also have access to the examples used in the Usage tutorial. Both possibilities are explained in detail below.

Import the jar

Note: The links to the JAR files are currently dead, please use the repository instead (see below)

If you only want to use the library and not modify it for your needs, download the protobuf_comm.jar from here. There you will also find the current version of Google's protobuf-java library, which you will need as we communicate with the refbox using protobuf messages.

To import both libraries, copy them into the folder of the project in which you want to communicate with the refbox. Then open Eclipse, right-click on your project in the Package Explorer and select "Properties". In the Java Build Path properties, open the Libraries tab and click on "Add JARs..." as shown in the picture below. (If you haven't copied the libraries into the project's folder in the workspace, select "Add External JARs..." instead.)

Extend your project in the hierarchy tree, find the jars and click ok.

After closing the Properties window, you are ready to use the protobuf_comm library as explained in the Usage tutorial.

Alternatively, after copying the libs into your project's folder, extend you project in the Package Explorer, right-click on the jars and select Build Path -> Add to Build Path.

Import the source code

Clone our git repository by executing the following command on the command line:

$ git clone https://github.com/robocup-logistics/protobuf_comm_java

If you use the git repository instead of the zip file, you can stay up-to-date and always get the latest version. This can be achieved with the following command:

$ git pull

After extracting the zip or cloning our repository, open Eclipse and choose File -> Import... causing the following window to open.

Extend the General entry, choose "Existing Projects into Workspace" and click Next. Fill in the "Select root directory" field by clicking "Browse" and choosing the folder containing the source code. Make sure that the project is selected in the Projects list and click Finish. Now you should see the project in the Package Explorer.

In the org.robocup_logistics.llsf_example package you can find the two example classes used in the Usage tutorial. Read the tutorial for further instructions on using the library.

Usage

This tutorial explains how to use the protobuf_comm_java library. If you haven't downloaded it yet, please do so. See Setup for further instructions.

The protobuf_comm library helps you to connect to a running refbox and to exchange information with it by sending and receiving stream and broadcast messages. You can find the examples used in this tutorial in the org.robocup_logistics.llsf_example package if you download the library as an Eclipse project. But now let's start.

Set up the connection

Before you can start sending or receiving messages, you have to connect to a refbox. You can do this in two different ways depending on the types of messages you want to use. If you want to exchange stream messages via TCP, you have to use the ProtobufClient, otherwise if you wish to send and receive broadcast messages via UDP, create an instance of the ProtobufBroadcastPeer. As you will most likely use broadcast messages (during the game you cannot access the TCP port), we will first focus on the UDP communication. The following code shows how to set up the connection:

//Connect over UDP
ProtobufBroadcastPeer peer = new ProtobufBroadcastPeer("localhost",4445,4444);
try {
    peer.start();
} catch (IOException e) {
    e.printStackTrace();
}

The first parameter specifies the hostname or IP address of the refbox, if you run one locally on your computer, use "localhost" as shown in the example. Next, the ProtobufBroadcastPeer expects two port numbers, the send port and the receive port. To communicate with the real (remote) refbox, set both ports to 4444, but if you want to connect to a local refbox (as in the example), you need to set the send port to 4445 and edit the refbox's config.yaml as described in the Configuration tutorial. This is required, because your refbox and your Java program cannot listen on the same ports. After changing the configuration, your local refbox will listen on port 4445 and send to port 4444, so your send and receive ports have to be inverted.

After calling start(), the connection is established and you can start sending messages.

Send a message

The communication with the refbox is realized by Google protobuf messages. These messages are defined in so-called proto files, containing one or more records representing a single message. Within such a message you define name-value pairs, for example "required string team_name". After automatically generating java code from proto files, what can be done with one single command, you just have to create an instance of a message and set the values previously defined. For a detailed description of protobuf messages, visit the Developer Guide from Google.

In this tutorial we will create a BeaconSignal, this is a message containing a timestamp and a robot name, it is used to detect active robots. The timestamp itself is another protobuf message that we will create first:

NanoSecondsTimestampProvider nstp = new NanoSecondsTimestampProvider();

long ms = System.currentTimeMillis();
long ns = nstp.currentNanoSecondsTimestamp();

int sec = (int) (ms / 1000);
long nsec = ns - (ms * 1000000L);

Time t = Time.newBuilder().setSec(sec).setNsec(nsec).build();

As you can see, you need to create a builder for the protobuf message. Then you can set the fields defined in the proto file, here a timestamp devided into seconds and nanoseconds is required. Finally call build() to get an instance of your message. As Java doesn't provide a nanoseconds timestamp, the library contains the class NanoSecondsTimestampProvider that undertakes this task.

Now you can create the BeaconSignal:

BeaconSignal bs = BeaconSignal.newBuilder().setTime(t).setSeq(1).setNumber(1).setPeerName("R-1").setTeamName("YourTeam").build();

The BeaconSignal requires a timestamp (the Time message you previously created), a sequence and peer number and two names. This is how BeaconSignal.proto looks like:

message BeaconSignal {
  enum CompType {
    COMP_ID  = 2000;
    MSG_TYPE = 1;
  }

  // Local time in UTC
  required Time   time = 1;
  // Sequence number
  required uint64 seq = 2;

  // The robot's jersey number
  required uint32 number = 8;
  // The robot's team name
  required string team_name = 4;
  // The robot's name
  required string peer_name = 5;

  // Position and orientation of the
  // robot on the RCLL playing field
  optional Pose2D pose = 7;
}

In order to send your new BeaconSignal to the refbox, you have to wrap it into a ProtobufMessage. This is done to be able to recognize its type when it is received.

ProtobufMessage msg = new ProtobufMessage(2000,1,bs);

The two integers represent the component and message ID of the message used to identify the message type. They need to be the same as defined in the proto file (see above). Now you can send your message by enqueueing it into the client's send queue.

peer.enqueue(msg);

The message should be on its way now, you're done. The required java files that have been automatically generated from all our proto files are contained in the org.robocup_logistics.llsf_msgs package.

Receive a message

To receive messages, you need to perform two important steps. First of all you need to register all the message types you want the ProtobufBroadcastPeer to be able to handle. So if you want to receive RobotInfo messages containing information about the active robots in the game, you have to tell the ProtobufBroadcastPeer first:

peer.<RobotInfo>add_message(RobotInfo.class);

As you can see this is done by generics. The peer will now filter out and remember the component and message ID of the message. If a RobotInfo message arrives (always wrapped into a ProtobufMessage), the peer can identify it and create a proper instance of the actual RobotInfo message.

The second step is to create and register a handler that is responsible for deserializing the RobotInfo message. This has been done exemplarily in the class called Handler. You can find it in the org.robocup_logistics.llsf_example package in the source code. We will look at its content later on, first you need to create an instance of it and announce it to the peer:

Handler handler = new Handler();
peer.register_handler(handler);

By registering it you ensure that your handler will get all the protobuf messages it has to deserialize, because the peer will now pass its newly created instance of the RobotInfo message to your handler.

Now you can receive all the incoming protobuf messages that you registered previously and extract the containing information in the handler. This is the content of a sample handler:

public class Handler implements ProtobufMessageHandler {

    public void handle_message(ByteBuffer in_msg, GeneratedMessage msg) {

        if (msg instanceof RobotInfo) {

            byte[] array = new byte[in_msg.capacity()];
            in_msg.rewind();
            in_msg.get(array);
            RobotInfo info;

            try {
                info = RobotInfo.parseFrom(array);
                int count = info.getRobotsCount();
                System.out.println("Number of robots: " + count);
                List<Robot> robots = info.getRobotsList();
                for (int i = 0; i < robots.size(); i++) {
                    Robot robot = robots.get(i);
                    String name = robot.getName();
                    String team = robot.getTeam();
                    int number = robot.getNumber();
                    System.out.println("  robot #" + number + ": " + name + " - " + team);
                }

            } catch (InvalidProtocolBufferException e) {
                e.printStackTrace();
            }

        }
    }
}

You can see that your handler has to implement the interface ProtobufMessageHandler so that the handle_message method exists. This method is automatically called by the peer, if you have registered your handler before. You will get an instance of the protobuf message that the peer created for you, so that you can identify its type. The actual data is contained in the ByteBuffer you get.

By calling RobotInfo.parseFrom(array) you create an instance of the RobotInfo protobuf message containing the data. As easy as you could set the values when sending a message, you can read them. This is how the RobotInfo message is defined in the proto file:

message RobotInfo {
  enum CompType {
    COMP_ID  = 2002;
    MSG_TYPE = 30;
  }

  // List of all known robots
  repeated Robot robots = 1;
}

The message contains a list ("repeated") of robots that are itself protobuf messages with the fields "required string name", "required string team", "required uint32 number". With info.getRobotsCount() and info.getRobotsList() you receive the size of the list or the list itself. You can then iterate over it and call robot.getName() on each robot.

If you need another example, have a look at the complete class in the source code, it also contains the deserializing of a BeaconSignal.

Send and receive stream messages

As explained above, you need to use the ProtobufClient if you want to exchange stream messages over TCP. This passage explains how to enable the TCP connection and using the ProtobufClient.

Setting up the connection can be done like this:

ProtobufClient client = new ProtobufClient("localhost", 4444);
try {
    client.connect();
} catch (IOException e) {
    e.printStackTrace();
}

The ProtobufClient expects only one port number, which is usually also 4444. Notice that you need to call connect() on the client, not start() as you did with the peer.

Sending stream messages is the same as sending broadcast messages:

NanoSecondsTimestampProvider nstp = new NanoSecondsTimestampProvider();

long ms = System.currentTimeMillis();
long ns = nstp.currentNanoSecondsTimestamp();

int sec = (int) (ms / 1000);
long nsec = ns - (ms * 1000000L);

Time t = Time.newBuilder().setSec(sec).setNsec(nsec).build();
BeaconSignal bs = BeaconSignal.newBuilder().setTime(t).setSeq(1).setNumber(1).setPeerName("R-1").setTeamName("YourTeam").build();

ProtobufMessage msg = new ProtobufMessage(2000,1,bs);
client.enqueue(msg);

To receive stream messages, perform the same steps as for the peer:

client.<RobotInfo>add_message(RobotInfo.class);

Handler handler = new Handler();
client.register_handler(handler);

The content of the handler is exactly the same, there is no difference in deserializing stream messages or broadcast messages.

Closing the connection

When you have done all your communication, you need to close the communication channels. How to do this depends on the class you use for sending and receiving messages.

peer.stop();
client.disconnect();

Now you should be able to communicate with a refbox from Java. If you have any other questions, please don't hesitate to contact us.