【ROS理论与实践】第3讲:ROS通信编程

在这里插入图片描述

在这里插入图片描述

一、ROS项目开发流程

在这里插入图片描述

在这里插入图片描述

1.1、STEP1:创建工作空间

在这里插入图片描述

“安装空间”中的部分内容和“开发空间”重复,它里面也会放置可执行文件和环境变量。需要注意,默认的编译不会生成“安装空间”,需要单独的指令生成“安装空间”。“安装空间”的含义相当于windows中的Program Files文件夹,会把ROS编译的结果安装到“安装空间”路径下,“安装空间”的路径是可以动态指定的,相当于指定最终的可执行文件安装位置,默认是在workspace下的“安装空间”下。

在这里插入图片描述

上面有句话没写完整:要想生成“安装空间”,需要执行:catkin_make install

如果不想每次都手动生效环境变量,可以在~/.bashrc中设置环境变量,如下图:
在这里插入图片描述

在这里插入图片描述

1.2、STEP2:创建功能包

在这里插入图片描述

在这里插入图片描述

区分功能包还是文件夹,功能包一定有CMakeLists.txtpackage.xml这两个文件!

  • package.xml是通过很多的xml标签来做描述的,如描述了功能包名字、版本号、作用、维护者名字及邮箱、许可证等。下面是这个功能包依赖的其他功能包的名字,这些就是前面执行创建功能包命令时:catkin_create_pkg learning_communication rospy roscpp std_msgs std_srvs,添加的一些依赖。当然如果后面有其他依赖,可以在这个文件中手动添加!
  • CMakeLists.txt描述功能包编译的规则

二、ROS Topic通信编程

在这里插入图片描述

2.1、STEP3:创建源代码

在这里插入图片描述

这个例程中有三个角色,在ROS Master的管理下Publisher和Subscriber两个节点要完成具体数据通信,通信的内容是“Hello World”这样的字符串,通信的话题名(Topic)叫“chatter”,消息的类型是“std_msgs::String”。

在这里插入图片描述

源码如下:

/**
 * Publish "/chatter" topic, message type is string
 */

#include <sstream>
#include "ros/ros.h"
#include "std_msgs/String.h"

int main(int argc, char **argv)
{
  //ros node init
  ros::init(argc, argv, "string_publisher");

  //create node  handle
  ros::NodeHandle n;

  //create a Publisher, publish a topic named "chatter", message type is "std_msgs::String", buffer size is 1000
  ros::Publisher chatter_pub = n.advertise<std_msgs::String>("chatter", 1000);

  //set loop freq
  ros::Rate loop_rate(10);

  int count = 0;
  while(ros::ok())
  {
    //init "std_msgs::String" type message
    std_msgs::String msg;
    std::stringstream ss;
    ss << "hello world" << count;
    msg.data = ss.str();

    //publish message
    ROS_INFO("%s", msg.data.c_str());
    chatter_pub.publish(msg);

    //loop delay
    loop_rate.sleep();
    ++count;
  }
}

在这里插入图片描述

源码如下:

/**
 * Server provide a service named "print_string", message type is "std_srvs::SetBool"
 */

#include "ros/ros.h"
#include "learning_communication/PersonSrv.h"

int main(int argc, char **argv)
{
  //ros node init
  ros::init(argc, argv, "person_client");

  //create node handle
  ros::NodeHandle n;

  //create a client, service message type is "learning_communication::PersonSrv"
  ros::service::waitForService("/show_person");
  ros::ServiceClient person_client = n.serviceClient<learning_communication::PersonSrv>("/show_person");

  //create service message, message type is learning_communication::PersonSrv
  learning_communication::PersonSrv srv;
  srv.request.name = "Tom";
	srv.request.age  = 20;
	srv.request.sex  = learning_communication::PersonSrv::Request::male; // notion 

  // client pub request to service, wait service response 
  ROS_INFO("Call service to show person[name:%s, age:%d, sex:%d]", 
			 srv.request.name.c_str(), srv.request.age, srv.request.sex);

	person_client.call(srv);

  ROS_INFO("Show person result : %s", srv.response.result.c_str());

  return 0;
}

2.2、STEP4:配置编译规则

在这里插入图片描述

