安卓与ROS通信的现状
因为ROS官方支持的语言绑定只有C++和Python,所以目前安卓想与ROS通信,必须借助半官方的rosjava包,而Rosjava太重了,因为它跟C++/Python一样,是一个全功能的ROS绑定,意即你可以在Java(android)平台上创建Master Node,然后其他Node(C++/Python)可以连上这个Master,进行分布式通信!这对于桌面Java或许还能接受,但对于android实在是过于复杂了。
另外,rosjava的gradle脚本太复杂,需要很深的gradle知识才能将其集成到自己的android工程,很多公司嫌麻烦直接导入rosjava的demo工程,然后将自己的代码添加进去,团队里如果有新人加入,则还要重新搭建一个rosjava环境,太麻烦了。
rosbridge协议
很多人都觉得移动平台or嵌入式系统要实现跟ROS进行分布式通信成本太高昂,大家寻思用C/S架构可能更符合移动平台的需求,于是提出了rosbridge协议,该协议的基本思想是将节点间的分布式通信,改成client节点与一个代理节点进行C/S通信,然后代理节点再将请求转发给server节点,这样移动端就不需要实现整个ROS平台,只需要跟代理节点通信即可。
这是一篇比较rosjava跟rosbridge优劣的pdf,简单来说,就是移动平台以牺牲做server节点的代价,换来了轻量级ROS交互的能力。不过特殊情况下rosjava还是有用的,比如机器人的底盘调用机械臂的service,如果机械臂只支持rosbridge,则调用不可行——不过这种情况通过pub/sub应该也能解决。
ROSBridgeClient库
要让android能收发rosbridge消息,首先要支持WebSocket这种特殊的传输通道才能实现android接收ROS端publish(推送)过来的消息,目前实现WebSocket的大部分集中在桌面Java,比如Jetty、Netty等,其中Jetty因为用到了某些Dalvik VM不支持的java类而导致不能在android上使用。另外android自身的webview对WebSocket的支持较晚,不能保证全机型覆盖,所以Java-WebSocket这个用java.nio包里的类实现的WebSocket就脱颖而出。
有了传输通道,剩下的就是怎么组包发送了,这个库不仅要将发送的Java类型转换成语言无关的ROS消息类型(反之亦然),还要将ROS操作(订阅、发布、调用service、广播topic等)转换成rosbridge里规定的json串,在评估了java_rosbridge库和ROSBridgeClient库后,我选择了后者,因为前者虽然更小巧,但是用的jetty来实现WebSocket通信,没法跑在android上。
添加std_msgs包里的消息类型
在试用ROSBridgeClient库的过程中,我发现作者连std_msgs里的消息类型——例如String——都没有实现,却而代之的,是一个 精巧的注解加反射机制实现的meta message类型,要扩展很简单,见我fork出来的repo 。
在摸索std_msgs的过程中,我弄明白一个机制:ROS的内置类型其实并不是实际存在的,它必须对应到具体语言的内置类型,所以为了跨语言通信,所有Message只能使用Wrapper类型,这就解释了为什么std_msgs包里一堆wrapper了,因为每个msg文件里的内置类型(比如string),都是不存在的,必须对应到Java的String,或Python的str、或C++的std::string,唯独不能对应ROS的string,因为ROS不是一门语言。
上文为DarrenChan陈驰博文所写,我们发现相较于ROSjava而言ROSBridge更易于开发,同时适用于JAVA、C#等语言具有更好的扩展性。同时能够跨平台设计实现。
ROSBridge是一个可用于非ROS系统和ROS系统进行通信的功能包,非ROS的系统使用指定数据内容的基于JSON(或BSON)格式的网络请求(ROSBridge支持TCP、UDP、WebSocket三种网络通讯方式)来调用ROS的功能,既然非ROS系统能通过ROSBridge基于TCP/UDP/WebSocket与机器人上的ROS进行交互,那就是实现了外部系统和机器人上的ROS的解耦合,也就是外部系统完全可以与机器人使用不同的开发语言不同的OS平台。
ROS是用在机器人本体上的(前面说过,ROS只是个中间件,跑在机器人内安装的Ubuntu之类Linux上,而且两者的版本安装要匹配,要按ROS官网上指定的来),而管理和控制机器人的机器人后端(或者叫服务端)控制系统(或者叫平台)通常是使用Java,C#之类的语言开发的。
ROSBridge就非常适合用于两者之间的交互通讯实现机器人后端控制系统对机器人的控制 (这里的说的控制不仅仅包含机器人遥控器对机器人的那种运动和语音之类的手段控制功能,而且还包括机器人后端控制系统向机器人下发配置数据、地图与导航路径数据、任务数据、特殊动作实时控制、软件更新等等数据以及机器人向机器人后端控制系统上传音视频设备实时获取的音视频数据、地图坐标数据、各种传感器收集的数据、告警数据、任务执行相关数据等等) 。
另外,我觉得机器人之间也适合使用ROSBridge来进行通讯,无论另外的机器人使用的是Ubuntu+ROS还是纯Android的还是三者都使用了(三者都使用了的情况常见于有人机交互界面采用上下位机两块板子的机器人)。 当然,如果融资多、人力资源充裕、工期不紧张并且有特殊大数据传输要求,完全可以不使用ROSBridge,自己用C++开发一个TCP/WebSocket Sever部署在Linux上,作为机器人本体上的ROS与机器人后端控制系统之间的通讯的桥梁。如果创业公司需要快速出产品、人手又少、没多少钱烧,那么基于现有的ROSBridge来实现机器人后端控制系统与机器人之间的通讯还是比较好的选择。
ROS默认安装没有包含ROSBridge,需要执行下面命令来安装它:
sudo apt-get install ros-<rosdistro>-rosbridge-suite
比如:
sudo apt-get install ros-kinetic-rosbridge-suite
(或者也可以从 https://github.com/RobotWebTools/rosbridge_suite 获取到完整的源码包,从下载的包可以看到其实源码都是用python写的脚本,另外是一些msg和srv的定义文件,然后使用catkin编译和安装)
安装完后,可以在/opt/ros/kinetic/lib/python2.7/dist-packages/下看到两个重要的包rosbridge_server和rosbridge_library。
ROSBridge内容包括通讯协议规范定义rosbridge v2.0 Protocol Specification和代码实现。
之后加入ROS环境
source /opt/ros/indigo/setup.bash
创建工作空间和下载代码
mkdir -p ~/catkin_ws/src cd ~/catkin_ws/src git clone https://github.com/RobotWebTools/rosbridge_suite.git
编译
cd ~/catkin_ws catkin_make
加入工作环境
cd ~/catkin_ws source devel/setup.bash
rosbridge_server、rosbridge_library、rosapi就是ROSBridge的代码实现部分,三部分的功能分别是:
rosbridge_server
说明:
介绍rosbridge_server提供的多种服务器模式及实现
代码目录结构:
├── CHANGELOG.rst ├── CMakeLists.txt ├── launch #启动 │ ├── rosbridge_tcp.launch #启动TCP服务器,调用rosbridge_tcp.py │ ├── rosbridge_udp.launch #启动UDP服务器,调用rosbridge_udp.py │ └── rosbridge_websocket.launch #启动WEBSOCKET服务器,调用rosbridge_websocket.py ├── package.xml ├── scripts │ ├── rosbridge_tcp -> ./rosbridge_tcp.py │ ├── rosbridge_tcp.py #ros TCP节点, 利用SocketServer实现TCP通讯 │ ├── rosbridge_udp -> rosbridge_udp.py │ ├── rosbridge_udp.py #ros UPD节点,利用reactor.listenUDP实现UDP通讯 │ ├── rosbridge_websocket -> rosbridge_websocket.py │ └── rosbridge_websocket.py #ros websocket节点,利用tornado.web实现websocket通讯 ├── setup.py └── src ├── backports │ ├── __init__.py │ └── ssl_match_hostname │ ├── __init__.py │ ├── LICENSE.txt │ └── README.txt ├── rosbridge_server #服务器处理 │ ├── __init__.py │ ├── tcp_handler.py #实现RosbridgeTcpSocket类,用于处理TCP消息 │ ├── udp_handler.py #实现RosbridgeUdpFactory类和RosbridgeUdpSocket类,用于处理UDP消息 │ ├── websocket_handler.py #实现RosbridgeWebSocket类,用于处理websocket消息 └── tornado #Tornado 是 FriendFeed 使用的可扩展的非阻塞式 web 服务器及其相关工具的开源版本 ├── auth.py ├── autoreload.py ├── concurrent.py ├── curl_httpclient.py ├── escape.py ├── gen.py ├── http1connection.py ├── httpclient.py ├── httpserver.py ├── httputil.py ├── __init__.py ├── ioloop.py ├── iostream.py ├── locale.py ├── log.py ├── log.pyc ├── netutil.py ├── options.py ├── platform │ ├── asyncio.py │ ├── auto.py │ ├── auto.pyc │ ├── caresresolver.py │ ├── common.py │ ├── epoll.py │ ├── epoll.pyc │ ├── __init__.py │ ├── interface.py │ ├── kqueue.py │ ├── posix.py │ ├── select.py │ ├── twisted.py │ └── windows.py ├── process.py ├── simple_httpclient.py ├── speedups.c ├── stack_context.py ├── tcpclient.py ├── tcpserver.py ├── template.py ├── test │ ├── auth_test.py │ ├── concurrent_test.py │ ├── csv_translations │ │ └── fr_FR.csv │ ├── curl_httpclient_test.py │ ├── escape_test.py │ ├── gen_test.py │ ├── gettext_translations │ │ └── fr_FR │ │ └── LC_MESSAGES │ │ ├── tornado_test.mo │ │ └── tornado_test.po │ ├── httpclient_test.py │ ├── httpserver_test.py │ ├── httputil_test.py │ ├── import_test.py │ ├── __init__.py │ ├── ioloop_test.py │ ├── iostream_test.py │ ├── locale_test.py │ ├── log_test.py │ ├── __main__.py │ ├── netutil_test.py │ ├── options_test.cfg │ ├── options_test.py │ ├── process_test.py │ ├── README │ ├── resolve_test_helper.py │ ├── runtests.py │ ├── simple_httpclient_test.py │ ├── stack_context_test.py │ ├── static │ │ ├── dir │ │ │ └── index.html │ │ └── robots.txt │ ├── tcpclient_test.py │ ├── templates │ │ └── utf8.html │ ├── template_test.py │ ├── test.crt │ ├── testing_test.py │ ├── test.key │ ├── twisted_test.py │ ├── util.py │ ├── util_test.py │ ├── websocket_test.py │ ├── web_test.py │ └── wsgi_test.py ├── testing.py ├── util.py ├── web.py ├── websocket.py └── wsgi.py
重要文件说明:
SocketServer
- SocketServer内部使用 IO多路复用 以及 “多线程” 和 “多进程” ,从而实现并发处理多个客户端请求的Socket服务端
- Twisted/reactor
- Twisted 是用Python实现的基于事件驱动的网络引擎框架
tornado
- Tornado是FriendFeed使用的可扩展的非阻塞式 web 服务器及其相关工具的开源版本
rosbridge_library
说明:
介绍rosbridge_library各文件及功能
代码目录结构:
├── CHANGELOG.rst ├── CMakeLists.txt ├── msg #定义消息 │ ├── Num.msg #定义数字消息 │ ├── TestChar.msg #定义字符消息 │ ├── TestDurationArray.msg #定义Duration数组消息 │ ├── TestHeaderArray.msg #定义Header数组消息 │ ├── TestHeader.msg #定义Header消息 │ ├── TestHeaderTwo.msg #定义Header消息 │ ├── TestTimeArray.msg #定义Time数组消息 │ ├── TestUInt8FixedSizeArray16.msg #定义Unit8[16]消息 │ └── TestUInt8.msg #定义Unit8[]消息 ├── package.xml ├── setup.py #python安装脚本 ├── src │ └── rosbridge_library #核心库 │ ├── capabilities #功能包 │ │ ├── advertise.py #执行话题的广播和取消 │ │ ├── advertise_service.py #执行服务的广播 │ │ ├── call_service.py #调用服务 │ │ ├── defragmentation.py #对消息内容解析 │ │ ├── fragmentation.py #对消息内容封装 │ │ ├── __init__.py #声明为Python库 │ │ ├── publish.py #发布话题 │ │ ├── service_response.py #服务反馈处理 │ │ ├── subscribe.py #订阅话题 │ │ └── unadvertise_service.py #取消服务广播 │ ├── capability.py │ ├── __init__.py │ ├── internal #内部功能实现 │ │ ├── exceptions.py #异常处理 │ │ ├── __init__.py │ │ ├── message_conversion.py #消息转换 │ │ ├── pngcompression.py #图像压缩 │ │ ├── publishers.py #跟踪使用特定发布者的客户端。提供用于发布消息和注册使用这个发布者的客户端的API │ │ ├── ros_loader.py #ros相关核心类导入 │ │ ├── services.py #为特定的服务创建服务调用者,使用start()开启独立线程,或在线程中使用run()函数。 │ │ ├── subscribers.py #管理和与ROS订阅服务器对象接口.单个订阅者在多个客户端之间共享 │ │ ├── subscription_modifiers.py #位于来自订阅的传入消息和传出消息之间发布方法,提供限制/缓冲功能.当参数改变时,处理程序可以转换到不同的类型的处理程序 │ │ └── topics.py #发布者和订阅者共同的代码和异常处理 │ ├── protocol.py #单个客户端与ROS交互的接口. │ ├── rosbridge_protocol.py #继承protocol协议,初始化rosbridge功能列表 │ └── util │ └── __init__.py ├── srv #服务定义 │ ├── AddTwoInts.srv # │ ├── SendBytes.srv #发送内容定义 │ ├── TestArrayRequest.srv │ ├── TestEmpty.srv │ ├── TestMultipleRequestFields.srv │ ├── TestMultipleResponseFields.srv │ ├── TestNestedService.srv │ ├── TestRequestAndResponse.srv │ ├── TestRequestOnly.srv │ └── TestResponseOnly.srv └── test #相关测试,包括核心功能,扩展功能,内部执行 ├── capabilities │ ├── __init__.py │ ├── test_advertise.py │ ├── test_call_service.py │ ├── test_capabilities.test │ ├── test_publish.py │ └── test_subscribe.py ├── experimental │ ├── complex_srv+tcp │ │ ├── test_non-ros_service_client_complex-srv.py │ │ └── test_non-ros_service_server_complex-srv.py │ └── fragmentation+srv+tcp │ ├── test_non-ros_service_client_fragmented.py │ └── test_non-ros_service_server_fragmented.py ├── __init__.py ├── internal │ ├── __init__.py │ ├── publishers │ │ ├── __init__.py │ │ ├── test_multi_publisher.py │ │ ├── test_multi_unregistering.py │ │ ├── test_publisher_consistency_listener.py │ │ └── test_publisher_manager.py │ ├── subscribers │ │ ├── __init__.py │ │ ├── test_multi_subscriber.py │ │ ├── test_subscriber_manager.py │ │ └── test_subscription_modifiers.py │ ├── test_compression.py │ ├── test_internal.test │ ├── test_message_conversion.py │ ├── test_ros_loader.py │ └── test_services.py └── test_all.test
重要文件说明:
rosbridge_library/src/protocol.py
- 单个客户端与ROS交互的接口.
- 协议实例的生命周期如下:
- 处理从客户端传入的消息
- 发送处理后的消息到客户端
- 客户端完成清除相关资源
rosbridge_library/test
- python的单元测试目录
- http://wiki.ros.org/unittest
- http://wiki.ros.org/rostest