ROS学习笔记

ROS学习笔记二:ROS编程基础



前言

ROS学习笔记一:ROS框架基础

注:
这一阶段可能需要将代码或者文件从主机复制到虚拟机,然而 ubunt18.04 在安装vmware tool之后,大概率仍然会出现无法复制黏贴的情况!
更正的方法见链接

知乎:vmware实现复制黏贴功能

注意linux系统中复制黏贴是Ctrl + shift + C 和Ctrl + shift + V
这么设置的原因,是Ctrl + C在linux系统中是中止运行的作用。



一、第10讲:发布者publisher的编程实现

1. 发布者的信息传递模型

古月居

上图可以看出,ROSmaster起登记 publisher 和 subscriber 的作用,但并不参与其数据传输。
也就是说,当publisher 和 subscriber通过ROSmaster建立联系之后 (因为未在ROSmaster登记的节点无法被检索到) ,其数据传输即可通过话题进行。即使这之后ROSmaster关闭了也可以继续传输,因为连接已经建立。

2. 通过C++程序实现publisher 功能
1. 创建功能包:
            cd ~/catkin_ws/src            ---进入对应目录,也可通过鼠标手动进入
            catkin_create_pkg learning_topic roscpp rospy std_msgs geometry_msgs turtlesim
                              ---创建功能包名为learning_topic, 并添加数个依赖。
                       
2. 代码实现publisher的创建:
"下述代码用于注释讲解,想获取可直接运行的代码建议去古月居视频下载,或者复制下述代码自己删除无法运行的字符串部分"

 /*该例程将发布turtle1/cmd_vel话题,消息类型geometry_msgs::Twist 
      来自于古月居*/
#include <ros/ros.h>
#include <geometry_msgs/Twist.h>      //引入相关库

int main(int argc, char **argv)
{
	// ROS节点初始化
	ros::init(argc, argv, "velocity_publisher");    "调用ros.h 库中的初始化函数"
	// 创建节点句柄
	ros::NodeHandle n;     
	"句柄是便于管理节点资源的一种形式,你可以将所以通信方面的功能分配一个句柄,"
	"然后将所有处理数据方面的功能分配给另一个句柄,而这两个句柄将归属于同一个节点。"
	"这就是句柄对于节点的作用,它可以将节点进一步分为多个更小的模块"

	// 创建一个Publisher,发布名为/turtle1/cmd_vel的topic,消息类型为geometry_msgs::Twist,队列长度10
	ros::Publisher turtle_vel_pub = n.advertise<geometry_msgs::Twist>("/turtle1/cmd_vel", 10);
	"              publisher的名称  句柄                  消息类型             话题名      队列长度"
	"话题名: 订阅者需要订阅的就是该话题名"
	"队列长度: 要发布的数据会先存入队列,再往外发数据。如果一定时间内存入的数据超出队列长度,且来不及发送出去,数据就会有丢失"
	
	// 设置循环的频率
	ros::Rate loop_rate(10);

	int count = 0;
	while (ros::ok())
	{
	    // 初始化geometry_msgs::Twist类型的消息
		geometry_msgs::Twist vel_msg;
		"c++ 定义结构体的格式,其实类似于 int a,定义一个整数a。而此处 ~~~ vel_msg,也就是定义一个 ~~~ 类型的变量 vel_msg"
		vel_msg.linear.x = 0.5;
		vel_msg.angular.z = 0.2;
	    // 发布消息
		turtle_vel_pub.publish(vel_msg);
		ROS_INFO("Publsh turtle velocity command[%0.2f m/s, %0.2f rad/s]", 
				vel_msg.linear.x, vel_msg.angular.z);
		"ROS中c++的打印函数, 平常我们会使用printf 或者 cout,但ROS包中不使用。"
		
	    // 按照循环频率延时
	    loop_rate.sleep();
	}
	return 0;
}           
3.配置publisher 编译规则,即修改CMakelists.txt文件:

古月居

将对应代码复制到对应位置即可  放置在#Build#部分的最下方即可

add_executable(velocity_publisher src/velocity_publisher.cpp)
"该函数用来描述":要将哪一段程序,编译成可执行文件
(上述代码是对 / 前的cpp编译,成为 / 后的velocity_publisher这样一个可执行文件)

