在看文章之前,请确定你已经熟悉了ROS和rosjava,并且具备基本的android开发经验
首先,放上视频:基于SLAM的室内自主导航服务机器人
这个机器人由上网本作为核心,在ubuntu上运行ROS系统,由android端辅助控制。这里主要将android的ROSjava部分,会在以后分篇详述。
第一篇,总体介绍ROSjava-android在这个机器人设计中发挥了怎么样的作用。这个android控制程序有如下功能:英文语音识别,能在脱机状况下语音控制机器人的基本运动;中文语音识别,语音控制并可以布置机器人任务(比如去某地区寻找某物体,和人对话等);最基本的按键控制和舵机控制
整个rosjava程序由主程序PocketSphinxROS.java、处理发送命令的dispose.java、IsrActivity.java中文语音识别、R开头的几个英文语音识别文件和Talk开头的几个负责发送各种命令的文件组成。这里就先介绍下PocketSphinxROS.java,为什么取这个名字,因为最开始主要为了开发PocketSphinx英文语音,后来功能增多名字也就没改。
package rosjava.wtf.pocketsphinx.version1; import java.util.Date; import android.app.ProgressDialog; import android.content.Intent; import android.os.Bundle; import android.os.Handler; import android.util.Log; import android.view.MotionEvent; import android.view.View; import android.view.View.OnTouchListener; import android.widget.Button; import android.widget.SeekBar; import android.widget.TextView; import android.widget.SeekBar.OnSeekBarChangeListener; import org.ros.address.InetAddressFactory; import org.ros.android.MessageCallable; import org.ros.android.RosActivity; import org.ros.android.view.RosTextView; import org.ros.node.NodeConfiguration; import org.ros.node.NodeMainExecutor; import rosjava.wtf.pocketsphinx.version1.IsrActivity; import rosjava.wtf.pocketsphinx.version1.RecognitionListener; import rosjava.wtf.pocketsphinx.version1.RecognizerTask; import rosjava.wtf.pocketsphinx.version1.TalkChnString; import rosjava.wtf.pocketsphinx.version1.TalkEnString; import rosjava.wtf.pocketsphinx.version1.TalkParam; import edu.cmu.pocketsphinx.demo.R; public class PocketSphinxROS extends RosActivity implements OnTouchListener, RecognitionListener { private RosTextView<std_msgs.String> rosTextView; private TalkParam talker; private TalkEnString talkerenstring; private TalkChnString talkerchnstring; private TalkServo talkerserver; private TalkObject talkerobject; public Button forward,back,right,left,stop,btn_isr_demo,revive; public static boolean send=false,sendenstring=false,sendchnstring=false,sendservo=true,sendobject=false; public Handler handler = new Handler(){}; //servo public TextView Value01,Value02; public static SeekBar servo1; public static SeekBar servo2; public PocketSphinxROS() { // The RosActivity constructor configures the notification title and ticker // messages. super("Pubsub Tutorial", "Pubsub Tutorial"); } //*pocketsphinx*************************************************************************************** static { System.loadLibrary("pocketsphinx_jni"); } /** * Recognizer task, which runs in a worker thread. */ RecognizerTask rec; /** * Thread in which the recognizer task runs. */ Thread rec_thread; /** * Time at which current recognition started. */ Date start_date; /** * Number of seconds of speech. */ float speech_dur; /** * Are we listening? */ boolean listening; /** * Progress dialog for final recognition. */ ProgressDialog rec_dialog; /** * Performance counter view. */ static TextView performance_text; /** * Editable text view. */ public boolean onTouch(View v, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: start_date = new Date(); this.listening = true; this.rec.start(); break; case MotionEvent.ACTION_UP: if (this.listening) { Log.d(getClass().getName(), "Showing Dialog"); this.rec_dialog = ProgressDialog.show(this, "", "Recognizing speech...", true); this.rec_dialog.setCancelable(false); this.listening = false; } this.rec.stop(); break; default: ; } /* Let the button handle its own state */ return false; } //**************************************************************************************** @SuppressWarnings("unchecked") @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); rosTextView = (RosTextView<std_msgs.String>) findViewById(R.id.text); rosTextView.setTopicName("recognizer/output"); rosTextView.setMessageType(std_msgs.String._TYPE); rosTextView.setMessageToStringCallable(new MessageCallable<String, std_msgs.String>() { @Override public String call(std_msgs.String message) { return message.getData(); } }); this.rec = new RecognizerTask(); this.rec_thread = new Thread(this.rec); this.listening = false; Button voice = (Button) findViewById(R.id.Voice); voice.setOnTouchListener(this); performance_text = (TextView) findViewById(R.id.PerformanceText); this.rec.setRecognitionListener(this); this.rec_thread.start(); forward = (Button)findViewById(R.id.forward); back = (Button)findViewById(R.id.back); right = (Button)findViewById(R.id.right); left = (Button)findViewById(R.id.left); stop = (Button)findViewById(R.id.stop); revive = (Button)findViewById(R.id.revive); btn_isr_demo=(Button)findViewById(R.id.btn_isr_demo); Value01 = (TextView)findViewById(R.id.value1); Value02 = (TextView)findViewById(R.id.value2); final SeekBar servo1 = (SeekBar)findViewById(R.id.servo1); //左右 final SeekBar servo2 = (SeekBar)findViewById(R.id.servo2); //上下 servo1.setMax(180); servo1.setProgress(73); servo2.setMax(150); servo2.setProgress(145); forward.setOnTouchListener(new OnTouchListener(){ @Override public boolean onTouch(View v, MotionEvent event) { if (event.getAction() == MotionEvent.ACTION_DOWN) { dispose.control("forward"); send=true; } else if(event.getAction() == MotionEvent.ACTION_UP) { send=false; sendenstring=true; sendchnstring=true; } return false; } }); back.setOnTouchListener(new OnTouchListener(){ @Override public boolean onTouch(View v, MotionEvent event) { if (event.getAction() == MotionEvent.ACTION_DOWN) { dispose.control("back"); send=true; } else if(event.getAction() == MotionEvent.ACTION_UP) { send=false; sendenstring=true; sendchnstring=true; } return false; } }); left.setOnTouchListener(new OnTouchListener(){ @Override public boolean onTouch(View v, MotionEvent event) { if (event.getAction() == MotionEvent.ACTION_DOWN) { dispose.control("left"); send=true; } else if(event.getAction() == MotionEvent.ACTION_UP) { send=false; sendenstring=true; sendchnstring=true; } return false; } }); right.setOnTouchListener(new OnTouchListener(){ @Override public boolean onTouch(View v, MotionEvent event) { if (event.getAction() == MotionEvent.ACTION_DOWN) { dispose.control("right"); send=true; } else if(event.getAction() == MotionEvent.ACTION_UP) { send=false; sendenstring=true; sendchnstring=true; } return false; } }); stop.setOnTouchListener(new OnTouchListener(){ @Override public boolean onTouch(View v, MotionEvent event) { if (event.getAction() == MotionEvent.ACTION_DOWN) { dispose.control("stop"); send = false; } else if(event.getAction() == MotionEvent.ACTION_UP) { sendenstring=true; sendchnstring=true; } return false; } }); btn_isr_demo.setOnTouchListener(new OnTouchListener(){ @Override public boolean onTouch(View v, MotionEvent event) { if (event.getAction() == MotionEvent.ACTION_DOWN) { } else if(event.getAction() == MotionEvent.ACTION_UP) { Intent intent = new Intent(); intent.setClass(PocketSphinxROS.this,IsrActivity.class); startActivity(intent); } return false; } }); revive.setOnTouchListener(new OnTouchListener(){ @Override public boolean onTouch(View v, MotionEvent event) { if (event.getAction() == MotionEvent.ACTION_DOWN) { dispose.control("revive"); } else if(event.getAction() == MotionEvent.ACTION_UP) { send=false; servo1.setProgress(73); servo2.setProgress(145); } return false; } }); servo1.setOnSeekBarChangeListener(new OnSeekBarChangeListener(){ @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { if(progress > 150){ Value01.setText("当前角度:" + 0.6 ); TalkServo.pos[0]=0.6; } else{ Value01.setText("当前角度:" + ((float)(progress-90)/100 )); TalkServo.pos[0]=(float)(progress-90)/100; } sendservo=true; } @Override public void onStartTrackingTouch(SeekBar seekBar) { } @Override public void onStopTrackingTouch(SeekBar seekBar) { } }); servo2.setOnSeekBarChangeListener(new OnSeekBarChangeListener(){ @Override public void onProgressChanged(SeekBar seekBar, int progress,boolean fromUser) { Value02.setText("当前角度:" +((float)(progress-75)/100)); TalkServo.pos[1]=(float)(progress-75)/100; sendservo=true; } @Override public void onStartTrackingTouch(SeekBar seekBar) { } @Override public void onStopTrackingTouch(SeekBar seekBar) { } }); } //Voice Control /** Called when partial results are generated. */ public void onPartialResults(Bundle b) { final String hyp = b.getString("hyp"); handler.post(new Runnable() { public void run() { performance_text.setText(hyp); } }); } /** Called with full results are generated. */ public void onResults(Bundle b) { final String hyp = b.getString("hyp"); final PocketSphinxROS that = this; handler.post(new Runnable() { public void run() { performance_text.setText(hyp); dispose.control(hyp); sendenstring=true; send=true; Log.d(getClass().getName(), "Hiding Dialog"); that.rec_dialog.dismiss(); } }); } public void onError(int err) { } @Override protected void init(NodeMainExecutor nodeMainExecutor) { // TODO Auto-generated method stub talker = new TalkParam(); talkerenstring = new TalkEnString(); talkerchnstring = new TalkChnString(); talkerserver = new TalkServo(); talkerobject = new TalkObject(); NodeConfiguration nodeConfiguration = NodeConfiguration.newPublic(InetAddressFactory.newNonLoopback().getHostAddress(), getMasterUri()); // 这里用户已经被提示输入master的URI来开始或启动本地master nodeConfiguration.setMasterUri(getMasterUri()); nodeMainExecutor.execute(talker, nodeConfiguration); nodeMainExecutor.execute(talkerenstring, nodeConfiguration); nodeMainExecutor.execute(talkerchnstring, nodeConfiguration); nodeMainExecutor.execute(talkerserver, nodeConfiguration); nodeMainExecutor.execute(talkerobject, nodeConfiguration); // RosTextView作为显示输入信息是必须存在的 nodeMainExecutor.execute(rosTextView, nodeConfiguration); } }打开程序首先是填写masterURI,告诉rosjava连接到哪个master上。然后进入这个简陋的操作界面。其中有前后左右的基本命令按钮,2个舵机控制滑条,英文识别语音按钮和跳转到中文语音识别activity的按钮。
开发过pocketsphinx语音识别的一定很熟悉其中部分代码,这个是pocketsphinx的固定套路。中文语音我用了讯飞的引擎,联网的状况下效果很好。信息通道建立部分,我建了6个ROS节点,talker(TalkParam)负责发送控制电机的参数命令,使用geometry_msgs/Twist格式。talkerchnstring(TalkChnString)负责发送中文语音识别信息到PC的ROS,使用std_msgs.String._TYPE格式。talkerenstring(TalkEnString)负责发送英文语音识别信息。这里要说明,中文使用讯飞是需要联网才能识别的,而英文pocketsphinx是可以脱机小范围语音识别,用于应对恶劣情况(没网络)。talkerserver(TalkServo)是负责发送控制舵机的命令,使用sensor_msgs/JointState格式。talkerobject(TalkObject)负责把需要寻找的物体名发给机器人,使用std_msgs.String._TYPE格式。这里说明下,我们的机器人具有物体识别能力,通过发送物体名称给机器人,机器人就会明白自己的任务,然后去寻找该物体。
总结,以上代码是由3部分组成:RosActivity中固定格式,包括master连接和节点以及主题的建立;各个功能按键的设定;pocketsphinx英文语音。详细的请看后续文章。
by:season