安卓UI线程向ROS节点发送消息的方法

想要在安卓上运行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 [email protected] (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();
    }

}


发布了159 篇原创文章 · 获赞 27 · 访问量 46万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章