target_link_libraries(velocity_publisher ${catkin_LIBRARIES})
"该函数作用": 将可执行文件与相关的库作链接。
(上述代码将velocity_publisher 与 catkin_LIBRARIES库进行了链接)

python文件并不需要在cmakelists中像这样修改,因为python文件不用单独编译。

4. 编译并且运行publisher
catkin_make
source devel/setup.bash
rosrun turtlesim
新开终端
rosrun learning_topic velocity_publisher   
                      ---即运行learning_topic包中的velocity_publisher节点
                         如果无法在工作空间找到包或者节点,则说明编译阶段有问题。

如何简化source devel/setup.bash环节?
众所周知,catkin_make只用构建工作空间阶段输入一次,但是source devel/setup.bash每次编译环境时都要输入非常麻烦, 我们可以通过将source devel/setup.bash编入启动文件来实现启动时顺便编译环境。
1)在主文件夹按 ctrl + H 显示隐藏文件
2)打开 .bashrc ,拉到最后一行,模仿原本最后一行的格式,加入我们自己写的启动文件。
3)即在最后添加一行 /home/user/catkin_ws/devel/setup.bash。
注意!路径需要按自己工作空间位置以及名称进行修改

5、通过python实现命令发布:
#!/usr/bin/env python
# -*- coding: utf-8 -*-

########################################################################
####          Copyright 2020 GuYueHome (www.guyuehome.com).          ###
########################################################################

# 该例程将发布turtle1/cmd_vel话题,消息类型geometry_msgs::Twist

import rospy
from geometry_msgs.msg import Twist

def velocity_publisher():
	# ROS节点初始化
    rospy.init_node('velocity_publisher', anonymous=True)

	# 创建一个Publisher,发布名为/turtle1/cmd_vel的topic,消息类型为geometry_msgs::Twist,队列长度10
    turtle_vel_pub = rospy.Publisher('/turtle1/cmd_vel', Twist, queue_size=10)

	#设置循环的频率
    rate = rospy.Rate(10) 

    while not rospy.is_shutdown():
		# 初始化geometry_msgs::Twist类型的消息
        vel_msg = Twist()
        vel_msg.linear.x = 0.5
        vel_msg.angular.z = 0.2

		# 发布消息
        turtle_vel_pub.publish(vel_msg)
    	rospy.loginfo("Publsh turtle velocity command[%0.2f m/s, %0.2f rad/s]", 
				vel_msg.linear.x, vel_msg.angular.z)

		# 按照循环频率延时
        rate.sleep()

if __name__ == '__main__':
    try:
        velocity_publisher()
    except rospy.ROSInterruptException:
        pass

注意!
1、python文件需要权限为可执行文件才能运行,如何查看文件的运行权限?
右键py文件,进入属性,上侧切换到权限页面,下方执行栏,框内点为勾即可。
2、python文件不需要编译,直接rosrun 即可。



二、第11讲:订阅者subscriber的编程实现

1.通过C++编程实现subscriber:
'古月居'
 //该例程将订阅/turtle1/pose话题,消息类型turtlesim::Pose
 
#include <ros/ros.h>
#include "turtlesim/Pose.h"
// 接收到订阅的消息后,会进入消息回调函数
void poseCallback(const turtlesim::Pose::ConstPtr& msg)
{
    // 将接收到的消息打印出来
    ROS_INFO("Turtle pose: x:%0.6f, y:%0.6f", msg->x, msg->y);
    "ROS_INFO就是平常编程中的打印函数"
}
int main(int argc, char **argv)
{
    // 初始化ROS节点
    ros::init(argc, argv, "pose_subscriber");
    // 创建节点句柄
    ros::NodeHandle n;
    // 创建一个Subscriber,订阅名为/turtle1/pose的topic,注册回调函数poseCallback
    ros::Subscriber pose_sub = n.subscribe("/turtle1/pose", 10, poseCallback);
    "创建一个subscribe,核心是:收到/turtle1/pose话题的消息后,将其传入poseCallback,"
    "即pose对应的回调函数队列,传入后保持待处理的状态,10 指设定的队列上限"
    // 循环等待回调函数
    ros::spin();
    "spin是一个循环回调函数队列处理函数,主要的工作,就是循环检查回调函数队列有数据"
    "如果有的话,它就会马上去执行callback函数,也就是最上面的那个void poseCallback。"
    "如果没有的话,它就会阻塞,不会占用CPU"

    return 0;
}