添加代码如下:

add_executable(string_publisher src/string_publisher.cpp)
target_link_libraries(string_publisher ${catkin_LIBRARIES})

add_executable(string_subscriber src/string_subscriber.cpp)
target_link_libraries(string_subscriber ${catkin_LIBRARIES})

2.3、STEP5:编译与运行

在这里插入图片描述

2.4、自定义数据接口

上述实例是使用ROS中定义好的数据接口,但在很多场景下ROS定义的标准数据接口是没有办法完全满足我们的需求,比如下面这个场景:
在这里插入图片描述

上述场景中Publisher向外发Person数据结构,Subscriber来订阅Person数据结构,通过的话题叫“person_info”。而该场景中的Person数据结构在ROS是没有定义的,需要自行定义,可能包括人的名字、年龄、性别等信息。下面我们就来自定义数据接口,并在Publisher和Subscriber中进行调用。

在这里插入图片描述

编译完毕后会在~/catkin_ws/devel/include/learning_communication目录下生成PersonMsg.h头文件

PersonMsg.msg源码如下,package.xmlCMakeLists.txt根据上图修改后,编译整个工程。

string name
uint8  age
uint8  sex

uint8 unknown = 0
uint8 male    = 1
uint8 female  = 2

在这里插入图片描述

源码如下:

/**
 * Publish "/person_info" topic, message type is "learning_communication::PersonMsg"
 */

#include "ros/ros.h"
#include "learning_communication/PersonMsg.h"

int main(int argc, char **argv)
{
  //ros node init
  ros::init(argc, argv, "person_publisher");

  //create node  handle
  ros::NodeHandle n;

  //create a Publisher, publish a topic named "person_info", message type is "learning_communication::PersonMsg", buffer size is 10
  ros::Publisher person_info_pub = n.advertise<learning_communication::PersonMsg>("/person_info", 10);

  //set loop freq
  ros::Rate loop_rate(1);

  while(ros::ok())
  {
    //init "learning_communication::PersonMsg" type message
    learning_communication::PersonMsg person_msg;
    person_msg.name = "Tom";
    person_msg.age  = 18;
    person_msg.sex  = learning_communication::PersonMsg::male;

    //publish message
    person_info_pub.publish(person_msg);
    ROS_INFO("Publish Person info: name%s  age:%d   sex:%d", person_msg.name.c_str(), person_msg.age, person_msg.sex);

    //loop delay
    loop_rate.sleep();
  }
}

在这里插入图片描述

源码如下:

/**
 * Subscribe "person_info" topic, message type is "learning_communication::PersonMsg"
 */

#include "ros/ros.h"
#include "learning_communication/PersonMsg.h"

void personInfoCallback(const learning_communication::PersonMsg::ConstPtr& msg)
{
  //print receive message
  ROS_INFO("Subscribe Person info: name:%s   age:%d    sex:%d", msg->name.c_str(), msg->age, msg->sex);
}

int main(int argc, char **argv)
{
  //ros node init
  ros::init(argc, argv, "person_subscriber");

  //create node handle
  ros::NodeHandle n;

  //create a Subscriber, subscribe a topic named "person_info", regisite callback function "personInfoCallback"
  ros::Subscriber person_info_sub = n.subscribe("/person_info", 10, personInfoCallback);

  //loop wait callback
  ros::spin();

  return 0;
}

在这里插入图片描述

因为我们这里动态产生了头文件,所以需要添加一个动态依赖!

添加代码如下:

add_executable(person_publisher src/person_publisher.cpp)
target_link_libraries(person_publisher ${catkin_LIBRARIES})
add_dependencies(person_publisher ${PROJECT_NAME}_gencpp)

add_executable(person_subscriber src/person_subscriber.cpp)
target_link_libraries(person_subscriber ${catkin_LIBRARIES})
add_dependencies(person_subscriber ${PROJECT_NAME}_gencpp)

在这里插入图片描述

三、ROS Service通信编程

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

想看SetBool中定义了哪些内容,可以打开一个终端后输入rossrv show std_srvs/SetBool
在这里插入图片描述
三个横杠线上面是request,三个横杠线下是response!

源码如下:

/**
 * Server provide a service named "print_string", message type is "std_srvs::SetBool"
 */

