安卓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萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章