Android使用ROSBridge与ROS通信 简单使用
ROS官方只支持了C++和Python,想要在Android上与ROS通讯,我的选择是ROSBridge
环境
ROS kinetic
ROS 服务端
安装
sudo apt-get install ros-<rosdistro>-rosbridge-suite
启动
roslaunch rosbridge_server rosbridge_websocket.launch
在这之前不需要开启 roscore, 因为 rosbridge 会默认执行 roscore
Android客户端
要让 android 接收或者发送 ROS 消息的话,首先要在 Android上完成 websocket,然后按照协议解析,也很麻烦,不过又要站在巨人的肩膀上了,找到一个开源项目:ROSBridgeClient,这位同学使用 java-websocket 的包在Android上实现了 websocket 的应用,很棒。
直接把 src/com/jilk/ros
目录复制到 我的 Android 项目里,
当然会报错啦,这些代码依赖了第三方库,加在Android工程的libs 里面 引用
- eventbus.jar 用于发送从ROS接收到的消息
- java_websocket.jar 用于websocket 的实现
- json-simple-1.1.jar 用于json解析
复制到项目包里的 代码包含了一个 example .
完全可以使用
public class Example { public Example() {} public static void main(String[] args) { ROSBridgeClient client = new ROSBridgeClient("ws://162.243.238.80:9090"); client.connect(); //testTopic(client); try { testService(client); } catch (RuntimeException ex) { ex.printStackTrace(); } finally { client.disconnect(); } } public static void testService(ROSBridgeClient client) { try { Service<Empty, GetTime> timeService = new Service<Empty, GetTime>("/rosapi/get_time", Empty.class, GetTime.class, client); timeService.verify(); //System.out.println("Time (secs): " + timeService.callBlocking(new Empty()).time.sec); Service<com.jilk.ros.rosapi.message.Service, Type> serviceTypeService = new Service<com.jilk.ros.rosapi.message.Service, Type>("/rosapi/service_type", com.jilk.ros.rosapi.message.Service.class, Type.class, client); serviceTypeService.verify(); String type = serviceTypeService.callBlocking(new com.jilk.ros.rosapi.message.Service("/rosapi/service_response_details")).type; Service<Type, MessageDetails> serviceDetails = new Service<Type, MessageDetails>("/rosapi/service_response_details", Type.class, MessageDetails.class, client); serviceDetails.verify(); //serviceDetails.callBlocking(new Type(type)).print(); Topic<Log> logTopic = new Topic<Log>("/rosout", Log.class, client); logTopic.verify(); /* System.out.println("Nodes"); for (String s : client.getNodes()) System.out.println(" " + s); System.out.println("Topics"); for (String s : client.getTopics()) { System.out.println(s + ":"); client.getTopicMessageDetails(s).print(); } System.out.println("Services"); for (String s : client.getServices()) { System.out.println(s + ":"); client.getServiceRequestDetails(s).print(); System.out.println("-----------------"); client.getServiceResponseDetails(s).print(); } */ } catch (InterruptedException ex) { System.out.println("Process was interrupted."); } /* Service<Empty, Topics> topicService = new Service<Empty, Topics>("/rosapi/topics", Empty.class, Topics.class, client); Service<Topic, Type> typeService = new Service<Topic, Type>("/rosapi/topic_type", Topic.class, Type.class, client); Service<Type, MessageDetails> messageService = new Service<Type, MessageDetails>("/rosapi/message_details", Type.class, MessageDetails.class, client); try { Topics topics = topicService.callBlocking(new Empty()); for (String topicString : topics.topics) { Topic topic = new Topic(); topic.topic = topicString; Type type = typeService.callBlocking(topic); MessageDetails details = messageService.callBlocking(type); System.out.println("Topic: " + topic.topic + " Type: " + type.type); details.print(); System.out.println(); } Type type = new Type(); type.type = "time"; System.out.print("Single type check on \'time\': "); messageService.callBlocking(type).print(); } catch (InterruptedException ex) { System.out.println("testService: process was interrupted."); } */ } public static void testTopic(ROSBridgeClient client) { Topic<Clock> clockTopic = new Topic<Clock>("/clock", Clock.class, client); clockTopic.subscribe(); try {Thread.sleep(20000);} catch(InterruptedException ex) {} Clock cl = null; try { cl = clockTopic.take(); // just gets one } catch (InterruptedException ex) {} cl.print(); cl.clock.nsecs++; clockTopic.unsubscribe(); clockTopic.advertise(); clockTopic.publish(cl); clockTopic.unadvertise(); } }
example很好理解
就看了下 topic 相关的东西 testTopic,我觉得如果有很多topic就要用很多的testXXXTopic了,有点麻烦,所以我二次封装了一个 RosBridgeClientManager 来用
连接 ROS master
/** * 连接 ROS master * @param url ROS master IP * @param port ROS master 端口 * @param listener 连接状态监听器 */ public void connect(final String url, int port, final ROSClient.ConnectionStatusListener listener) { if (url != null && url.equals(mCurUrl)) { // already connected } else { mRosBridgeClient = new ROSBridgeClient("ws://" + url + ":" + port); mRosBridgeClient.connect(new ROSClient.ConnectionStatusListener() { @Override public void onConnect() { // connected successful mCurUrl = url; if (listener != null) { listener.onConnect(); } } @Override public void onDisconnect(boolean normal, String reason, int code) { // client disconnected if (listener != null) { listener.onDisconnect(normal, reason, code); } } @Override public void onError(Exception ex) { // connect error if (listener != null) { listener.onError(ex); } } }); } }
加了一个连接监听器,可以在业务层进行状态判断了。
注册topic 到 ROS
/** * 注册topic * @param topicName topic 名称 * @param data_type 消息类型 * @param <T> */ public <T> void advertiseTopic(String topicName, T data_type) { AdvertiseTopicObject<T> topic = new AdvertiseTopicObject<>(topicName, data_type, mRosBridgeClient); topic.setMessage_type(data_type); topic.advertise(); // 利用 反射获取泛型,主要是得到 T.class,我也没试 // Class <T> entityClass = (Class <T>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0]; // Topic topic = new Topic(topicName, entityClass, client); // topic.advertise(); }
原来的 advertise 已经很简单了,为什么我还要弄这个东西? 我也不知道啊
AdvertiseTopicObject.java
public class AdvertiseTopicObject<T> { private T message_type; private String topicName; private ROSBridgeClient client; public AdvertiseTopicObject(String topicName, T type, ROSBridgeClient rosBridgeClient) { this.client = rosBridgeClient; this.topicName = topicName; this.message_type = type; } public void advertise() { Topic topic = new Topic(topicName, message_type.getClass(), client); topic.advertise(); } }
发布topic 消息
/** * 发布 topic 消息 * @param topicName topic名称 * @param msg 消息 * @param <T> 消息类型 */ public <T> void publishTopic(String topicName, T msg) { PublishTopicObject<T> publishTopicObject = new PublishTopicObject<>(); publishTopicObject.setTopic(topicName); publishTopicObject.setMsg(msg); String msg_str = mGson.toJson(publishTopicObject); mRosBridgeClient.send(msg_str); }
跟上面的 AdvertiseTopicObject 保持一致,所以有了
PublishTopicObject.java
public class PublishTopicObject<T> { private String op = "publish"; private String topic; private T msg; }
订阅 topic
/** * 订阅topic * @param topicName topic 名称 * @param listener 消息监听器 */ public void subscribeTopic(String topicName, OnRosMessageListener listener { SubscribeTopicObject subscribeTopicObject = new SubscribeTopicObject(); subscribeTopicObject.setTopic(topicName); String msg_str = mGson.toJson(subscribeTopicObject); mRosBridgeClient.send(msg_str); addROSMessageListener(listener); }
同理:跟上面的 PublishTopicObject 保持一致,所以有了
SubscribeTopicObject.java
public class SubscribeTopicObject { private String op = "subscribe"; private String topic; public String getOp() { return op; } }
取消订阅 topic
/** * 取消订阅topic * @param topicName * @param listener */ public void unSubscribeTopic(String topicName, OnRosMessageListener listener) { UnSubscribeTopicObject unSubscribeTopicObject = new UnSubscribeTopicObject(); unSubscribeTopicObject.setTopic(topicName); String msg_str = mGson.toJson(unSubscribeTopicObject); mRosBridgeClient.send(msg_str); removeROSMessageListener(listener); }
还有 UnSubscribeTopicObject.java
public class UnSubscribeTopicObject { private String op = "unsubscribe"; private String topic; }
接收ROS 消息
//Receive data from ROS server, send from ROSBridgeWebSocketClient onMessage() // using eventbus ?! public void onEvent(final PublishEvent event) { Log.d("TAG", event.msg); for (int index = 0 ; index < mROSListenerList.size(); index++) { mROSListenerList.get(curIndex).onStringMessageReceive(event.name, stringData); //mROSListenerList.get(curIndex).onImageMessageReceive(event.name, imageData); } }
在 ROSBridgeWebSocketClient.java 里面找到了接收消息后发出来的代码
EventBus.getDefault().post(new PublishEvent(operation,publish.topic,content));
Emmm... 用的是 EventBus,先用起来再说。
还有就是关于服务的封装了,没写。
完成代码在 Github gist