#include "ros/ros.h"
#include "std_srvs/SetBool.h"

bool print(std_srvs::SetBool::Request &req, std_srvs::SetBool::Response &res)
{
  //print string
  if(req.data)
  {
    ROS_INFO("Hello ROS!");
    res.success = true;
    res.message = "Print Successully";
  }
  else
  {
    res.success = false;
    res.message = "Print Failed";
  }

  return true;
}

int main(int argc, char **argv)
{
  //ros node init
  ros::init(argc, argv, "string_server");

  //create node handle
  ros::NodeHandle n;

  //create a Server named "print_string", regisite callback function "print"
  ros::ServiceServer service = n.advertiseService("print_string", print);

  //loop wait callback
  ROS_INFO("Ready to print hello string.");
  ros::spin();

  return 0;
}

在这里插入图片描述

源码如下:

/**
 * Server provide a service named "print_string", message type is "std_srvs::SetBool"
 */

#include "ros/ros.h"
#include "std_srvs/SetBool.h"

int main(int argc, char **argv)
{
  //ros node init
  ros::init(argc, argv, "string_client");

  //create node handle
  ros::NodeHandle n;

  //create a client, service message type is "std_srvs::SetBool"
  ros::ServiceClient client = n.serviceClient<std_srvs::SetBool>("print_string");

  //create service message, message type is std_srvs::SetBool
  std_srvs::SetBool srv;
  srv.request.data = true;

  // client pub request to service, wait service response 
  if(client.call(srv))//"call" is a blocking function, 
  {
    ROS_INFO("Response : [%s] %s", srv.response.success?"True":"False", srv.response.message.c_str());
  }
  else
  {
    ROS_ERROR("Failed to call service print_string");
    return 1;
  }

  return 0;
}

在这里插入图片描述

添加代码如下:

add_executable(string_server src/string_server.cpp)
target_link_libraries(string_server ${catkin_LIBRARIES})

add_executable(string_client src/string_client.cpp)
target_link_libraries(string_client ${catkin_LIBRARIES})

在这里插入图片描述

client每发送一次请求,只返回一次打印!注意需要先运行server,再运行client,否则会有如下错误打印:
在这里插入图片描述

3.1、自定义数据接口

在这里插入图片描述

在这里插入图片描述

编译完成后会在~/catkin_ws/devel/include/learning_communication目录下会产生PersonSrv.hPersonSrvRequest.hPersonSrvResponse.h。但是调用的时候,只需要调用PersonSrv.h即可!

PersonSrv.srv源码如下,package.xmlCMakeLists.txt根据上图修改后,编译整个工程。

string name
uint8  age
uint8  sex

uint8 unknown = 0
uint8 male    = 1
uint8 female  = 2

---
string result

在这里插入图片描述

源码如下:

/**
 * Server provide a service named "print_string", message type is "std_srvs::SetBool"
 */

#include "ros/ros.h"
#include "learning_communication/PersonSrv.h"

bool personCallback(learning_communication::PersonSrv::Request &req, learning_communication::PersonSrv::Response &res)
{
  //show request data
  ROS_INFO("Person: name:%s   age:%d   sex:%d", req.name.c_str(), req.age, req.sex);

  //set response data
  res.result = "OK";

  return true;
}

int main(int argc, char **argv)
{
  //ros node init
  ros::init(argc, argv, "person_server");

  //create node handle
  ros::NodeHandle n;

  //create a Server named "show_person", regisite callback function "personCallback"
  ros::ServiceServer person_service = n.advertiseService("/show_person", personCallback);

  //loop wait callback
  ROS_INFO("Ready to print show person information.");
  ros::spin();

  return 0;
}

在这里插入图片描述

源码如下:

/**
 * Server provide a service named "print_string", message type is "std_srvs::SetBool"
 */

#include "ros/ros.h"
#include "learning_communication/PersonSrv.h"

