在看文章之前,请确定你已经熟悉了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 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) findViewById(R.id.text);
rosTextView.setTopicName("recognizer/output");
rosTextView.setMessageType(std_msgs.String._TYPE);
rosTextView.setMessageToStringCallable(new MessageCallable() {
@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