ros::spin 与 ros::spinOnce:
两者的区别就是,spin会一直循环,而spinonce运行一次后会继续向后运行,可以配合其他代码一起工作(通过一个更大的循环包起来即可替代spin的循环功能)。


需要注意的是,假如spinOnce与耗时长的函数一起循环,会导致无法及时处理回调函数队列,很可能导致队列溢出丢失数据。
以及,这两个函数都需要考虑回调函数内容编写的时间复杂度,如果单次回调函数执行耗时过久,一样会导致队列溢出丢失数据。

2、编译并运行subscriber:
1. 修改CMakeLists.txt文件的内容:
同样是复制到 #build# 栏的下便
add_executable(pose_subscriber src/pose_subscriber.cpp)
target_link_libraries(pose_subscriber${catkin_LIBRARIES})

3. 终端的命令
catkin_make
source devel/setup.bash      ---如果在之前完成了启动文件添加setup.bash代码就不用输这行了。
roscore
rosrun turtlesim turtlesim_node
rosrun learning_topic velocity_publisher
3、通过python来实现
#!/usr/bin/env python
# -*- coding: utf-8 -*-
########################################################################
####          Copyright 2020 GuYueHome (www.guyuehome.com).          ###
########################################################################
# 该例程将订阅/turtle1/pose话题,消息类型turtlesim::Pose

import rospy
from turtlesim.msg import Pose

def poseCallback(msg):
    rospy.loginfo("Turtle pose: x:%0.6f, y:%0.6f", msg.x, msg.y)

def pose_subscriber():
	# ROS节点初始化
    rospy.init_node('pose_subscriber', anonymous=True)
	# 创建一个Subscriber,订阅名为/turtle1/pose的topic,注册回调函数poseCallback
    rospy.Subscriber("/turtle1/pose", Pose, poseCallback)
	# 循环等待回调函数
    rospy.spin()

if __name__ == '__main__':
    pose_subscriber()


三、第12讲:话题消息msg的定义与使用

1、什么是话题消息?

话题中的消息,即话题传递信息的格式。

2、如何自定义话题消息?
1)定义msg文件
      问题1. 'msg文件放在哪里?'
      答: 首先创建msg文件夹,放在对应功能包内(与src文件夹平级),然后在msg文件夹内创建msg文件。
           因为msg文件同样需要cmakelists 和 package.xml来编译,所以与src文件放置方式类似。
       问题2.'使用代码创建msg文件'
               touch Person.msg 即可
               
2)在package.xml中添加功能包依赖

      <build_depend>message_generation</build_depend>
      <exec_depend>message_runtime</exec_depend>
      将这两行代码复制至 .xml 文件中,相同格式内容的下方即可,有一片相同格式的代码都在那里。

3)在cmakelists.txt中添加编译选项
      这一步要分三个地方:
          1. find_package(......
                          ......
                          message_generation)
             '位置':找对应函数名
             其中,find_package 是cmakelists中本来就有的函数名称,
             我们需要做的,就是在该函数中加入 message_generation 参数
                                 注意该参数名与上面 .xml 文件中 build_depend 栏的参数名一致。
                                                             (即编译)
                                 
         2. add_message_files(FILES Person.msg)
            generate_messages(DEPENDENCIES std_msgs)
            '位置':在 ##Declare ROS messages,service and actions## 栏的最下面
               在该栏的最下面,有很多注释起来的函数说明,仔细观察即可发现
               我们补充的函数正是这些被注释的函数,可以阅读注释来了解函数对应的意思。
            因此我们只需要在该栏最后加入上述函数即可。

         3. catkin_package(......
                           ......
                           message_runtime)
            '位置': ##catkin specific configuration ## 栏下
               其中,catkin_package 是其中没有被注释的函数名,但是它的参数被注释了,
               因此我们需要将 #CATKIN_DEPENDS geometry_msgs roscpp ...... 行解除注释
               再然后,将 message_runtime 参数加入到该行最后即可
                       注意该参数名与上面 .xml 文件中 exec_depend 栏的参数名一致。
                                                   (即依赖)