int main(int argc, char **argv)
{
  //ros node init
  ros::init(argc, argv, "person_client");

  //create node handle
  ros::NodeHandle n;

  //create a client, service message type is "learning_communication::PersonSrv"
  ros::service::waitForService("/show_person");
  ros::ServiceClient person_client = n.serviceClient<learning_communication::PersonSrv>("/show_person");

  //create service message, message type is learning_communication::PersonSrv
  learning_communication::PersonSrv srv;
  srv.request.name = "Tom";
	srv.request.age  = 20;
	srv.request.sex  = learning_communication::PersonSrv::Request::male; // notion 

  // client pub request to service, wait service response 
  ROS_INFO("Call service to show person[name:%s, age:%d, sex:%d]", 
			 srv.request.name.c_str(), srv.request.age, srv.request.sex);

  person_client.call(srv);

  ROS_INFO("Show person result : %s", srv.response.result.c_str());

  return 0;
}

在这里插入图片描述

添加代码如下:

add_executable(person_server src/person_server.cpp)
target_link_libraries(person_server ${catkin_LIBRARIES})
add_dependencies(person_server ${PROJECT_NAME}_gencpp)

add_executable(person_client src/person_client.cpp)
target_link_libraries(person_client ${catkin_LIBRARIES})
add_dependencies(person_client ${PROJECT_NAME}_gencpp)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

四、作业1

4.1、题目

创建一个节点,在其中实现一个订阅者和一个发布者,完成以下功能:

  • 发布者:发布海龟速度指令,让海龟圆周运动
  • 订阅者:订阅海龟的位置信息,并在终端中周期打印输出

4.2、思路

  • 基于turtlesim预定义的topic,在一个节点中创建一个对速度控制话题/turtle1/cmd_vel的发布器pub;
  • 创建控制海龟运动的消息msg,并赋予圆周运动的角速度和线速度参数,并在循环中按照10Hz发布;
  • 创建海龟位姿信息对应话题/turtle1/pose的订阅器sub;
  • 在循环中由spinOnce()单次检测是否有订阅消息,若订阅到则进入回调函数打印对应位姿和速度信息。

ros::spin()ros::spinOnce() 使用:

  • 在ros节点中,如果有订阅消息,则ros的socket连接控制进程会在后台接收订阅的消息,所有接收到的消息并不是立即处理,而是等到spin()或者spinOnce()执行时才集中处理。

ros::spin()ros::spinOnce() 区别:

  • ros::spin(): 一般放在main函数最后,或者return之前,因为此函数不会返回,所以其后的所有程序不会被执行;使用此接口的节点可以看成是消息驱动型,消息到来执行回调函数。
  • ros::spinOnce(): 执行一次本接口,系统处理一次所有的回调函数,然后从此函数返回继续执行。所以一般配合while(ros::ok())使用,并需要自己设置调用频率,以及需要根据消息到达的频率设置消息队列大小以防止消息丢失。

在这里插入图片描述

特别注意上图中确定数据类型以及确定数据格式的命令!

4.3、创建源文件

turtle_pub_sub.cpp

#include "ros/ros.h"
#include "turtlesim/Pose.h"
#include "geometry_msgs/Twist.h"

void poseCallback(const turtlesim::Pose::ConstPtr& msg)
{
  //print receive message
  ROS_INFO("Turtle pose: x:%.4f, y:%.4f",msg->x, msg->y);
}

int main(int argc, char **argv)
{
  //ros node init
  ros::init(argc, argv, "turtle_pub_sub");

  //create node  handle
  ros::NodeHandle n;

  //create a Publisher, publish a topic named "/turtle1/cmd_vel", message type is "geometry_msgs::Twist", buffer size is 1000
  ros::Publisher  pub = n.advertise<geometry_msgs::Twist>("/turtle1/cmd_vel", 1000);
  ros::Subscriber sub = n.subscribe("/turtle1/pose", 1000, poseCallback);

  //set loop freq
  ros::Rate loop_rate(10);

  while(ros::ok())
  {
    //init "geometry_msgs::Twist" type message
    geometry_msgs::Twist msg;
    msg.linear.x = 0.5;
    msg.angular.z = 0.5;

    //publish message
    pub.publish(msg);

    //loop wait callback once
    ros::spinOnce();

    //loop delay
    loop_rate.sleep();

  }
}

4.4、配置编译规则

首先需要在package.xml中添加turtlesim和geometry_msgs这两个功能包,如下图所示:

在这里插入图片描述

然后在CMakeLists.txt中依次添加如下内容:

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

