Android使用ROSBridge與ROS通訊 簡單使用
環境 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,然後按照協議解析,也很麻煩,不過又要站在巨人的肩膀上了,找到一個ofollow,noindex" target="_blank">開源專案: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 ROSBridgeSocket/">WebSocketClient 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