DiegoAPP–机器人控制

机器人的控制主要涉及到如下几个文件

ControlApp,在连接到Robot后的主界面,执行控制任务,及执行各种功能的Fragment
RobotController,主要的机器人控制代码,在此文件中定义了要接收发送的ROS Topic,以及接收后的处理方式
Fragement,执行各主要功能的的Fragment,通过ControlApp进行切换

在这里插入图片描述

  1. 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.

  1. 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信息,并处理。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章