전 과정에서 rviz의 2D Nav Goal 버튼을 이용해서 로봇에게 목표 지점을 주고 움직여보도록 하였습니다. 그럼 이제 사용자가 만든 노드에서 로봇에게 명령을 내리려면 어떻게 해야 할까요?

토픽 (/move_base_simple/goal) 으로 명령을 줘도 되지만, 이 경우 로봇이 잘 움직이고 있는지, 골 지점에 도착했는지에 대한 것을 확인이 어렵습니다.

/move_base_simple/goal (geometry_msgs/PoseStamped.msg)

PoseStamped.msg

	# A Pose with reference coordinate frame and timestamp
	Header header
	Pose pose

이때 사용해야 하는 것이 Actionlib입니다. move_base는 MoveBase.action 타입의 action 서버를 제공합니다. 따라서 이를 이용하면 노드에서 로봇에 명령을 줄 수 있고, 현재 동작 및 골에 도착했는지에 대한 확인이 가능합니다.

이제 navigation client 패키지를 만들어봅시다.

$ catkin create pkg mobile_manipulator_robot_navigation_client --catkin-deps move_base_msgs tf2 actionlib actionlib_msgs roscpp

src 디렉토리 아래에 nav_client_node.cpp를 만들고 아래와 같이 입력합니다.

#include <ros/ros.h>
#include <actionlib/client/simple_action_client.h>
#include <actionlib/client/terminal_state.h>
#include <move_base_msgs/MoveBaseAction.h>
#include "tf2/LinearMath/Transform.h"
#include "tf2/utils.h"

void callback_done(const actionlib::SimpleClientGoalState& state, const move_base_msgs::MoveBaseResultConstPtr& result)
{
    ROS_INFO("Robot reached to goal.");
}

void callback_active()
{
    ROS_INFO("Robot start move.");
}

void callback_feedback(const move_base_msgs::MoveBaseFeedbackConstPtr& feedback)
{
    ROS_INFO("Current robot pose is %f %f %f on /map",
                        feedback->base_position.pose.position.x, feedback->base_position.pose.position.y,
                        tf2::getYaw(feedback->base_position.pose.orientation) * 180 / 3.141592654);
}

int main(int argc, char **argv)
{
    ros::init(argc, argv, "nav_client_node");

    if (argc != 4)
    {
        ROS_INFO("usage: nav_client_node <X> <Y> <THETA(deg)>");
        return 1;
    }

    actionlib::SimpleActionClient<move_base_msgs::MoveBaseAction> ac("move_base", true);

    ROS_INFO("Waiting for action server to start.");
    ac.waitForServer();
    ROS_INFO("Action server started, sending goal.");

    move_base_msgs::MoveBaseGoal goal;
    // goal.goal.target_pose.header.stamp = ros::Time::now();
    goal.target_pose.header.frame_id = "map";
    goal.target_pose.pose.position.x = atof(argv[1]);
    goal.target_pose.pose.position.y = atof(argv[2]);
    goal.target_pose.pose.position.z = 0;

    tf2::Quaternion q;
    q.setRPY(0, 0, atof(argv[2]) * 3.141592654 / 180 );

    goal.target_pose.pose.orientation.x = q[0];
    goal.target_pose.pose.orientation.y = q[1];
    goal.target_pose.pose.orientation.z = q[2];
    goal.target_pose.pose.orientation.w = q[3];

    ac.sendGoal(goal, &callback_done, &callback_active, &callback_feedback);

    ros::spin();
    return 0;
}

코드를 하나씩 살펴보면,

#include <move_base_msgs/MoveBaseAction.h>
#include "tf2/LinearMath/Transform.h"
#include "tf2/utils.h"

MoveBase action에 대한 헤더 파일과 tf2 헤더 파일을 include 합니다. (Quaternion 계산에 필요)

actionlib::SimpleActionClient<move_base_msgs::MoveBaseAction> ac("move_base", true);

SimpleActionClient를 생성해 줍니다. action 이름은 "move_base"