(!) Please ask about problems and questions regarding this tutorial on answers.ros.org. Don't forget to include in your question the link to this page, the versions of your OS & ROS, and also add appropriate tags.

Use the tango ROS node in your own app

Description: Tutorial to integrate the tango ROS node of TangoRosStreamer in your own app.

Keywords: tango, ros, node, rosjava, android, application

Tutorial Level: INTERMEDIATE

The tango ROS node is the core of the Tango Ros Streamer application. This tutorial explains how to integrate it in an another application. See the app's main webpage to see the description of available topics that can be published by the node.

The artifact corresponding to the node in its released version 1.3.1 available in rosjava_mvn_repo for easy integration in other applications.

Pre requisites

Basic knowledge of ROS in Android is required to develop applications using this node. See here for further reference. The application should be created as a catkin-android package.

Installing dependencies

Module-level Gradle script

The tango ROS node is embedded inside a nodelet. To use it on Android, you will need to include the module called tango_nodelet_manager which contains the nodelet. You will also need to include the tango_ros_common module which contains some required utility classes. To do so, add the following lines to your module-level build.gradle file:

dependencies {
    compile('org.ros.android_core:android_10:[0.3, 0.4)') {
        exclude group: 'junit'
        exclude group: 'xml-apis'
    }

    compile 'eu.intermodalics:tango_nodelet_manager:1.3.1'
    compile 'eu.intermodalics:tango_ros_common:1.3.1'
}

android {
    ...
    compileSdkVersion 25
    buildToolsVersion "25.0.2"
    ...
}

Top level Gradle script

Your top-level Gradle file should be created using the latest Kinetic tools. If you are not using ROS Kinetic, you can use the following code as your top-level build.gradle:

task wrapper(type: Wrapper) {
    gradleVersion = '2.14.1'
}

buildscript {
    apply from: "https://github.com/rosjava/android_core/raw/kinetic/buildscript.gradle"
}

apply plugin: 'catkin'

allprojects {
    // A github url provides a good standard unique name for your project
    group 'com.example'
    version = project.catkin.pkg.version
}

subprojects {
    apply plugin: 'ros-android'

    afterEvaluate { project ->
        android {
            // Exclude a few files that are duplicated across our dependencies and
            // prevent packaging Android applications.
            packagingOptions {
                exclude "META-INF/LICENSE.txt"
                exclude "META-INF/NOTICE.txt"
            }
        }
    }
}

Integrating the node to your application

Running the tango ROS node is somewhat similar to a standard rosjava nodes, but some previous steps have to be done before its execution to bind to Tango Service. We will assume that the code samples detailed below shall be inside your Activity, which should extend RosActivity, defined in Android_10.

Tango Service Binding

Create an Android ServiceConnection to handle binding to the Tango Service. The TangoInitializationHelper class provides a default service connection; it will help to override a callback that will be called after the service is bound. Here's a code snippet:

   1     // In your Android Activity
   2     ServiceConnection tangoServiceConnection = 
   3 new TangoInitializationHelper.DefaultTangoServiceConnection(
   4         new AfterConnectionCallback() {
   5             @Override
   6             public void execute() {
   7                 if (TangoInitializationHelper.isTangoServiceBound()) {
   8                 // Handle successful service connection (e.g: send log msg.).
   9                 } else {
  10                 // Handle unsuccessful service connection (e.g: log msg. & app shutdown).
  11                 }
  12             }
  13         });

Create and start node

