机器人论坛
直播中

张敏

8年用户 1664经验值
私信 关注
[经验]

在Android中使用ROS

由于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
选择Empty Activity后点击Next
然后点击Finish
等待项目Sync完成。
2.修改build.gradle文件
项目Sync完成之后,在项目左侧的文件列表内会有两个build.gradle文件。其中一个是Project的,另一个是Module的。
首先修改Project的build.gradle文件
把文件中的
  1. buildscript {
  2.     repositories {
  3.         jcenter()
  4.     }
  5.     dependencies {
  6.         classpath 'com.android.tools.build:gradle:2.2.3'

  7.         // NOTE: Do not place your application dependencies here; they belong
  8.         // in the individual module build.gradle files
  9.     }
  10. }

修改为
  1. buildscript {
  2.   apply from: "https://github.com/rosjava/android_core/raw/kinetic/buildscript.gradle"
  3. }

然后在文件中添加
  1. subprojects {
  2.     apply plugin: 'ros-android'

  3.     afterEvaluate { project ->
  4.         android {
  5.             // Exclude a few files that are duplicated across our dependencies and
  6.             // prevent packaging Android applications.
  7.             packagingOptions {
  8.                 exclude "META-INF/LICENSE.txt"
  9.                 exclude "META-INF/NOTICE.txt"
  10.             }
  11.         }
  12.     }
  13. }

然后修改Module的build.gradle,在dependencies 中添加ros依赖
  1. ...
  2. dependencies {
  3.     ...
  4.     // You now now add any rosjava dependencies, like so:
  5.     compile 'org.ros.android_core:android_10:[0.3,0.4)'
  6. }
  7. ...

同时把dependencies 中的 全部implementation修改为compile。注意修改时的大小写。
把文件中的compileSdkVersion版本设置为25
targetSdkVersion也设置为25
把 com.android.support:appcompat-v7:27.1.1也修改成25的版本
最后修改完成的文件如下面所示
  1. apply plugin: 'com.android.application'

  2. android {
  3.     compileSdkVersion 25
  4.     defaultConfig {
  5.         applicationId "org.bwbot.rostest"
  6.         minSdkVersion 15
  7.         targetSdkVersion 25
  8.         versionCode 1
  9.         versionName "1.0"
  10.         testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
  11.     }
  12.     buildTypes {
  13.         release {
  14.             minifyEnabled false
  15.             proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
  16.         }
  17.     }
  18. }

  19. dependencies {
  20.     compile fileTree(dir: 'libs', include: ['*.jar'])
  21.     compile 'com.android.support:appcompat-v7:25.4.0'
  22.     compile 'com.android.support.constraint:constraint-layout:1.1.3'
  23.     testCompile 'junit:junit:4.12'
  24.     androidTestCompile 'com.android.support.test:runner:1.0.2'
  25.     androidTestCompile 'com.android.support.test.espresso:espresso-core:3.0.2'
  26.     compile 'org.ros.android_core:android_10:[0.3,0.4)'
  27. }
3.修改AndroidManifest.xml文件
此时如果编译项目会出现下面的错误
  1. Manifest merger failed : Attribute application@icon value=(@mipmap/ic_launcher) from AndroidManifest.xml:7:9-43
  2.         is also present at [org.ros.android_core:android_10:0.3.3] AndroidManifest.xml:19:9-36 value=(@mipmap/icon).
  3.         Suggestion: add 'tools:replace="android:icon"' to element at AndroidManifest.xml:5:5-19:19 to override.
此时需要修改AndroidManifest.xml文件在application项目中做如下修改
  1.         tools:replace="android:icon"
  2.         ...

为了能够正常使用还需要给app添加网络权限。在AndroidManifest.xml文件中添加

最后的AndroidManifest.xml文件如下

  1.     package="org.bwbot.rostest">
  2.    
  3.    
  4.         xmlns:tools="http://schemas.android.com/tools"
  5.         tools:replace="android:icon"
  6.         android:allowBackup="true"
  7.         android:icon="@mipmap/ic_launcher"
  8.         android:label="@string/app_name"
  9.         android:roundIcon="@mipmap/ic_launcher_round"
  10.         android:supportsRtl="true"
  11.         android:theme="@style/AppTheme">
  12.         
  13.             
  14.                

  15.                
  16.             
  17.         
  18.    

