ROS-action通信-发送坐标控制小海龟

实验现象

运行小海龟程序后,向actionserver发送一个坐标,程序会将小海龟移动到该坐标。在移动期间还会不断向客户端发送实时位置。

在这里插入图片描述

实验步骤

1.创建工作空间

2.VSCODE集成ROS

笔者使用了VSCode来编写代码,VSCode集成ROS的过程我不自己写,大家可以看看这位博主的博客,写的很好:【ROS】VSCODE + ROS 配置方法(保姆级教程,总结了多篇)

3.创建软件包

在创建好工作空间之后,在src右键选择Create Catkin Package,输入包名后,导入依赖的库:

roscpp rospy std_msgs turtlesim geometry_msgs actionlib actionlib_msgs

4.创建action文件

分析一下程序的过程。服务端订阅话题/turtle1/pose获得小海龟的位姿信息,当客户端发出目的坐标时,服务端根据小海龟的当前位姿信息与目的坐标之间的相对位置,向话题/turtle1/cmd_vel发送控制信息控制小海龟的移动。

4.1 turtlesim/Pose

上文说到,服务端通过话题/turtle1/pose得到小海龟的当前位姿信息,而使用命令:

rostopic info /turtle1/pose

可以知道,这个话题发出的数据类型是turtlesim/Pose,该数据类型有以下五个成员变量:

x:横坐标
y:纵坐标
theta:方向(右边为0,逆时针到左边从0到3.1,顺时针到左边0到-3.1)
linear_velocity:线速度
angular_velocity:角速度

服务器从话题/turtle1/pose拿到一个turtlesim/Pose类型的变量之后,通过x,y,theta三个属性可以知道当前小海龟的位置与朝向。

4.2 geometry_msgs/Twist

小海龟结点根据话题/turtle1/cmd_vel发出的数据,不断设置小海龟的朝向与速度。

而/turtle1/cmd_vel使用的数据类型是geometry_msgs/Twist,有六个成员变量:

geometry_msgs/Vector3 linear
  float64 x
  float64 y//不使用
  float64 z//不使用
geometry_msgs/Vector3 angular
  float64 x//不使用
  float64 y//不使用
  float64 z

前面服务端得到了turtlesim/Pose的当前位置与目的位置,可以计算出合适的朝向与线速度角速度,并将这三条数据封装成geometry_msgs/Twist类型的数据,发送给话题/turtle1/cmd_vel。小海龟结点从这个话题得到数据后,就开始控制小海龟了。

简单的例子

小海龟结点从/turtle1/pose得到一个Pose变量,里面的x为5,y为5,theta为0,表示当前位置为(5,5),朝向为右。

然后客户端发送两个数据9,9,表示目的坐标(9,9)。

服务端拿到目的坐标后,先横向移动,再纵向移动。

横向移动时,先给话题/turtle1/cmd_vel发送0线速度,1角速度,直到小海龟朝向为右;然后根据当前横坐标与目的横坐标的相对位置给话题/turtle1/cmd_vel发送1(或者-1)线速度,0角速度来左右移动。等到横坐标差不多一致后,再给话题/turtle1/cmd_vel发送0线速度,1角速度使小海龟朝上;再根据纵坐标控制向上或者向下,直到到达最终位置。

因此,我们在软件包下新建action目录,并这样创建action文件:

float64 x
float64 y
---
bool res
---
float64 x
float64 y

上面是客户端发送的数据类型,两个浮点数组成坐标;下面是实时返回的坐标信息;中间是最终移动结果。

创建好action后,修改配置文件:

 add_action_files(
   FILES
   action文件名.action
 )
 generate_messages(
   DEPENDENCIES
   actionlib_msgs#   geometry_msgs#   std_msgs
 )
 catkin_package(
#  INCLUDE_DIRS include
#  LIBRARIES hw_action
  CATKIN_DEPENDS actionlib actionlib_msgs geometry_msgs roscpp rospy std_msgs turtlesim
#  DEPENDS system_lib
)
include_directories(
# include
  ${catkin_INCLUDE_DIRS}
)

然后Ctrl+shift+b,在devel/include/包名/下面编译生成中间文件。

5.编写源文件

客户端

客户端代码如下:

#include "ros/ros.h"
#include "actionlib/client/simple_action_client.h"
#include "hw_action/turtle_controlAction.h"
typedef actionlib::SimpleActionClient<hw_action::turtle_controlAction> Client;
//处理最终结果
void done_cb(const actionlib::SimpleClientGoalState &state, const hw_action::turtle_controlResultConstPtr &result){
    if (state.state_ == state.SUCCEEDED)
    {
        ROS_INFO("最终结果:%d",result.get()->res);
    } else {
        ROS_INFO("任务失败!");
    }
}
//服务已经激活
void active_cb(){
    ROS_INFO("服务已经被激活....");
}
//处理连续反馈
void  feedback_cb(const hw_action::turtle_controlFeedbackConstPtr &feedback){
    ROS_INFO("当前:%.2f,%.2f",feedback.get()->x,feedback.get()->y);
}
int main(int argc, char* argv[]){
	//防止乱码
    setlocale(LC_ALL,"");
    //初始化
    ros::init(argc, argv, "control_client");
    ros::NodeHandle nh;
    Client client(nh,"turtle_controlAction",true);
    client.waitForServer();
    //发送目的坐标,并在回调函数中处理结果
    hw_action::turtle_controlGoal goal;
    goal.x = 3.0;
    goal.y = 3.0;
    client.sendGoal(goal,&done_cb,&active_cb,&feedback_cb);
    ros::spin();
    return 0;
}

