Getting started¶
Before diving into ROS enabled Android application development, you should be familiar with rosjava and Android application development in general.
Creating a new Android application¶
Note
This is still a work in progress. There are many obvious limitations and the process will be improved in the near future.
Currently, the easiest way to create a new application is to create your own package in the android_core stack by copying one of the tutorial packages (e.g. android_tutorial_pubsub).
roscd android_core
cp -a android_tutorial_pubsub my_package
After that, modify android_core/settings.gradle to include your new package.
rosed android_core/settings.gradle
./gradlew my_package:clean my_package:debug
At this point, you may interact with your Android projects as described in the Android documentation. Please start there if the following quick start instructions are insufficient for you.
Use Apache Ant to install your new Android application:
roscd my_package
ant installd
You can also use ant to build the application. However, if you add, remove, or modify a dependency in the build.gradle file, you will need to execute the gradle wrapper as described above in order to update the Android application’s external dependencies (located in the my_package/libs directory).
Note
You may also build and run your application from Eclipse. For more information, see Building android_core.
Using RosActivity¶
The RosActivity class is the base class for all of your ROS enabled Android applications. Let’s consider the following example from the android_tutorial_pubsub package. In this example, we create a Publisher and a Subscriber that will exchange “Hello, World” messages.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | package org.ros.android.android_tutorial_pubsub;
import android.os.Bundle;
import org.ros.android.MessageCallable;
import org.ros.android.RosActivity;
import org.ros.android.view.RosTextView;
import org.ros.node.NodeConfiguration;
import org.ros.node.NodeMainExecutor;
import org.ros.rosjava_tutorial_pubsub.Talker;
/**
* @author damonkohler@google.com (Damon Kohler)
*/
public class MainActivity extends RosActivity {
private RosTextView<std_msgs.String> rosTextView;
private Talker talker;
public MainActivity() {
// The RosActivity constructor configures the notification title and ticker
// messages.
super("Pubsub Tutorial", "Pubsub Tutorial");
}
@SuppressWarnings("unchecked")
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
rosTextView = (RosTextView<std_msgs.String>) findViewById(R.id.text);
rosTextView.setTopicName("chatter");
rosTextView.setMessageType(std_msgs.String._TYPE);
rosTextView.setMessageToStringCallable(new MessageCallable<String, std_msgs.String>() {
@Override
public String call(std_msgs.String message) {
return message.getData();
}
});
}
@Override
protected void init(NodeMainExecutor nodeMainExecutor) {
talker = new Talker();
NodeConfiguration nodeConfiguration = NodeConfiguration.newPrivate();
// At this point, the user has already been prompted to either enter the URI
// of a master to use or to start a master locally.
nodeConfiguration.setMasterUri(getMasterUri());
nodeMainExecutor.execute(talker, nodeConfiguration);
// The RosTextView is also a NodeMain that must be executed in order to
// start displaying incoming messages.
nodeMainExecutor.execute(rosTextView, nodeConfiguration);
}
}
|
On line 14, we extend RosActivity. When our activity starts, the RosActivity super class will:
- start the NodeMainExecutorService as a service in the foreground,
- launch the MasterChooser activity to prompt the user to configure a master URI,
- and display an ongoing notification informing the user that ROS nodes are running in the background.
On line 22 we call the super constructor with two strings that become the title and ticker message of an Android notification. The user may tap on the notification to shut down all ROS nodes associated with the application.
Lines 28-30 should look familiar to Android developers. We load the activity layout and get a reference to our RosTextView (more on that later).
On line 42 we define the abstract method RosActivity.init. This is where we kick off our NodeMains and other business logic.
And that’s it. RosActivity handles the rest of the application’s lifecycle management including:
- acquiring and releasing WakeLocks and WifiLocks,
- binding and unbinding the NodeMainExecutorService,
- and shutting down NodeMains when the application exits.
Nodes and Views¶
The android_core stack provides a number of Android Views which implement NodeMain. For example, let’s look at the implementation of RosTextView. The intent of this view is to display the textual representation of published messages.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 | package org.ros.android.view;
import android.content.Context;
import android.util.AttributeSet;
import android.widget.TextView;
import org.ros.android.MessageCallable;
import org.ros.message.MessageListener;
import org.ros.namespace.GraphName;
import org.ros.node.ConnectedNode;
import org.ros.node.Node;
import org.ros.node.NodeMain;
import org.ros.node.topic.Subscriber;
/**
* @author damonkohler@google.com (Damon Kohler)
*/
public class RosTextView<T> extends TextView implements NodeMain {
private String topicName;
private String messageType;
public void setTopicName(String topicName) {
this.topicName = topicName;
}
public void setMessageType(String messageType) {
this.messageType = messageType;
}
public void setMessageToStringCallable(MessageCallable<String, T> callable) {
this.callable = callable;
}
@Override
public GraphName getDefaultNodeName() {
return new GraphName("android_gingerbread/ros_text_view");
}
@Override
public void onStart(ConnectedNode connectedNode) {
Subscriber<T> subscriber = connectedNode.newSubscriber(topicName, messageType);
subscriber.addMessageListener(new MessageListener<T>() {
@Override
public void onNewMessage(final T message) {
if (callable != null) {
post(new Runnable() {
@Override
public void run() {
setText(callable.call(message));
}
});
} else {
post(new Runnable() {
@Override
public void run() {
setText(message.toString());
}
});
}
postInvalidate();
}
});
}
@Override
public void onShutdown(Node node) {
}
@Override
public void onShutdownComplete(Node node) {
}
@Override
public void onError(Node node, Throwable throwable) {
}
}
|
The view is configured with a topic name, message type, and a MessageCallable. On line 40, in the NodeMain.onStart method, we create a new Subscriber for the configured topic and message type.
When a new message arrives, we either use the configured callable to transform the incoming message to a string (line 49), or we use the default toString() method if no callable was configured (line 56). We then set the text of the view to the string representation of the incoming message.
As with any other NodeMain, the RosTextView must be executed by the NodeMainExecutor. In the Using RosActivity example, we execute it in RosActivity.init and use the it to display incoming messages from the Talker node.