4.5、编译与运行

依次在终端运行如下命令:

roscore
rosrun turtlesim turtlesim_nod
rosrun learning_communication turtle_pub_sub

运行结果,如下图所示:

在这里插入图片描述

五、作业2

5.1、题目

创建另外一个节点,在其中实现一个客户端,实现以下功能:

  • 客户端:请求海龟诞生的服务,在仿真器中产生一只新的海龟

5.2、思路

  • 基于turtlesim中预定义的服务名/spawn,进行服务等待;
  • 创建客户端turtle_spawn,服务数据类型为turtlesim::Spawn,对应服务名/spawn;
  • 定义服务信息,通过终端输入新生海龟的参数,并赋值给请求信息;
  • 客户端turtle_spawn请求服务,终端打印反馈信息。

在这里插入图片描述

特别注意上图中确定数据类型以及确定数据格式的命令!

题目难点:

  • 1、命令行传递参数
    • 解决方案:使用C++关键字cin, 需包含头文件#include <iostream>程序运行到cin命令则循环等待终端输入字符串。
  • 2、位置不重复
    • 解决方案①:随机生成位置,但是不能完全保证不重复
    • 解决方案②:定义全局变量数组存储所有海龟位置, 在新创建海龟时判断当前位置是否已经存在海龟, 若存在则重新创建。(std容器中的std::set、std::map
  • 3、海龟运动时命令行控制其停止
    • 解决方案①:

5.3、创建源文件

turtle_client.cpp

#include "ros/ros.h"
#include "turtlesim/Spawn.h"

int main(int argc, char **argv)
{
  //ros node init
  ros::init(argc, argv, "turtle_client");

  //create node handle
  ros::NodeHandle n;

  //create a client, service message type is "turtlesim::Spawn"
  ros::service::waitForService("/spawn");
  ros::ServiceClient client = n.serviceClient<turtlesim::Spawn>("/spawn");

  //create service message, message type is turtlesim::Spawn
  turtlesim::Spawn srv;
  srv.request.x = 3.0;
  srv.request.y = 3.0;
  srv.request.theta = 0.0;
  srv.request.name  = "turtle2";


  // client pub request to service, wait service response 
  if(client.call(srv))//"call" is a blocking function, 
  {
    ROS_INFO("Response : %s", srv.response.name.c_str());
  }
  else
  {
    ROS_ERROR("Failed to call service print_string");
    return 1;
  }

  return 0;
}

5.4、配置编译规则

在第四节的基础上添加本节的编译规则,只需在CMakeLists.txt中添加如下内容:

在这里插入图片描述

5.5、编译与运行

依次在终端运行如下命令:

roscore
rosrun turtlesim turtlesim_nod
rosrun learning_communication turtle_client

运行结果,如下图所示:

在这里插入图片描述

六、作业3

综合运用话题与服务编程、命令行使用,实现以下场景:

小R想要实现一个海龟运动控制的功能包,需要具备以下功能(以下指令的接收方均为该功能包中的节点)∶

  • 通过命令行发送新生海龟的名字,即可在界面中产生一只海龟,并且位置不重叠;
  • 通过命令行发送指令控制界面中任意海龟圆周运动的启动/停止,速度可通过命令行控制;

6.1、第一问(随机思路)

6.1.1、题目

  • 通过命令行发送新生海龟的名字,即可在界面中产生一只海龟,并且位置不重叠;

6.1.2、思路

  • 创建服务/spawn对应的客户端,并等待服务;
  • 随机生成海龟位姿参数,终端输入海龟名称,并将这些参数赋给服务申请信息;
  • 发送生成海龟请求并打印反馈信息。

6.1.3、创建源文件

turtle_create.cpp

#include "ros/ros.h"
#include "turtlesim/Spawn.h"
#include <iostream>

int main(int argc, char **argv)
{
    //ros node init
    ros::init(argc, argv, "turtle_create");

    //create node handle
    ros::NodeHandle n;

    //create a client, service message type is "turtlesim::Spawn"
    ros::service::waitForService("/spawn");
    ros::ServiceClient client = n.serviceClient<turtlesim::Spawn>("/spawn");

    //create service message, message type is turtlesim::Spawn
    turtlesim::Spawn srv;

    int frontier = 11 * 10; 
    float pi = 3.1415926;
    srand((unsigned)time(NULL));
    float pose_x = (rand() % frontier) / 10.0; //海龟x和y边界是11(m),随机生成坐标,精确到小数点后一位
    float pose_y = (rand() % frontier) / 10.0;
    float pose_theta = (rand() % 360) * pi / 180; //生成(0-359°)角度,然后将其转换成弧度
    ROS_INFO(" random pose calculating done ! ");

    ROS_INFO(" name of new-born turtle is: [name should be within 20 chars]");
    char turtle_name[20];
    std::cin >> turtle_name;

    //将上述定义海龟的参数赋给服务请求
    srv.request.name  = turtle_name;
    srv.request.x = pose_x;
    srv.request.y = pose_y;
    srv.request.theta = pose_theta;

    // client pub request to service, wait service response 
    if(client.call(srv))//"call" is a blocking function, 
    {
        ROS_INFO("Create a new turtle: %s", srv.response.name.c_str());
        // ROS_INFO("New turtle pose is : x:%.4f, y:%.4f, theta:%.4f", srv.response.x, srv.response.y, srv.response.theta);
    }
    else
    {
        ROS_ERROR("Failed to call service print_string");
        return 1;
    }

    return 0;
}

6.1.4、配置编译规则

在上述章节基础上添加本节的编译规则,只需在CMakeLists.txt中添加如下内容:

在这里插入图片描述

6.1.5、编译与运行

依次在终端运行如下命令:

roscore
rosrun turtlesim turtlesim_nod
rosrun learning_communication turtle_create

在这里插入图片描述

6.2、第二问

6.2.1、题目

  • 通过命令行发送指令控制界面中任意海龟圆周运动的启动/停止,速度可通过命令行控制;

6.2.2、思路

  • 自定义服务数据类型TurtleSrv,包括海龟名、运动状态、线速度、x轴线速度、z轴角速度、反馈信息;
  • 创建海龟服务端,turtle_cmd_serve.cpp脚本中实例化服务端turtle_cmd_server,当接收到请求时,判断请求数据中海龟的运动状态(move/stop),通过内置循环中的发布器pub向对应海龟的速度控制话题("/"+req.name + “/cmd_vel”)发布相应的运动信息指令;
  • 创建海龟控制客户端,turtle_cmd_client.cpp脚本中实例化客户端turtle_cmd_client,等待自定义服务/turtle_cmd,通过终端输入对turtlesim中已经存在的海龟控制指令(包括:海龟名、运动状态、线速度、角速度),赋值给服务请求并发送。

6.2.3、创建源文件

TurtleSrv.srv

string name
string state
uint8  linear_x
uint8  angular_z

---

string feedback

turtle_cmd_server.cpp

/**
 * Server provide a service named "print_string", message type is "std_srvs::SetBool"
 */

#include "ros/ros.h"
#include "learning_communication/TurtleSrv.h"
#include "geometry_msgs/Twist.h"

bool turtleCallback(learning_communication::TurtleSrv::Request &req, 
                    learning_communication::TurtleSrv::Response &res)
{
    //state=move时,指定的海龟根据指定参数运动
    if (req.state == "move")
    {

        ros::NodeHandle n1;
        ros::Publisher pub = n1.advertise<geometry_msgs::Twist>("/"+req.name + "/cmd_vel", 10);
        ros::Rate loop_rate(1);
        
        //按照循环频率发布海龟运动信息,会被turtlesim接收并控制海龟运动
        while (ros::ok()) {
            geometry_msgs::Twist msg;
            msg.linear.x = req.linear_x;
            msg.angular.z = req.angular_z;
            ROS_INFO("vel_publish: linear_x=%f angular_z=%f", msg.linear.x, msg.angular.z);
            pub.publish(msg);
            loop_rate.sleep();
            ros::spinOnce();
        }
		//输出反馈信息
        res.feedback = req.name + " has been controlled ! ";
        return true;
    }

    //state=stop时,指定的海龟停止
    else if (req.state == "stop")
    {
        ros::NodeHandle n1;
        ros::Publisher pub = n1.advertise<geometry_msgs::Twist>("/"+req.name + "/cmd_vel", 10);
        ros::Rate loop_rate(1);
        
        //按照循环频率发布海龟运动信息,会被turtlesim接收并控制海龟运动
        while (ros::ok()) {
            geometry_msgs::Twist msg;
            msg.linear.x = 0;
            msg.angular.z = 0;
            ROS_INFO("vel_publish: linear_x=%f angular_z=%f", msg.linear.x, msg.angular.z);
            pub.publish(msg);
            loop_rate.sleep();
            ros::spinOnce();
        }
        //输出反馈信息
        res.feedback = req.name + " has been controlled ! ";
        return true;
    }

    else
    {
        //输出反馈信息
        res.feedback = " wrong command ! ";
        return false;
    }
}

int main(int argc, char **argv)
{
  //ros node init
  ros::init(argc, argv, "turtle_cmd_server");

  //create node handle
  ros::NodeHandle n;

  //create a Server named "turtle_cmd", regisite callback function "turtleCallback"
  ros::ServiceServer turtle_cmd_service = n.advertiseService("/turtle_cmd", turtleCallback);

  //loop wait callback
  ROS_INFO("Ready to print show turtle information.");
  ros::spin();

  return 0;
}

turtle_cmd_client.cpp

/**
 * Server provide a service named "print_string", message type is "std_srvs::SetBool"
 */

#include "ros/ros.h"
#include "learning_communication/TurtleSrv.h"

int main(int argc, char **argv)
{
  //ros node init
  ros::init(argc, argv, "turtle_cmd_client");

  //create node handle
  ros::NodeHandle n;

  //create a client, service message type is "learning_communication::TurtleSrv"
  ros::service::waitForService("/turtle_cmd");
  ros::ServiceClient turtle_cmd_client = n.serviceClient<learning_communication::TurtleSrv>("/turtle_cmd");

  //create service message, message type is learning_communication::TurtleSrv
  learning_communication::TurtleSrv srv;
  ROS_INFO(" give commands of turtle to be controlled: ");

  ROS_INFO(" name: ");
  char name[10]; //char name[20];
  std::cin >> name;

  ROS_INFO(" state: [ move or stop ] ");
  char state[10];
  std::cin >> state;

  ROS_INFO(" linear_x: ");
  int linear_x;
  std::cin >> linear_x;

  ROS_INFO(" angular_z: ");
  int angular_z;
  std::cin >> angular_z;

  //给服务中的请求信息赋值
  srv.request.name = name;
  srv.request.state = state;
  srv.request.linear_x = linear_x;
  srv.request.angular_z = angular_z;

  ROS_INFO("Call service to command turtle [name:%s, state:%s, linear_x:%d, angular_z:%d] ",
            srv.request.name.c_str(), srv.request.state.c_str(), srv.request.linear_x, srv.request.angular_z);

	turtle_cmd_client.call(srv);

  ROS_INFO("Show turtle result : %s", srv.response.feedback.c_str());

  return 0;
}

6.2.4、配置编译规则

在上述章节基础上添加本节的编译规则,只需在CMakeLists.txt中添加如下内容:

在这里插入图片描述
在这里插入图片描述

6.2.5、编译与运行

依次在终端运行如下命令:

roscore
rosrun turtlesim turtlesim_nod
rosrun learning_communication turtle_create
rosrun learning_communication turtle_create
rosrun learning_communication turtle_cmd_server
rosrun learning_communication turtle_cmd_client

在这里插入图片描述

6.3、待完善地方

  • 作业3的第一问,随机的方式生成位置,可能存在重叠,继续完善可以使用C++ 容器中的set和map方法(去重、遍历)。还有一种思路,利用一个服务端记录全局信息, 所有的海龟信息都上传到该服务端存储。
  • 作业3的第二问,上述给出的代码还有点小问题,①、必须在server端的回调函数中加入spinOnce函数,不然程序一旦启动无法停下。②、Server端正常move或stop后不会反馈feedback信息,暂时没找到是哪里原因。③、同时只能启动一只turtle。
  • 作业3的第二问,尝试多线程这样是不是就可以同时启动多只海龟?

参考

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ReCclay

如果觉得不错,不妨请我喝杯咖啡

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值