一、ROS项目开发流程
1.1、STEP1:创建工作空间
“安装空间”中的部分内容和“开发空间”重复,它里面也会放置可执行文件和环境变量。需要注意,默认的编译不会生成“安装空间”,需要单独的指令生成“安装空间”。“安装空间”的含义相当于windows中的Program Files文件夹,会把ROS编译的结果安装到“安装空间”路径下,“安装空间”的路径是可以动态指定的,相当于指定最终的可执行文件安装位置,默认是在workspace下的“安装空间”下。
上面有句话没写完整:要想生成“安装空间”,需要执行:
catkin_make install
如果不想每次都手动生效环境变量,可以在
~/.bashrc
中设置环境变量,如下图:
1.2、STEP2:创建功能包
区分功能包还是文件夹,功能包一定有
CMakeLists.txt
和package.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.xml
和CMakeLists.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.h
、PersonSrvRequest.h
和PersonSrvResponse.h
。但是调用的时候,只需要调用PersonSrv.h
即可!
PersonSrv.srv
源码如下,package.xml
和CMakeLists.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命令则循环等待终端输入字符串。
- 解决方案:使用C++关键字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的第二问,尝试多线程这样是不是就可以同时启动多只海龟?