服务端

#include "ros/ros.h"
#include "actionlib/server/simple_action_server.h"
#include "hw_action/turtle_controlAction.h"
#include "turtlesim/Pose.h"
#include "geometry_msgs/Twist.h"
#include "math.h"
#include "std_msgs/String.h"
typedef actionlib::SimpleActionServer<hw_action::turtle_controlAction> Server;
//全局变量,保存小海龟当前位姿信息
turtlesim::Pose now_pose;
//订阅turtle1/pose后的回调函数,不断更新当前位姿
void callback(const turtlesim::Pose::ConstPtr& msg_p){
    now_pose = *msg_p.get();
}
//接收到客户端信息后执行这个
void cb(const hw_action::turtle_controlGoalConstPtr &goal,Server* server){
    //定义结果信息与回馈信息
    hw_action::turtle_controlResult result;
    hw_action::turtle_controlFeedback feedback;
    //提取目的坐标
    float goal_x = goal->x;
    float goal_y = goal->y;
    //成为/turtle1/cmd_vel的发布者,这样可以发送数据让小海龟结点拿到
    ros::NodeHandle nh;
    ros::Publisher pub = nh.advertise<geometry_msgs::Twist>("/turtle1/cmd_vel",10);
    ros::Rate r(50);
    //不同的Twist变量,分别实现水平移动,转向,停止,纵向移动
    geometry_msgs::Twist data;
    geometry_msgs::Twist data2;
    geometry_msgs::Twist pause;
    geometry_msgs::Twist data4;
    if(now_pose.x!= -1){
    	//水平方向是前进还是后退
        int v = 1 * (goal_x - now_pose.x < 0 ? -1 : 1);
        data.linear.x = v;
		//竖直方向是前进还是后退
        v = 1 * (goal_y - now_pose.y < 0 ? -1 : 1);
        data4.linear.x = v;
        //旋转的变量
        data2.angular.z = 1;
        //先开始旋转,大概头朝右时就可以了
        ROS_INFO("复位");
        while(abs(now_pose.theta) > 0.02) {
        	//旋转
            pub.publish(data2);
            //获取位姿反馈
            feedback.x = now_pose.x;
            feedback.y = now_pose.y;
            server->publishFeedback(feedback);
            //有没有转到,转到了就停下来
            if(abs(now_pose.theta)<=  0.02) pub.publish(pause);
            r.sleep();
        }
        ROS_INFO("开始水平移动");
        //开始水平移动,差不多到达目的横坐标就可以了
        while(abs(goal_x - now_pose.x) > 0.05){
        	//水平移动
            pub.publish(data);
            //反馈实时信息
            feedback.x = now_pose.x;
            feedback.y = now_pose.y;
            server->publishFeedback(feedback);
            //差不多了就停下来
            if(abs(goal_x - now_pose.x) <= 0.05) {pub.publish(pause);break;}
            r.sleep();
        }
        ROS_INFO("开始转向");
        pub.publish(pause);
        while(abs(now_pose.theta - 1.55) > 0.02) {
            pub.publish(data2);
            feedback.x = now_pose.x;
            feedback.y = now_pose.y;
            server->publishFeedback(feedback);
            if(abs(now_pose.theta - 1.55)<=  0.02) pub.publish(pause);
            r.sleep();
        }
        pub.publish(pause);
        ROS_INFO("开始纵向移动");
         while(abs(goal_y - now_pose.y) > 0.05){
            pub.publish(data4);
            feedback.x = now_pose.x;
            feedback.y = now_pose.y;
            server->publishFeedback(feedback);
             if(abs(goal_y - now_pose.y) <=  0.05) pub.publish(pause);
            r.sleep();
        }
        pub.publish(pause);
         ROS_INFO("成功到达!");
        result.res = 1;
         server->setSucceeded(result);
    }
    else{
        result.res = 0;
        server->setSucceeded(result);
        ROS_INFO("获取当前位置失败!");
    }
}
int main(int argc, char* argv[]){
	//防止乱码
    setlocale(LC_ALL,"");
    //初始化
    ros::init(argc,argv,"control_server");
    ros::NodeHandle nh;
    now_pose.x = -1;
    ros::Subscriber sub = nh.subscribe<turtlesim::Pose>("/turtle1/pose",10,callback);
    //开启action服务器
    Server server(nh,"turtle_controlAction",boost::bind(&cb,_1,&server),false);
    server.start();
    ros::spin();
    return 0;
}

配置文件

 add_executable(客户端名称 
  src/客户端名称.cpp
 )
 add_executable(服务端名称
  src/服务端名称.cpp
  )
 target_link_libraries(客户端名称
   ${catkin_LIBRARIES}
 )
  target_link_libraries(服务端名称
   ${catkin_LIBRARIES}
 )

6.编译执行

先提前运行小海龟结点,再执行服务器与客户端文件,发现小海龟按照客户端程序中发出的坐标移动。

总结

本次实验学习了ROS中的action通讯,并做了一个简单的小案例,通过控制小海龟的移动来学习。

参考资料

http://www.autolabor.com.cn/book/ROSTutorials

猜你喜欢

转载自blog.csdn.net/weixin_54435584/article/details/129623110