此时项目已经可以成功编译了。
2. 写一个简单的消息发布程序
MainActivity.java内容如下
  1. package org.bwbot.rostest;

  2. import android.support.v7.app.AppCompatActivity;
  3. import android.os.Bundle;

  4. import org.ros.android.RosActivity;
  5. import org.ros.concurrent.CancellableLoop;
  6. import org.ros.namespace.GraphName;
  7. import org.ros.node.ConnectedNode;
  8. import org.ros.node.Node;
  9. import org.ros.node.NodeConfiguration;
  10. import org.ros.node.NodeMain;
  11. import org.ros.node.NodeMainExecutor;
  12. import org.ros.node.topic.Publisher;

  13. import java.net.URI;

  14. import std_msgs.String;

  15. public class MainActivity extends RosActivity {

  16.     protected MainActivity() {
  17.         super("ros_test", "ros_test", URI.create("http://192.168.0.23:11311")); // 这里是ROS_MASTER_URI
  18.     }

  19.     @Override
  20.     protected void onCreate(Bundle savedInstanceState) {
  21.         super.onCreate(savedInstanceState);
  22.         setContentView(R.layout.activity_main);
  23.     }

  24.     @Override
  25.     protected void init(NodeMainExecutor nodeMainExecutor) {
  26.         NodeConfiguration nodeConfiguration = NodeConfiguration.newPublic(getRosHostname());
  27.         nodeConfiguration.setMasterUri(getMasterUri());
  28.         nodeMainExecutor.execute(new NodeMain() {
  29.             @Override
  30.             public GraphName getDefaultNodeName() {
  31.                 return GraphName.of("ros_test");
  32.             }

  33.             @Override
  34.             public void onStart(ConnectedNode connectedNode) {
  35.                 final Publisher pub =  connectedNode.newPublisher("/test", String._TYPE);
  36.                 connectedNode.executeCancellableLoop(new CancellableLoop() {
  37.                     @Override
  38.                     protected void loop() throws InterruptedException {
  39.                         std_msgs.String msg = pub.newMessage();
  40.                         msg.setData("hello world");
  41.                         pub.publish(msg);
  42.                         Thread.sleep(1000);
  43.                     }
  44.                 });
  45.             }

  46.             @Override
  47.             public void onShutdown(Node node) {

  48.             }

  49.             @Override
  50.             public void onShutdownComplete(Node node) {

  51.             }

  52.             @Override
  53.             public void onError(Node node, Throwable throwable) {

  54.             }
  55.         }, nodeConfiguration);
  56.     }
  57. }

编译后,在手机上运行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库文件,然后导入项目依赖。
下面以小强的galileo_serial_server里面的消息为例。
首先安装rosjava相关的依赖包
  1. sudo apt-get install ros-kinetic-genjavasudo apt-get install ros-kinetic-rosjava*

然后catkin_make具有相关消息的软件包
  1. catkin_make -DCATKIN_WHITELIST_PACKAGES="galileo_serial_server"

可以看到其输出如下
  1. 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.
  2. [ 73%] Built target galileo_serial_server_generate_messages_java
  3. Scanning dependencies of target galileo_serial_server_node
  4. [ 78%] Building CXX object galileo_serial_server/CMakeFiles/galileo_serial_server_node.dir/src/galileo_serial_server_node.cpp.o
  5. [ 84%] Building CXX object galileo_serial_server/CMakeFiles/galileo_serial_server_node.dir/src/galileo_serial_server.cpp.o
  6. [ 89%] Building CXX object galileo_serial_server/CMakeFiles/galileo_serial_server_node.dir/src/AsyncSerial.cpp.o
  7. [ 94%] Compiling Java code for galileo_serial_server
  8. [100%] Linking CXX executable /home/xiaoqiang/Documents/ros/devel/lib/galileo_serial_server/galileo_serial_server_node
  9. [100%] Built target galileo_serial_server_node
  10. warning: [options] bootstrap class path not set in conjunction with -source 1.7
  11. 1 warning
  12. 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/
  13. Transferring 2K from remote
  14. Uploaded 2K
  15. [100%] Built target galileo_serial_server_generate_messages_java_gradle
  16. Scanning dependencies of target galileo_serial_server_generate_messages
  17. [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中发布消息。同时提供一个发布消息的方法给别人调用。
比如下面的方法
  1. public class GalileoCommander extends CancellableLoop {

  2.     private Publisher pub;
  3.     private ConnectedNode node;
  4.     private Queue cmdList;

  5.     public GalileoCommander(ConnectedNode connectedNode){
  6.         node = connectedNode;
  7.         cmdList = new LinkedBlockingQueue<>(100);
  8.         pub = connectedNode.newPublisher("/test", GalileoNativeCmds._TYPE);
  9.     }

  10.     public void sendCmds(byte[] cmds){
  11.         if(!cmdList.offer(cmds)){
  12.             cmdList.poll();
  13.             cmdList.offer(cmds);
  14.         }
  15.     }

  16.     @Override
  17.     protected void loop() throws InterruptedException {
  18.         byte[] cmd = cmdList.poll();
  19.         if(cmd == null){
  20.             Thread.sleep(1);
  21.         }else{
  22.             galileo_serial_server.GalileoNativeCmds galileo_cmd =  pub.newMessage();
  23.             galileo_cmd.setLength(cmd.length);
  24.             pub.publish(galileo_cmd);
  25.         }
  26.     }
  27. }

使用时

  1. nodeMainExecutor.execute(new NodeMain() {
  2.     @Override
  3.     public GraphName getDefaultNodeName() {
  4.         return GraphName.of("test_node");
  5.     }

  6.     @Override
  7.     public void onStart(ConnectedNode connectedNode) {
  8.         galileoCommander = new GalileoCommander(connectedNode);
  9.         connectedNode.executeCancellableLoop(galileoCommander);
  10.     }
  11. }, nodeConfiguration)

这是我目前想到的比较好的模式,如果有其他更好的方法也欢迎交流。

更多回帖

发帖
×
20
完善资料,
赚取积分