4)编译
               在工作空间(注意不是功能包)打开终端,输入 catkin_make 编译即可。
               编译完成后,可以在 devel (即编译文件夹)内,include文件夹中找到编译出的对应话题信息C++文件。

3、运用发布者和订阅者传输自定义的话题信息格式:
1/发布者:
/***********************************************************************
Copyright 2020 GuYueHome (www.guyuehome.com).
***********************************************************************/
/**
 * 该例程将发布/person_info话题,自定义消息类型learning_topic::Person
 */
#include <ros/ros.h>
#include "learning_topic/Person.h"

int main(int argc, char **argv)
{
    // ROS节点初始化
    ros::init(argc, argv, "person_publisher");
    // 创建节点句柄
    ros::NodeHandle n;
    // 创建一个Publisher,发布名为/person_info的topic,消息类型为learning_topic::Person,队列长度10
    ros::Publisher person_info_pub = n.advertise<learning_topic::Person>("/person_info", 10);
    // 设置循环的频率
    ros::Rate loop_rate(1);

    int count = 0;
    while (ros::ok())
    {
        // 初始化learning_topic::Person类型的消息
    	learning_topic::Person person_msg;
		person_msg.name = "Tom";
		person_msg.age  = 18;
		person_msg.sex  = learning_topic::Person::male;
        // 发布消息
		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_rate.sleep();
    }
    return 0;
}
----------------------------------------
----------------------------------------

2/ 订阅者
/***********************************************************************
Copyright 2020 GuYueHome (www.guyuehome.com).
***********************************************************************/
/**
 * 该例程将订阅/person_info话题,自定义消息类型learning_topic::Person
 */
#include <ros/ros.h>
#include "learning_topic/Person.h"

// 接收到订阅的消息后,会进入消息回调函数
void personInfoCallback(const learning_topic::Person::ConstPtr& msg)
{
    // 将接收到的消息打印出来
    ROS_INFO("Subcribe Person Info: name:%s  age:%d  sex:%d", 
			 msg->name.c_str(), msg->age, msg->sex);
}

int main(int argc, char **argv)
{
    // 初始化ROS节点
    ros::init(argc, argv, "person_subscriber");
    // 创建节点句柄
    ros::NodeHandle n;
    // 创建一个Subscriber,订阅名为/person_info的topic,注册回调函数personInfoCallback
    ros::Subscriber person_info_sub = n.subscribe("/person_info", 10, personInfoCallback);
    // 循环等待回调函数
    ros::spin();
    return 0;
}
---------------------------------------------
---------------------------------------------

3、适配编译规则:
和之前一样,我们需要修改cmakelists.txt 文件,以适配对自定义话题信息的编译规则

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

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

'注意差别':
首先在于添加的函数内的参数不同,其次是多了一个 add_dependencies() ,这是为了
将自定义话题时自动生成的文件也加入依赖关系,一起编译。
(大概,笔者也不太懂)
-------------------------------------------
-------------------------------------------

4、编译
与之前同样,修改cmakelists.txt 后别忘了在工作空间进行编译。



四、第13讲:客户端client的编程实现

1、客户端(client)、服务器(server)信息传递方式

古月居

2、如何实现
1. 创建功能包
和实现话题、订阅功能类似,想要实现客户端 服务器模型同样通过创建功能包的方法实现

cd~/catkin_ws/src      ---进入对应的文件夹
catkin_create_pkg learning_service roscpp rospy std_msgs geometry_msgs turtlesim
                       ---通过指令创建对应的文件夹并一键添加依赖

2. 通过C++编程实现客户端client:
/**
 * 该例程将请求/spawn服务,服务数据类型turtlesim::Spawn; by古月居
 */
#include <ros/ros.h>
#include <turtlesim/Spawn.h>