In your init method of your Activity (which must be overridden from RosActivity as it is an abstract method), you can start the node calling a method like the following:

   1     public void startTangoRosNode(NodeMainExecutor nodeMainExecutor) {
   2 
   3         // getRosHostname() and getMasterUri() are methods provided by RosActivity,
   4         // which work in combination with `MasterChooser` Activity provided by 
   5         // Rosjava Android_10.  
   6         String hostName = getRosHostname();
   7         NodeConfiguration nodeConfiguration = NodeConfiguration.newPublic(hostName);
   8         nodeConfiguration.setMasterUri(getMasterUri());
   9 
  10         // Create and start Tango ROS Node
  11         nodeConfiguration.setNodeName(TangoNodeletManager.NODE_NAME);
  12         if (TangoInitializationHelper.loadTangoSharedLibrary() != 
  13                   TangoInitializationHelper.ARCH_ERROR && 
  14                   TangoInitializationHelper.loadTangoRosNodeSharedLibrary() != 
  15                   TangoInitializationHelper.ARCH_ERROR) {
  16             TangoNodeletManager tangoNodeletManager = new TangoNodeletManager();
  17             TangoInitializationHelper.bindTangoService(this, tangoServiceConnection);
  18             if (TangoInitializationHelper.isTangoVersionOk()) {
  19                 nodeMainExecutor.execute(tangoNodeletManager, nodeConfiguration);
  20             } else {
  21                 // Handle Tango version error
  22             }
  23         } else {
  24             // Handle Tango lib error        
  25         } 
  26     }

If you would like to remap topics/parameters/services of the tango ROS node, you need to use the Remapping Arguments. To do so, replace the line

   1 TangoNodeletManager tangoNodeletManager = new TangoNodeletManager();

by the following lines:

   1 String[] remappingArguments = {"/tango/laser_scan:=/laser", "/tango/point_cloud:=/pointcloud"};
   2 TangoNodeletManager tangoNodeletManager = new TangoNodeletManager(remappingArguments);

In this example the topic /tango/laser_scan is remapped to /laser and the topic /tango/point_cloud is remapped to /pointcloud.

Start streaming Tango data

Now that the tango ROS node is running, we need to explicitly connect to the Tango Service to be able to stream Tango data. To do so, we need to make a service call to the /tango/connect service using the TangoServiceClientNode. First, your activity has to declare an instance of TangoServiceClientNode and implements its CallbackListener interface:

   1     public class MyRosActivity extends RosActivity implements
   2         TangoServiceClientNode.CallbackListener {
   3         ...
   4         private TangoServiceClientNode tangoServiceClientNode;
   5         ...
   6     }

Then, in the same function as before, add the following lines.

   1     public void startTangoRosNode(NodeMainExecutor nodeMainExecutor) {
   2 
   3         ...
   4         tangoServiceClientNode = new TangoServiceClientNode();
   5         tangoServiceClientNode.setCallbackListener(this);
   6         nodeConfiguration.setNodeName(tangoServiceClientNode.getDefaultNodeName());
   7         nodeMainExecutor.execute(tangoServiceClientNode, nodeConfiguration);
   8         // Create and start Tango ROS Node
   9         nodeConfiguration.setNodeName(TangoNodeletManager.NODE_NAME);
  10         if (TangoInitializationHelper.loadTangoSharedLibrary() != 
  11                   TangoInitializationHelper.ARCH_ERROR && 
  12                   TangoInitializationHelper.loadTangoRosNodeSharedLibrary() != 
  13                   TangoInitializationHelper.ARCH_ERROR) {
  14             ...
  15             int maxTangoConnectionTry = 50;
  16             if (TangoInitializationHelper.isTangoVersionOk()) {
  17                 nodeMainExecutor.execute(tangoNodeletManager, nodeConfiguration, 
  18 new ArrayList<NodeListener>(){{
  19                     add(new DefaultNodeListener() {
  20                         @Override
  21                         public void onStart(ConnectedNode connectedNode) {
  22                             int count = 0;
  23                             while (count < maxTangoConnectionTry &&                           !tangoServiceClientNode.callTangoConnectService(TangoConnectRequest.CONNECT)) {
  24                                 try {
  25                                     count++;
  26                                     Log.e(TAG, "Trying to connect to Tango, attempt " + count);
  27                                     Thread.sleep(200);
  28                                 } catch (InterruptedException e) {
  29                                     e.printStackTrace();
  30                                 }
  31                             }
  32                             if (count >= maxTangoConnectionTry) {
  33                                 // Handle Tango connection error.
  34                             }
  35                         }
  36                     });
  37                 }});              
  38             } else {
  39                 // Handle Tango version error.
  40             }
  41         } else {
  42             // Handle tango lib error.  
  43         } 
  44     }

