环境 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
comments powered by Disqus