int main(int argc, char** argv)
{
    // 初始化ROS节点
	ros::init(argc, argv, "turtle_spawn");
    // 创建节点句柄
	ros::NodeHandle node;
    // 发现/spawn服务(即waitForService)后,创建一个服务客户端,连接名为/spawn的service
	ros::service::waitForService("/spawn");
	ros::ServiceClient add_turtle = node.serviceClient<turtlesim::Spawn>("/spawn");
    // 初始化turtlesim::Spawn的请求数据
	turtlesim::Spawn srv;
	srv.request.x = 2.0;
	srv.request.y = 2.0;
	srv.request.name = "turtle2";
	   "这段是客户端client以spawn格式,发送给服务器server的信息,服务器会根据请求数据做出反应。"
    // 请求服务调用
	ROS_INFO("Call service to spwan turtle[x:%0.6f, y:%0.6f, name:%s]", 
			 srv.request.x, srv.request.y, srv.request.name.c_str());
	add_turtle.call(srv);
	   "这段 add_turtlr.call() 函数是堵塞性函数,当它发送出 请求数据(即上面的那个) 之后会保持等待回应,不会继续运行。"
	   "等到服务器发出成功接收指令的回应馈之后,就会继续向下运行"
	
	// 显示服务调用结果
	ROS_INFO("Spwan turtle successfully [name:%s]", srv.response.name.c_str());
	return 0;
};

3. 在cmakelists.txt里配置代码编译规则
add_executable(turtle_spawn src/turtle_spawn.cpp)
target_link_libraries(turtle_spawn ${catkin_LIBRARIES})
"代码的解释见之前,都是差不多的"

4. 编译并运行客户端
catkin_make
source devel/setup.bash
roscore
rosrun turtlesim turtlesim_node
rosrun learning_service turtle_spawn

python实现方法就不展示了,感兴趣的可以去古月居的b站视频



四、第14讲:服务端server的实现:

1、如何实现一个服务器:
  • 初始化ros节点
  • 创建server实例
  • 循环等待服务请求,收到请求即进入回调函数
  • 在回调函数中完成服务功能的处理,并反馈应答数据
1. C++代码实现服务端
/**
 * 该例程将执行/turtle_command服务,服务数据类型std_srvs/Trigger  by 古月居
 */
#include <ros/ros.h>
#include <geometry_msgs/Twist.h>
#include <std_srvs/Trigger.h>

ros::Publisher turtle_vel_pub;
bool pubCommand = false;

// service回调函数,输入参数req,输出参数res
bool commandCallback(std_srvs::Trigger::Request  &req,
         			std_srvs::Trigger::Response &res)
{
    // 对pubcommand取反,即类似开关(trigger)的形式,一开一关。
	pubCommand = !pubCommand;
    // 显示请求数据
    ROS_INFO("Publish turtle velocity command [%s]", pubCommand==true?"Yes":"No");
	// 设置反馈数据
	res.success = true;
	res.message = "Change turtle command state!";
    return true;
}

int main(int argc, char **argv)
{
    // ROS节点初始化
    ros::init(argc, argv, "turtle_command_server");
    // 创建节点句柄
    ros::NodeHandle n;
    // 创建一个名为/turtle_command的server,注册回调函数commandCallback
    ros::ServiceServer command_service = n.advertiseService("/turtle_command", commandCallback);
	// 创建一个Publisher,发布名为/turtle1/cmd_vel的topic,消息类型为geometry_msgs::Twist,队列长度10
	turtle_vel_pub = n.advertise<geometry_msgs::Twist>("/turtle1/cmd_vel", 10);
    // 循环等待回调函数
    ROS_INFO("Ready to receive turtle command.");
	// 设置循环的频率
	ros::Rate loop_rate(10);
	while(ros::ok())
	{
		// 查看一次回调函数队列
    	ros::spinOnce();
		// 如果标志为true,则发布速度指令
		if(pubCommand)
		{
			geometry_msgs::Twist vel_msg;
			vel_msg.linear.x = 0.5;
			vel_msg.angular.z = 0.2;
			turtle_vel_pub.publish(vel_msg);
		}
		//按照循环频率延时
	    loop_rate.sleep();
	}
    return 0;
}

'注意':
服务端server 在收到客户端的请求之后,需要发布指令,此时仍然是以topic形式发布信息的,也就是publisher、subscriber模式。
因此可以看出,服务器客户端模式 和 发布者订阅者模式 各有特点,并且他们可以结合在一起使用。

---------------------------------------------------
2. 通过命令行查看服务数据的定义:
rosrv show std_srvs/trigger      ---显示trigger的数据结构
'数据结构含义的分析在下一讲会详细讲解'