The key line is tangoServiceClientNode.callTangoConnectService(TangoConnectRequest.CONNECT) (line 23). This will try to call the /tango/connect service which connects to the Tango Service. Keep in mind that it returns true if the service is found, not when the call to the service is finished.

Note that we need a while loop which tries to call the service until it is found. This is necessary because when the tango ROS node starts, the /tango/connect service is not immediately available.

Finally, you will need to implement the callbacks from the CallbackListener interface of the TangoServiceClientNode. This is where you should add the code you want to execute once a specific service call is finished.

In this minimal example, only onTangoConnectServiceFinish will be called, but we'll show an example of implementation for each callback. Still in your Activity, add the following lines:

   1     public class MyRosActivity extends RosActivity implements
   2         TangoServiceClientNode.CallbackListener {
   3 
   4         ...
   5 
   6         public void onSaveMapServiceCallFinish(boolean success, final String message,
   7                                                final String mapName, 
   8                                                final String mapUuid) {
   9             if (success) {
  10                 Log.i(TAG, "Map " + mapName " has been successfully saved with uuid: "
  11 + mapUuid);
  12             } else {
  13                 Log.e(TAG, "Error while saving map: " + message);
  14             }
  15         }
  16 
  17         public void onTangoConnectServiceFinish(int response, String message) {
  18             if (response == TangoConnectResponse.TANGO_SUCCESS) {
  19                 Log.i(TAG, "Successfully connected to Tango.");
  20             } else {
  21                 Log.e(TAG, "Error while connecting to Tango: " + response 
  22 + ", message: " + message);
  23             }
  24         }
  25 
  26         public void onTangoDisconnectServiceFinish(int response, String message) {
  27             if (response == TangoConnectResponse.TANGO_SUCCESS) {
  28                 Log.i(TAG, "Successfully disconnected to Tango.");
  29             } else {
  30                 Log.e(TAG, "Error while disconnecting to Tango: " + response 
  31 + ", message: " + message);
  32             }
  33         }
  34 
  35         public void onTangoReconnectServiceFinish(int response, String message) {
  36             if (response == TangoConnectResponse.TANGO_SUCCESS) {
  37                 Log.i(TAG, "Successfully reconnected to Tango.");
  38             } else {
  39                 Log.e(TAG, "Error while reconnecting to Tango: " + response 
  40 + ", message: " + message);
  41             }
  42         }
  43 
  44         public void onGetMapUuidsFinish(List<String> mapUuids, List<String> mapNames) {
  45             if (mapUuids == null || mapNames == null) return;
  46             assert(mapUuids.size() == mapNames.size());
  47             for (int i = 0; i < mapUuids.size(); ++i) {
  48                 Log.i(TAG, "Uuid " + mapUuids.get(i) + " corresponds to map " 
  49 + mapNames.get(i));
  50             }
  51         }
  52 
  53         public void onTangoStatus(int status) {
  54             Log.i(TAG, "Current Tango status is " + status);
  55         }
  56 
  57         public void onLoadOccupancyGridServiceCallFinish(
  58             boolean success, final String message, boolean aligned, String mapUuid) {
  59             if (success) {
  60                 if (aligned) {
  61                     Log.i(TAG, "Occupancy grid successfully loaded and aligned with
  62 localization map.");
  63                 } else {
  64                     Log.w(TAG, "Occupancy grid successfully loaded but not aligned with
  65 current localization map. Please load localization map with uuid " + mapUuid);
  66                 }
  67                 Log.i(TAG, message);
  68             } else {
  69                 Log.e(TAG, "Error while loading occupancy grid: " + message);
  70             }
  71         }
  72         ...
  73     }

If you really don't need to implement all callbacks, you can extend the DefaultCallbackListener of the TangoServiceClientNode class and override only the callbacks you are interested in. In this case your activity does not need to implement the CallbackListener interface.

That's it! Your TangoNodeletManager should be up and running if everything goes well.

Additional information

You can check the available documentation in the source code of the node in Tango Ros Streamer public repository. For a quick reference, look into the node's main files:

Wiki: tango_ros_streamer/tango_ros_node (last edited 2017-10-02 16:16:16 by PerrineAguiar)