由于ROS提供了Android的对应的开发库,我们可以方便的在Android中开发相应的ROS客户端程序。下面介绍一下在Android中使用ROS库的方法。 1. 开发环境配置Android的开发一般使用Android Studio. 其ROS相关的配置方式可以有两种。一种是在ROS环境中使用,另一种是给普通的Android App添加上ROS的依赖库。第二种方式可以在开发机器没有安装ROS的条件下进行开发。由于我使用Windows系统开发Android,所以这里使用第二种方式。 1.创建Android App项目首先在Android Studio中创建一个普通的Android App 设置好项目名称后点击Next 继续点击Next 然后点击Finish 等待项目Sync完成。 2.修改build.gradle文件项目Sync完成之后,在项目左侧的文件列表内会有两个build.gradle文件。其中一个是Project的,另一个是Module的。 首先修改Project的build.gradle文件 把文件中的
- buildscript {
- repositories {
- jcenter()
- }
- dependencies {
- classpath 'com.android.tools.build:gradle:2.2.3'
- // NOTE: Do not place your application dependencies here; they belong
- // in the individual module build.gradle files
- }
- }
复制代码
修改为
- buildscript {
- apply from: "https://github.com/rosjava/android_core/raw/kinetic/buildscript.gradle"
- }
复制代码
然后在文件中添加
- 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"
- }
- }
- }
- }
复制代码
然后修改Module的build.gradle,在dependencies 中添加ros依赖
- ...
- dependencies {
- ...
- // You now now add any rosjava dependencies, like so:
- compile 'org.ros.android_core:android_10:[0.3,0.4)'
- }
- ...
复制代码
同时把dependencies 中的 全部implementation修改为compile。注意修改时的大小写。 把文件中的compileSdkVersion版本设置为25
targetSdkVersion也设置为25
把 com.android.support:appcompat-v7:27.1.1也修改成25的版本 最后修改完成的文件如下面所示
- apply plugin: 'com.android.application'
- android {
- compileSdkVersion 25
- defaultConfig {
- applicationId "org.bwbot.rostest"
- minSdkVersion 15
- targetSdkVersion 25
- versionCode 1
- versionName "1.0"
- testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
- }
- buildTypes {
- release {
- minifyEnabled false
- proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
- }
- }
- }
- dependencies {
- compile fileTree(dir: 'libs', include: ['*.jar'])
- compile 'com.android.support:appcompat-v7:25.4.0'
- compile 'com.android.support.constraint:constraint-layout:1.1.3'
- testCompile 'junit:junit:4.12'
- androidTestCompile 'com.android.support.test:runner:1.0.2'
- androidTestCompile 'com.android.support.test.espresso:espresso-core:3.0.2'
- compile 'org.ros.android_core:android_10:[0.3,0.4)'
- }
复制代码
3.修改AndroidManifest.xml文件此时如果编译项目会出现下面的错误
- Manifest merger failed : Attribute application@icon value=(@mipmap/ic_launcher) from AndroidManifest.xml:7:9-43
- is also present at [org.ros.android_core:android_10:0.3.3] AndroidManifest.xml:19:9-36 value=(@mipmap/icon).
- Suggestion: add 'tools:replace="android:icon"' to element at AndroidManifest.xml:5:5-19:19 to override.
复制代码
此时需要修改AndroidManifest.xml文件在application项目中做如下修改
-
- tools:replace="android:icon"
- ...
复制代码
为了能够正常使用还需要给app添加网络权限。在AndroidManifest.xml文件中添加 最后的AndroidManifest.xml文件如下
-
- package="org.bwbot.rostest">
-
-
- xmlns:tools="http://schemas.android.com/tools"
- tools:replace="android:icon"
- android:allowBackup="true"
- android:icon="@mipmap/ic_launcher"
- android:label="@string/app_name"
- android:roundIcon="@mipmap/ic_launcher_round"
- android:supportsRtl="true"
- android:theme="@style/AppTheme">
-
-
-
-
-
-
-
复制代码
此时项目已经可以成功编译了。 2. 写一个简单的消息发布程序MainActivity.java内容如下
- package org.bwbot.rostest;
- import android.support.v7.app.AppCompatActivity;
- import android.os.Bundle;
- import org.ros.android.RosActivity;
- import org.ros.concurrent.CancellableLoop;
- import org.ros.namespace.GraphName;
- import org.ros.node.ConnectedNode;
- import org.ros.node.Node;
- import org.ros.node.NodeConfiguration;
- import org.ros.node.NodeMain;
- import org.ros.node.NodeMainExecutor;
- import org.ros.node.topic.Publisher;
- import java.net.URI;
- import std_msgs.String;
- public class MainActivity extends RosActivity {
- protected MainActivity() {
- super("ros_test", "ros_test", URI.create("http://192.168.0.23:11311")); // 这里是ROS_MASTER_URI
- }
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- }
- @Override
- protected void init(NodeMainExecutor nodeMainExecutor) {
- NodeConfiguration nodeConfiguration = NodeConfiguration.newPublic(getRosHostname());
- nodeConfiguration.setMasterUri(getMasterUri());
- nodeMainExecutor.execute(new NodeMain() {
- @Override
- public GraphName getDefaultNodeName() {
- return GraphName.of("ros_test");
- }
- @Override
- public void onStart(ConnectedNode connectedNode) {
- final Publisher pub = connectedNode.newPublisher("/test", String._TYPE);
- connectedNode.executeCancellableLoop(new CancellableLoop() {
- @Override
- protected void loop() throws InterruptedException {
- std_msgs.String msg = pub.newMessage();
- msg.setData("hello world");
- pub.publish(msg);
- Thread.sleep(1000);
- }
- });
- }
- @Override
- public void onShutdown(Node node) {
- }
- @Override
- public void onShutdownComplete(Node node) {
- }
- @Override
- public void onError(Node node, Throwable throwable) {
- }
- }, nodeConfiguration);
- }
- }
复制代码
编译后,在手机上运行App。然后再运行的ROS的主机上打印/test话题 可以看到消息已经成功发送出来了。 本项目已经发布到 Github 3. 注意事项compileSdkVersion 版本问题
由于在新版本的Android中,com.android.support:appcompat-v7软件包移除了一些组件。而这些组件ROS的库使用到了。所以在Android SDK > 25的版本中无法使用Android ROS. 所以我们要在配置文件中修改SDK版本。 Android模拟器网络
Android模拟器默认是有NAT转换,所以使用虚拟机是无法访问到局域网内的ROS Master的。开发时建议使用实际的手机。
4. 如何使用自定义的消息类型使用自己定义的消息需要首先生成消息的jar库文件,然后导入项目依赖。 首先安装rosjava相关的依赖包
- sudo apt-get install ros-kinetic-genjavasudo apt-get install ros-kinetic-rosjava*
复制代码
然后catkin_make具有相关消息的软件包
- catkin_make -DCATKIN_WHITELIST_PACKAGES="galileo_serial_server"
复制代码
可以看到其输出如下
- WARNING: Package name "NLlinepatrol_planner" does not follow the naming conventions. It should start with a lower case letter and only contain lower case letters, digits, underscores, and dashes.
- [ 73%] Built target galileo_serial_server_generate_messages_java
- Scanning dependencies of target galileo_serial_server_node
- [ 78%] Building CXX object galileo_serial_server/CMakeFiles/galileo_serial_server_node.dir/src/galileo_serial_server_node.cpp.o
- [ 84%] Building CXX object galileo_serial_server/CMakeFiles/galileo_serial_server_node.dir/src/galileo_serial_server.cpp.o
- [ 89%] Building CXX object galileo_serial_server/CMakeFiles/galileo_serial_server_node.dir/src/AsyncSerial.cpp.o
- [ 94%] Compiling Java code for galileo_serial_server
- [100%] Linking CXX executable /home/xiaoqiang/Documents/ros/devel/lib/galileo_serial_server/galileo_serial_server_node
- [100%] Built target galileo_serial_server_node
- warning: [options] bootstrap class path not set in conjunction with -source 1.7
- 1 warning
- Uploading: org/ros/rosjava_messages/galileo_serial_server/1.0.0/galileo_serial_server-1.0.0.jar to repository remote at file:/home/xiaoqiang/Documents/ros/devel/share/maven/
- Transferring 2K from remote
- Uploaded 2K
- [100%] Built target galileo_serial_server_generate_messages_java_gradle
- Scanning dependencies of target galileo_serial_server_generate_messages
- [100%] Built target galileo_serial_server_generate_messages
复制代码
从输出中可以看出,消息的jar包已经生成到了/home/xiaoqiang/Documents/ros/devel/share/maven/文件夹中 把此jar文件复制到Android项目中的 applibs文件夹中。 右键点击app,在弹出的菜单中点击Open Module Settings 选择dependencies页面,然后点击右侧加号 选择jar dependencies,然后选择jar文件点击确认就可以了 这样就可以在程序中使用自定义的消息了。 5. ROS Android的程序设计模式在ROS Android库中,ROS的相关操作都是异步的(通过回调的方式),比如创建节点,发布和订阅消息。这个在使用中会比较麻烦。比如我们需要实现点击一个按钮就发布一个消息的功能。就需要把这个发布消息的程序封装成一个类,然后继承CancellableLoop。在loop中发布消息。同时提供一个发布消息的方法给别人调用。
比如下面的方法
- public class GalileoCommander extends CancellableLoop {
- private Publisher pub;
- private ConnectedNode node;
- private Queue cmdList;
- public GalileoCommander(ConnectedNode connectedNode){
- node = connectedNode;
- cmdList = new LinkedBlockingQueue<>(100);
- pub = connectedNode.newPublisher("/test", GalileoNativeCmds._TYPE);
- }
- public void sendCmds(byte[] cmds){
- if(!cmdList.offer(cmds)){
- cmdList.poll();
- cmdList.offer(cmds);
- }
- }
- @Override
- protected void loop() throws InterruptedException {
- byte[] cmd = cmdList.poll();
- if(cmd == null){
- Thread.sleep(1);
- }else{
- galileo_serial_server.GalileoNativeCmds galileo_cmd = pub.newMessage();
- galileo_cmd.setLength(cmd.length);
- pub.publish(galileo_cmd);
- }
- }
- }
复制代码
使用时
- nodeMainExecutor.execute(new NodeMain() {
- @Override
- public GraphName getDefaultNodeName() {
- return GraphName.of("test_node");
- }
- @Override
- public void onStart(ConnectedNode connectedNode) {
- galileoCommander = new GalileoCommander(connectedNode);
- connectedNode.executeCancellableLoop(galileoCommander);
- }
- }, nodeConfiguration)
复制代码
这是我目前想到的比较好的模式,如果有其他更好的方法也欢迎交流。
|