机器人的控制主要涉及到如下几个文件
ControlApp,在连接到Robot后的主界面,执行控制任务,及执行各种功能的Fragment
RobotController,主要的机器人控制代码,在此文件中定义了要接收发送的ROS Topic,以及接收后的处理方式
Fragement,执行各主要功能的的Fragment,通过ControlApp进行切换
-
RobotController主要功能说明
进入RobotController后,表示APP已经和机器人连接,可以通过APP对机器人进行控制,对机器人状态进行监控,主要功能如下:
ODOM信息的监控,机器的线速度,和角速度的监控
GPS信息的监控,显示当前GPS定位信息
监控机器人的电池电量
接收显示,激光雷达,双摄像头,地图的信息
发送cmd_vel信息,通过遥控杆,手机重力感应来控制
高德地图,通过接收机器人发送来的GPS信息,在高德地图上实时显示
2. ControllApp代码主要逻辑说明
ControllApp继承自RosActivity,并实现了ListView.OnItemClickListener接口,声明了Fragment,Robotinfo,RobotController等变量,变量声明部分代码如下:
public class ControlApp extends RosActivity implements ListView.OnItemClickListener,
IWaypointProvider, AdapterView.OnItemSelectedListener {
/** Notification ticker for the App */
public static final String NOTIFICATION_TICKER = "Diego Robot";
/** Notification title for the App */
public static final String NOTIFICATION_TITLE = "Diego Robot";
/** The RobotInfo of the connected Robot */
public static RobotInfo ROBOT_INFO;
// Variables for managing the DrawerLayout
private String[] mFeatureTitles;
private DrawerLayout mDrawerLayout;
private ListView mDrawerList;
private ActionBarDrawerToggle mDrawerToggle;
// NodeMainExecutor encapsulating the Robot's connection
private NodeMainExecutor nodeMainExecutor;
// The NodeConfiguration for the connection
private NodeConfiguration nodeConfiguration;
// Fragment for the Joystick
private JoystickFragment joystickFragment;
// Fragment for the HUD
private HUDFragment hudFragment;
// The RobotController for managing the connection to the Robot
private RobotController controller;
// The WarningSystem used for detecting imminent collisions
private WarningSystem warningSystem;
// Stuff for managing the current fragment
private Fragment fragment = null;
FragmentManager fragmentManager;
int fragmentsCreatedCounter = 0;
private int currentfragment=0;
// For enabling/disabling the action menu
// private boolean actionMenuEnabled = true;
// The ActionBar spinner menu
private Spinner actionMenuSpinner;
// The index of the currently visible drawer
private int drawerIndex = 1;
// Log tag String
private static final String TAG = "ControlApp";
// List of waypoints
private final LinkedList waypoints;
// Specifies how close waypoints need to be to be considered touching
private static final double MINIMUM_WAYPOINT_DISTANCE = 1.0;
// Laser scan map // static so that it doesn't need to be saved/loaded every time the screen rotates
private static LaserScanMap laserScanMap;
// Bundle keys
private static final String WAYPOINT_BUNDLE_ID = "com.diegorobot.app.waypoints";
private static final String SELECTED_VIEW_NUMBER_BUNDLE_ID = "com.diegorobot.app.drawerIndex";
private static final String CONTROL_MODE_BUNDLE_ID = "com.diegorobot.app.Views.Fragments.JoystickFragment.controlMode";
// The saved instance state
private Bundle savedInstanceState;
在ControlApp变量声明部分,主要的变量有:
ROBOT_INFO, 连接的机器人信息类,从保存的机器人数据中读取,保存修改的机器人信息
mFeatureTitles加载左侧滑动菜单的内容,详细的定义在资源文件strings.xml中描述
nodeMainExecutor,连接ROSMaster的Main node,这个是RosJava连接RosMaster必须的变量,通过此变量连接Master
joystickFragment,遥控杆Fragement
hudFragment,擡头显示的Fragment,显示ODOM, GPS, BATTERY,已经急停按钮
controller,机器人的控制变量, 通过接收和发送RosTopic来控制机器
fragment,当前界面显示Fragment
onCreate()函数初始化了相关的变量,和Android APP一般的onCreate函数没有什么太大的区别,读者如有疑惑,请学习Android App的开放教程,这里不在说明
这里主要说明一下selectItem()函数,这个函数在单击事件中被调用,用于切换Fragement,代码如下:
private void selectItem(int position) {
Bundle args = new Bundle();
if (joystickFragment != null && getControlMode().ordinal() <= ControlMode.Tilt.ordinal()) {
joystickFragment.show();
}
if (hudFragment != null) {
hudFragment.show();
}
if (controller != null) {
controller.initialize();
}
fragmentManager = getFragmentManager();
setActionMenuEnabled(true);
switch (position) {
case 0:
Log.d(TAG, "Drawer item 0 selected, finishing");
fragmentsCreatedCounter = 0;
currentfragment=0;
int count = fragmentManager.getBackStackEntryCount();
for (int i = 0; i < count; ++i) {
fragmentManager.popBackStackImmediate();
}
if (controller != null) {
controller.shutdownTopics();
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... params) {
nodeMainExecutor.shutdownNodeMain(controller);
return null;
}
}.execute();
}
finish();
return;
case 1:
fragment = new OverviewFragment();
fragmentsCreatedCounter = 0;
currentfragment=1;
break;
case 2:
//fragment = new CameraViewFragment();
fragment=new CameraTopViewFragment();
fragmentsCreatedCounter = fragmentsCreatedCounter + 1;
currentfragment=2;
break;
case 3:
//fragment = new CameraViewFragment();
fragment=new Camera1TopViewFragment();
fragmentsCreatedCounter = fragmentsCreatedCounter + 1;
currentfragment=3;
break;
case 4:
//fragment = new CameraViewFragment();
fragment=new CameraDoubleViewFragment();
fragmentsCreatedCounter = fragmentsCreatedCounter + 1;
currentfragment=4;
break;
case 5:
fragment = new LaserScanFragment();
fragmentsCreatedCounter = fragmentsCreatedCounter + 1;
currentfragment=5;
break;
case 6:
fragment = new LaserScanMapFragment();
fragmentsCreatedCounter = fragmentsCreatedCounter + 1;
currentfragment=6;
break;
case 7:
fragment = new MapFragment();
fragmentsCreatedCounter = fragmentsCreatedCounter + 1;
currentfragment=7;
break;
case 8://modify by william
if (joystickFragment != null)
joystickFragment.hide();
if (hudFragment != null) {
hudFragment.hide();
boolean stop = controller.getMotionPlan() == null || !controller.getMotionPlan().isResumable();
stop &= !controller.hasPausedPlan();
hudFragment.toggleEmergencyStopUI(stop);
}
setActionMenuEnabled(false);
stopRobot(false);
fragment = new PreferencesFragment();
fragmentsCreatedCounter = fragmentsCreatedCounter + 1;
currentfragment=8;
break;
case 9:
if (joystickFragment != null)
joystickFragment.hide();
if (hudFragment != null) {
hudFragment.hide();
boolean stop = controller.getMotionPlan() == null || !controller.getMotionPlan().isResumable();
stop &= !controller.hasPausedPlan();
hudFragment.toggleEmergencyStopUI(stop);
}
setActionMenuEnabled(false);
stopRobot(false);
fragment = new AboutFragment();
fragmentsCreatedCounter = fragmentsCreatedCounter + 1;
currentfragment=9;
default:
break;
}
drawerIndex = position;
try {
//noinspection ConstantConditions
((RosFragment) fragment).initialize(nodeMainExecutor, nodeConfiguration);
} catch (Exception e) {
// Ignore
}
if (fragment != null) {
fragment.setArguments(args);
// Insert the fragment by replacing any existing fragment
fragmentManager.beginTransaction()
.replace(R.id.content_frame, fragment)
.commit();
if (fragment instanceof Savable && savedInstanceState != null)
((Savable) fragment).load(savedInstanceState);
}
// Highlight the selected item, update the title, and close the drawer
mDrawerList.setItemChecked(position, true);
mDrawerLayout.closeDrawer(mDrawerList);
setTitle(mFeatureTitles[position]);
// Refresh waypoints
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... params) {
try {
Thread.sleep(100L);
} catch (InterruptedException ignore) {}
waypointsChanged();
return null;
}
}.execute();
}
此段代码根据用户点击菜单的item来选择不同的Fragment并初始化,然后切换主界面的Fragment;后续章节将详细介绍主要的Fragment.
-
RobotController 代码主要逻辑讲解
ROS机器人的监控,主要是通过发送和接收RosTopic来实现的,所以在RobotController也主要是实现RosTopic的功能,在变量声明部分为每种要处理的topic都定义了相关的订阅变量,如下图
图中红色框为定义的Twist消息发布变量,绿色框是定义的订阅的消息变量,读者可以根据自己的需求来订阅不同的Topic,扩展APP功能
消息的处理主要在refreshTopics()函数中处理,这段代码初始化了消息订阅和发布的逻辑,由于代码太长就不全贴出来,只把主要的代码逻辑贴出来说明
// Get the correct topic names
String moveTopic = PreferenceManager.getDefaultSharedPreferences(context)
.getString(context.getString(R.string.prefs_joystick_topic_edittext_key),
context.getString(R.string.joy_topic));
String navSatTopic = PreferenceManager.getDefaultSharedPreferences(context)
.getString(context.getString(R.string.prefs_navsat_topic_edittext_key),
context.getString(R.string.navsat_topic));
String batteryTopic = PreferenceManager.getDefaultSharedPreferences(context)
.getString(context.getString(R.string.prefs_battery_topic_edittext_key),
context.getString(R.string.battery_topic));
这段代码,从配置信息中读取Topic的名称
// Refresh the Move Publisher
if (movePublisher == null
|| !moveTopic.equals(movePublisher.getTopicName().toString())) {
if (publisherTimer != null) {
publisherTimer.cancel();
}
if (movePublisher != null) {
movePublisher.shutdown();
}
// Start the move publisher
movePublisher = connectedNode.newPublisher(moveTopic, Twist._TYPE);
currentVelocityCommand = movePublisher.newMessage();
publisherTimer = new Timer();
publisherTimer.schedule(new TimerTask() {
@Override
public void run() { if (publishVelocity) {
movePublisher.publish(currentVelocityCommand);
}
}
}, 0, 80);
publishVelocity = false;
}
这段代码初始化了Twist的发布逻辑,通过定时器来不断发送当前的Twist消息
// Refresh the NavSat Subscriber
if (navSatFixSubscriber == null
|| !navSatTopic.equals(navSatFixSubscriber.getTopicName().toString())) {
if (navSatFixSubscriber != null)
navSatFixSubscriber.shutdown();
// Start the NavSatFix subscriber
navSatFixSubscriber = connectedNode.newSubscriber(navSatTopic, NavSatFix._TYPE);
navSatFixSubscriber.addMessageListener(new MessageListener() {
@Override
public void onNewMessage(NavSatFix navSatFix) {
setNavSatFix(navSatFix);
}
});
}
这段代码初始化了GPS Topic的订阅逻辑,收到GPSTopic后,在通过Android的消息机制来传递,只要需要用到GPS Topic的代码实现此消息接口,便可实施活动GPS信息,并处理。