--------------------------------------------------------
3. 修改cmakelists 编译配置文件:
add_executable(turtle_command_server src/turtle_command_server.cpp)
target_link_libraries(turtle_command_server ${catkin_LIBRARIES})

--------------------------------------------------------
4. 编译并运行服务器
catkin_make
source devel/setup.bash
roscore
rosrun turtlesim turtlesim_node       ---启动海龟仿真节点
rosrun learning_service turtle_command_server      ---启动自己编写的服务端server
rosservice call /turtle_command"{}"      ---call函数向服务端申请,服务端收到申请后发布命令


2、python实现服务端server:

这里就不再详细介绍,想了解详情可以去古月居b站原视频了解



五、第15讲:服务数据service的定义与使用

1. 什么是服务数据?

类似于话题数据。

2. 如何自定义服务数据service?
1)创建srv文件夹
与src同级,都在功能包下一级内

2)通过指令创建srv文件
touch Person.srv

3)在srv文件内编辑数据,也就是服务数据的格式
string name
uint8  age
uint8  sex

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

---
string result

'注意':上述代码以 --- 分割为上下两部分。
       --- 以上是我们通过client发送给服务端的数据,即request
       --- 以下是服务端将发回给client的数据,即Response


4)编辑cmakelists 编译规则以及 package.xml 的相关依赖
'注意':文件中需要修改的位置与编辑话题消息时一致,可以参考上面的话题消息教程对照学习。
    1.修改package.xml:
       <build_depend>message_generation</build_depend>
       <exec_depend>message_runtime</exec_depend>
    2.修改cmakelists:
       1. find_package(......
                    ......
                    message_generation)

       2. add_service_files(
                         FILES
                         Person.srv)

          generate_messages(
                          DEPENDENCIES
                          std_msgs)
                          
       3. catkin_package(
            #  INCLUDE_DIRS include
            #  LIBRARIES learning_service
               CATKIN_DEPENDS ......  message_runtime
            #  DEPENDS system_lib)
     
    3. 查看编译结果:
       catkin_make
       
       同样在devel文件夹下include文件夹内。
       相比于话题消息的编译结果,服务消息会多出两个 .h 头文件,
       分别是Req 和 Res ,代表请求request 和回应Response,其余并无差别。
   
3. 使用自己定义的服务数据进行 client 和 server 之间的通信:

古月居相关代码:

1)client
/**  BY 古月居
 * 该例程将请求/show_person服务,服务数据类型learning_service::Person
 */
#include <ros/ros.h>
#include "learning_service/Person.h"

int main(int argc, char** argv)
{
    // 初始化ROS节点
	ros::init(argc, argv, "person_client");
    // 创建节点句柄
	ros::NodeHandle node;
    // 发现/spawn服务后,创建一个服务客户端,连接名为/spawn的service
	ros::service::waitForService("/show_person");
	ros::ServiceClient person_client = node.serviceClient<learning_service::Person>("/show_person");
    // 初始化learning_service::Person的请求数据
	learning_service::Person srv;
	srv.request.name = "Tom";
	srv.request.age  = 20;
	srv.request.sex  = learning_service::Person::Request::male;
    // 请求服务调用
	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)server
/**   by 古月居
 * 该例程将执行/show_person服务,服务数据类型learning_service::Person
 */
#include <ros/ros.h>
#include "learning_service/Person.h"

// service回调函数,输入参数req,输出参数res
bool personCallback(learning_service::Person::Request  &req,
         			learning_service::Person::Response &res)
{
    // 显示请求数据
    ROS_INFO("Person: name:%s  age:%d  sex:%d", req.name.c_str(), req.age, req.sex);
	// 设置反馈数据
	res.result = "OK";
    return true;
}

int main(int argc, char **argv)
{
    // ROS节点初始化
    ros::init(argc, argv, "person_server");
    // 创建节点句柄
    ros::NodeHandle n;
    // 创建一个名为/show_person的server,注册回调函数personCallback
    ros::ServiceServer person_service = n.advertiseService("/show_person", personCallback);
    // 循环等待回调函数
    ROS_INFO("Ready to show person informtion.");
    ros::spin();

    return 0;
}

总结

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值