想要在安卓上运行ROS节点,需要rosjava这个client library,以及构建在rosjava之上的RosActivity类
安卓上的每个ros节点,跟ubuntu上的常规节点不一样,不是进程而是线程,亦称作nodelet,所以在一个安卓app里可以运行多个ros node,构成一个node网络
node通过NodeMain接口定义,NodeMain定义了2个函数:setup和loop
前者让线程有机会做初始化操作(等价于常规节点的subscriber/publisher/server/client实例化),后者是线程的消息循环(等价于常规节点的spinOnce方法)处理
问题来了,如果UI线程要向ros节点发送消息,该怎么发呢?
我最初想到的是用安卓的MessageQueue来实现,但很快发现问题所在,Looper.loop方法 跟 NodeMain的loop方法 不能并存
因为Looper.loop()一旦运行,则线程进入Looper消息循环,永远不会执行NodeMain的loop方法
后来想出了个方法,UI线程(通过调用相应方法)修改ros节点的成员变量,ros节点在消息循环里里读取该成员变量,完成消息传递,这有2个问题:①消息类型固定②如果还有其他线程发消息,则需要考虑同步(线程安全)
为了解决问题②,一番寻找,发现BlockingQueue可以达成效果,且该类一直是线程间通讯的标准方法,自己用MessageQueue反而显得孤陋寡闻了
那怎么解决问题①呢?考虑到NodeMain的loop方法不像Looper.loop()那样是个不返回的死循环,所以,理论上应该是可以将 ros的消息循环 揉 到android的消息循环里的
比如,定义一个Tick Message,在Looper.prepare后,Looper.loop前,发送该Tick(boot自举),然后在Handler里调用NodeMain的loop,然后再向自身发送Tick,使得ros消息循环也运转起来,这样问题①和问题②都解决了,还实现了在一个线程里运行两个消息循环
上代码
首先是这个运行在android上的ros节点
package org.ros.android.android_tutorial_pubsub;
import android.content.Context;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.text.format.Time;
import android.util.Log;
import org.ros.concurrent.CancellableLoop;
import org.ros.message.MessageListener;
import org.ros.namespace.GraphName;
import org.ros.node.AbstractNodeMain;
import org.ros.node.ConnectedNode;
import org.ros.node.Node;
import org.ros.node.service.ServiceResponseBuilder;
import org.ros.node.service.ServiceServer;
import org.ros.node.topic.Subscriber;
import java.util.logging.MemoryHandler;
import rosjava_test_msgs.AddTwoIntsRequest;
import rosjava_test_msgs.AddTwoIntsResponse;
/**
* Created by haipeng on 16-3-17.
*/
public class SpeechSynNode extends AbstractNodeMain {
private String topicName;
private Context mUiCtx;
private Handler mUiHandler;
private Handler mHandler;
private static String TAG = "SpeechSynNode";
public SpeechSynNode(Context ctx, Handler handler, String topic)
{
mUiCtx = ctx;
topicName = topic;
mUiHandler = handler;
}
@Override
public GraphName getDefaultNodeName() {
return GraphName.of("speech_synthesizer");
}
@Override
public void onStart(final ConnectedNode connectedNode) {
//create subscriber
final Subscriber<std_msgs.String> subscriber =
connectedNode.newSubscriber(topicName, std_msgs.String._TYPE);
subscriber.addMessageListener(new MessageListener<std_msgs.String>() {
@Override
public void onNewMessage(std_msgs.String message) {
String text = message.getData();
Log.i(TAG, "I heard msg from ubuntu : \"" + text + "\"");
Message msg = mHandler.obtainMessage(CommonMsg.PLAY_MSG, text);
mHandler.sendMessage(msg);
}
});
connectedNode.executeCancellableLoop(new CancellableLoop() {
@Override
protected void setup() {
Looper.prepare();//allocate MQ, required by ifly
mHandler = new Handler(){
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case CommonMsg.TICK_NODE_SYN: {
try {
//process ros msg
loop();
//enter next tick
Message tick = mHandler.obtainMessage(CommonMsg.TICK_NODE_SYN);
mHandler.sendMessage(tick);
}catch(InterruptedException e){
Log.e(TAG, "node thread interrupted " + e.getMessage());
}
break;
}
case CommonMsg.PLAY_MSG:{
String text = (String)msg.obj;
playMsg(text);
break;
}
default: {
break;
}
}
}
};
//trigger the ros msg loop
Message msg = mHandler.obtainMessage(CommonMsg.TICK_NODE_SYN);
mHandler.sendMessage(msg);
//tell UI thread node's handler
msg = mUiHandler.obtainMessage(CommonMsg.HANDLER_SYN, mHandler);
mUiHandler.sendMessage(msg);
//enter Looper loop, and never comeback
Looper.loop();
}
@Override
protected void loop() throws InterruptedException {
long time = System.currentTimeMillis();
if (time % 1000 == 0){
Log.i(TAG, "ros_node run again after 1s");
}
}
});
}
private void playMsg(String text){
if(!bSpeaking) {
int code = mTts.startSpeaking(text, mTtsListener);
if (code != ErrorCode.SUCCESS) {
Log.e(TAG, "语音合成失败,错误码: " + code);
}
}else{
Log.w(TAG, "语音合成繁忙,丢弃!");
}
}
@Override
public void onShutdown(Node arg0) {
try {
// 释放连接
mTts.stopSpeaking();
mTts.destroy();
android.util.Log.i(TAG, "Synthesizer destroyed");
}catch (Throwable e) {
android.util.Log.e(TAG, "error when destroying synthesizer");
}
}
}
再看UI线程怎么给node发消息
package org.ros.android.android_tutorial_pubsub;
import android.content.pm.ActivityInfo;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import org.ros.android.RosActivity;
import org.ros.node.NodeConfiguration;
import org.ros.node.NodeMainExecutor;
import com.hankcs.hanlp.HanLP;
import java.util.List;
import java.util.Map;
/**
* @author damonkohler@google.com (Damon Kohler)
*/
public class MainActivity extends RosActivity {
private Button btnPlayMsg;
private static final String TAG = "MainRosActivity";
private Handler mSynHandler = null;
private SpeechSynNode speechSynthesizer;
private final Handler mHandler = new Handler(){
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case CommonMsg.HANDLER_SYN:{
mSynHandler = (Handler)msg.obj;
break;
}
default: {
assert false;
break;
}
}
};
};
public MainActivity() {
// The RosActivity constructor configures the notification title and ticker
// messages.
super("Robot", "Robot");
}
@SuppressWarnings("unchecked")
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
btnPlayMsg = (Button) findViewById(R.id.play_msg);
btnPlayMsg.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (null != mSynHandler){
Message msg = mSynHandler.obtainMessage(CommonMsg.PLAY_MSG, "今天是个好日子");
mSynHandler.sendMessage(msg);
}
}
});
}
@Override
protected void init(NodeMainExecutor nodeMainExecutor) {
speechSynthesizer = new SpeechSynNode(this, mHandler, "speech_syn");
// At this point, the user has already been prompted to either enter the URI
// of a master to use or to start a master locally.
// The user can easily use the selected ROS Hostname in the master chooser
// activity.
NodeConfiguration nodeConfiguration = NodeConfiguration.newPublic(getRosHostname());
nodeConfiguration.setMasterUri(getMasterUri());
nodeMainExecutor.execute(speechSynthesizer, nodeConfiguration);
}
@Override
protected void onDestroy(){
super.onDestroy();
Log.i(TAG, "onDestroy");
nodeMainExecutorService.shutdownNodeMain(speechSynthesizer);
nodeMainExecutorService.forceShutdown();
}
}