ROS2之服务
ROS2 的服务(Service)
-
概念:
服务是一种 请求-响应(request-response)通信机制,由 客户端(Client) 和 服务端(Server) 两部分组成。 -
通信模式:
-
客户端 发送一个请求(Request)。
-
服务端 处理请求,并返回响应(Response)。
-
一问一答,适合短时操作。
-
-
特点:
-
一对一通信,不像话题那样是一对多。
-
请求和响应的数据类型是 固定定义好的
.srv
文件(里面有请求字段和响应字段)。 -
适合执行需要确认结果的操作。
-
-
典型应用场景:
-
查询机器人位置。
-
控制执行某个单次动作(例如开关灯、拍照)。
-
获取传感器的单次读数。
-
服务的本质是通过两个交互的话题实现的,当你创建一个服务时,系统会自动生成两个隐藏的话题:
-
请求话题(Request Topic)
-
客户端(Client)通过这个话题向服务端(Server)发送请求消息。
-
-
响应话题(Response Topic)
-
服务端(Server)通过这个话题把处理结果返回给客户端。
-
一个服务 = 请求话题 + 响应话题。
一般来说客户端节点会向请求话题发布消息,然后从响应话题得到消息,同理服务端向响应话题发布消息,从请求话题得到消息,二者的对于不同的话题交互都是单向的,保证了一个负责请求,一个负责响应。
基于服务的参数通信
参数通信
-
在 ROS2 中,参数(Parameter) 是节点的配置项,用来控制节点的行为。
例如:-
turtlesim_node
的背景颜色 -
激光雷达的刷新频率
-
控制器的 PID 系数
-
-
参数通信 指的是:节点之间通过 服务机制(Service) 来 设置、修改、查询参数。
ROS2 参数通信的特点
-
一对一的服务通信
-
参数操作是请求-响应模式(类似打电话)。
-
客户端(如
ros2 param
命令)发送请求,服务端(节点)返回结果。
-
-
内置服务接口
ROS2 每个支持参数的节点都会自动提供一些参数相关的服务:-
/node_name/get_parameters
获取参数值 -
/node_name/set_parameters
设置参数值 -
/node_name/list_parameters
列出参数 -
/node_name/describe_parameters
获取参数描述(类型、范围等)
-
-
接口类型定义
所有参数相关的服务接口都在rcl_interfaces
包中定义,比如:-
rcl_interfaces/srv/GetParameters
-
rcl_interfaces/srv/SetParameters
-
通过参数通信调节设置小海龟的背景颜色,在 ROS2 中,也可以通过 YAML 配置文件提前写好参数,然后在启动节点时加载,让节点自动带着这些参数运行。
话题消息接口为功能包下的msg下的.msg文件,而服务消息接口为srv下的.srv文件
服务接口一般形式靠---分割
# 请求部分(Request)
<请求字段类型> <请求字段名称>
<请求字段类型> <请求字段名称>
...---
# 响应部分(Response)
<响应字段类型> <响应字段名称>
<响应字段类型> <响应字段名称>
...
ROS2 接口命名规范对照表
类型 | 正确示例 ✅ | 错误示例 ❌ | 说明 |
---|---|---|---|
包名 (package) | face_interfaces |
FaceInterfaces , face-interfaces , faceInterfaces |
必须小写,下划线分隔,不允许大写或 - |
消息文件 (.msg) | Person.msg , FaceData.msg , RobotStatus.msg |
person.msg , face_data.msg , Face_data.msg |
文件名 PascalCase,首字母大写,不能用下划线 |
服务文件 (.srv) | GetFaceInfo.srv , AddTwoInts.srv |
get_face_info.srv , add_two_ints.srv |
文件名 PascalCase,系统会生成 _Request 和 _Response |
动作文件 (.action) | Navigate.action , TrackFace.action |
track_face.action , Navigate_Action.action |
文件名 PascalCase,系统会生成 _Goal 、_Result 、_Feedback |
消息/服务/动作字段 (内部) | string name , int32 age , geometry_msgs/Point position |
string Name , int Age , float positionX |
字段名必须 小写+下划线,类型用 ROS 标准类型 |
生成的类型名 (自动生成) | GetFaceInfo_Request , GetFaceInfo_Response Navigate_Goal , Navigate_Result , Navigate_Feedback |
get_face_info_Request , face_interfaces_Request |
自动生成的类型名也遵循 PascalCase,错误一般来自文件命名不规范 |
PascalCase:PascalCase 是一种命名方式,规则是 每个单词的首字母大写且不使用下划线或空格,例如:GetFaceInfo
、PersonMsg
。
Python实现人脸检测服务
该人脸检测服务由客户端上传人脸图片,发送给服务端,客户端启动之后会等待服务端的启动才能进行正常的服务,否则处于休眠状态,防止占用过多cpu,然服务端响应回人脸个数,用时和每个框的四个点的位置信息给客户端进行展示
首先我们需要创建一个工作控件service_example同时创建src文件夹用于存放功能包
创建服务接口,用于客户端和服务端交互数据,所需依赖除了解析消息接口的依赖还需要sensor_msgs用于传递人脸图片,注意消息接口的功能包需要用到C++的CMakeLists.txt文件进行构建
# 人脸图像
sensor_msgs/Image image
---
# 人脸个数
int16 number
# 识别耗时
float32 use_time
# 人脸框的四个点
int32[] top
int32[] right
int32[] bottom
int32[] left
在CMakeLists.txt文件加上对应的转换函数
rosidl_generate_interfaces(${PROJECT_NAME}
"srv/FaceInterfaces.srv"
DEPENDENCIES sensor_msgs
)
构建得到相应的服务接口
创建客户服务端功能包,里面创建两个节点,一个客户端节点,一个服务端节点
import rclpy
from rclpy.node import Node
from face_interfaces.srv import FaceInterfaces
import face_recognition
import cv2
from ament_index_python.packages import get_package_share_directory
import os
from cv_bridge import CvBridge
import time
class FaceDetectClientNode(Node):
def __init__(self):
super().__init__('face_detect_client_node')
# 用于 OpenCV 图像 ↔ ROS 图像消息的转换,因为ROS接口里面的图像类型和OpenCV的图像类型并不相同
self.bridge = CvBridge()
self.client = self.create_client(FaceInterfaces,'face_detect')
self.get_logger().info('face detect client start')
# 获取照片的绝对路径
self.face_image_path = os.path.join(get_package_share_directory('demo_python_service'),'resource/face.jpg')
self.image = cv2.imread(self.face_image_path)
#等待服务启动->构建请求->异步发送请求不堵塞线程->future作为容器存放响应此时为空->当获取响应时自动调用回调函数然后将响应存入future
def send_request(self):
# 每搁一秒询问服务死否启动,否则等待
while self.client.wait_for_service(timeout_sec = 1.0) is False:
self.get_logger().info('wait service start')
# 获取服务接口的请求数据结构
request = FaceInterfaces.Request()
# 图像结构转换
request.image =self.bridge.cv2_to_imgmsg(self.image)
# 异步发送请求,返回一个未来的响应,不能直接用它的值,但可以给它加回调函数,当结果返回时就会触发
future=self.client.call_async(request)
# 回调函数,响应时触发
future.add_done_callback(self.result_callback)
def result_callback(self,future):
response =future.result()
self.get_logger().info(f'face number :{response.number},use time : {response.use_time}')
self.show_response(response)
def show_response(self,response):
# 为每个人脸描绘出边框
for i in range(response.number):
top=response.top[i]
right =response.right[i]
bottom= response.bottom[i]
left=response.left[i]
cv2.rectangle(self.image,(left,top),(right,bottom),(255,0,0),4)
cv2.imshow('Face Detecte Result',self.image)
cv2.waitKey(0)
def main():
rclpy.init()
node = FaceDetectClientNode()
node.send_request()
rclpy.spin(node)
rclpy.shutdown()
import rclpy
from rclpy.node import Node
from face_interfaces.srv import FaceInterfaces
import face_recognition
import cv2
from ament_index_python.packages import get_package_share_directory
import os
from cv_bridge import CvBridge
import time
class FaceDetectServiceNode(Node):
def __init__(self):
super().__init__('face_detect_service_node')
#创建了一个服务
self.service = self.create_service(FaceInterfaces,'face_detect',self.face_detect_callback)
self.bridge = CvBridge()
# 人脸检测模型的参数
self.number_of_times_to_upsampl = 1
self.model = 'hog'
self.face_image_path = os.path.join(get_package_share_directory('demo_python_service'),'resource/face.jpg')
self.get_logger().info('face detect service start')
def face_detect_callback(self,request,response):
# 判断客户端给的图像是否为空
if request.image.data:
cv_image= self.bridge.imgmsg_to_cv2(request.image)
self.get_logger().warn('image is null,use defacult')
else:
cv_image = cv2.imread(self.face_image_path)
self.get_logger().info('image is finishing')
# 记录开始检测的时间
start_time = time.time()
face_locations = face_recognition.face_locations(cv_image,number_of_times_to_upsample=self.number_of_times_to_upsampl,model=self.model)
# 计算时间差
response.use_time = time.time() - start_time
# 数据赋值
response.number = len(face_locations)
for top,right,bottom,left in face_locations:
response.top.append(top)
response.right.append(right)
response.bottom.append(bottom)
response.left.append(left)
return response
def main():
rclpy.init()
node = FaceDetectServiceNode()
rclpy.spin(node)
rclpy.shutdown()
C++实现海龟巡逻服务
客户端随机生成一些目标点,让海龟去巡逻,服务端接收到这些目标点控制海龟去这些目标点,我们直接使用之前学习话题的代码进行控制
服务接口结构
float32 target_x
float32 target_y
---
int8 SUCESS=1
int8 FAIL=0
int8 result #结果,SUCESS/│FAIL取其一
创建服务端和客户端
#include <rclcpp/rclcpp.hpp>
#include <turtlesim/msg/pose.hpp>
#include <geometry_msgs/msg/twist.hpp>
#include <partol_interfaces/srv/partol.hpp>
using namespace std;
using namespace rclcpp;
using Partol = partol_interfaces::srv::Partol;
class TurtleClientNode:public Node
{
private:
Client<Partol>::SharedPtr partol_client;
TimerBase::SharedPtr timer;
public:
explicit TurtleClientNode(const string& node_name):Node(node_name){
partol_client = this->create_client<Partol>("partol");
// 随机数种子
srand(time(NULL));
//创建一个 10 秒周期的定时器,每次定时器触发时,先检查服务是否可用,如果服务端还没启动,循环等待,如果整个 ROS2 系统关闭了,就报错退出
timer = this->create_wall_timer(10s,[&]()->void{
while(!this->partol_client->wait_for_service(1s)){
if(!rclcpp::ok()){
RCLCPP_ERROR(this->get_logger(),"wait service,rclcpp is over");
}
RCLCPP_INFO(this->get_logger(),"wait service.........");
}
// 随机生成目标地点坐标
auto request = std::make_shared<Partol::Request>();
request->set__target_x(rand()%15);
request->set__target_y(rand()%15);
RCLCPP_INFO(this->get_logger(),"create target point(%f,%f)",request->target_x,request->target_y);
// 异步请求,这样不需要一直等待响应而什么都不干,可以在等待响应的过程中进行其他操作
this->partol_client->async_send_request(request,[&](Client<Partol>::SharedFuture future)->void{
auto response = future.get();
if(response->result == Partol::Response::SUCESS){
RCLCPP_INFO(this->get_logger(),"turtle is success");
}
else{
RCLCPP_ERROR(this->get_logger(),"turtle is fail");
}
});
});
}
};
int main(int argc,char** argv){
init(argc,argv);
auto node = make_shared<TurtleClientNode>("turtle_client");
RCLCPP_INFO(node->get_logger(),"turtle_client start");
spin(node);
shutdown();
return 0;
}
#include <rclcpp/rclcpp.hpp>
#include <turtlesim/msg/pose.hpp>
#include <geometry_msgs/msg/twist.hpp>
#include <partol_interfaces/srv/partol.hpp>
using namespace std;
using namespace rclcpp;
using Partol = partol_interfaces::srv::Partol;
class TurtleServiceNode:public Node
{
private:
Service<Partol>::SharedPtr service;
Publisher<geometry_msgs::msg::Twist>::SharedPtr publisher;
Subscription<turtlesim::msg::Pose>::SharedPtr subsciber;
double target_x {1.0};
double target_y {1.0};
double k {1.0};
double max_speed {3.0};
public:
explicit TurtleServiceNode(const string& node_name):Node(node_name){
service = this->create_service<Partol>("partol",[&](const Partol::Request::SharedPtr request,Partol::Response::SharedPtr response) -> void{
if(
(request->target_x>0 && request->target_x<12.0f)&&
(request->target_y>0 && request->target_y<12.0f)
){
this->target_x = request->target_x;
this->target_y = request->target_y;
response->result = Partol::Response::SUCESS;
}
else{
response->result = Partol::Response::FAIL;
}
});
publisher = this->create_publisher<geometry_msgs::msg::Twist>("/turtle1/cmd_vel", 10);
subsciber = this->create_subscription<turtlesim::msg::Pose>("/turtle1/pose", 10,bind(&TurtleServiceNode::pose_callback,this,placeholders::_1));
}
void pose_callback(const turtlesim::msg::Pose::SharedPtr pose){
auto current_x = pose->x;
auto current_y = pose->y;
RCLCPP_INFO(this->get_logger(),"x=%f y=%f \n",current_x,current_y);
auto distance = sqrt((target_x-current_x)*(target_x-current_x)+(target_y-current_y)*(target_y-current_y));
auto angle = atan2((target_y-current_y),(target_x-current_x)) - pose->theta;
auto msg = geometry_msgs::msg::Twist();
if(distance>0.1){
if(fabs(angle)>0.2){
msg.angular.z = fabs(angle);
}else{
msg.linear.x = k*distance;
}
}
if(msg.linear.x > max_speed){
msg.linear.x = max_speed;
}
publisher->publish(msg);
}
};
int main(int argc,char** argv){
init(argc,argv);
auto node = make_shared<TurtleServiceNode>("turtle_service");
RCLCPP_INFO(node->get_logger(),"turtle_service start");
spin(node);
shutdown();
return 0;
}
cmake_minimum_required(VERSION 3.8)
project(demo_cpp_service)
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
add_compile_options(-Wall -Wextra -Wpedantic)
endif()
# find dependencies
find_package(ament_cmake REQUIRED)
find_package(partol_interfaces REQUIRED)
find_package(rclcpp REQUIRED)
find_package(geometry_msgs REQUIRED)
find_package(turtlesim REQUIRED)
add_executable(turtleService src/turtle_service.cpp)
add_executable(turtleClient src/turtle_client.cpp)
ament_target_dependencies(turtleService partol_interfaces rclcpp geometry_msgs turtlesim)
ament_target_dependencies(turtleClient partol_interfaces rclcpp geometry_msgs turtlesim)
if(BUILD_TESTING)
find_package(ament_lint_auto REQUIRED)
# the following line skips the linter which checks for copyrights
# comment the line when a copyright and license is added to all source files
set(ament_cmake_copyright_FOUND TRUE)
# the following line skips cpplint (only works in a git repo)
# comment the line when this package is in a git repo and when
# a copyright and license is added to all source files
set(ament_cmake_cpplint_FOUND TRUE)
ament_lint_auto_find_test_dependencies()
endif()
install(TARGETS turtleService turtleClient
DESTINATION lib/${PROJECT_NAME}
)
ament_package()
先启动海龟模拟强,再启动客户端,然后启动服务端
参数设置
Python的人脸检测将模型的参数由ros的参数机制进行设置,受用命令行中的--ros-args -p 参数名:=参数值
self.declare_parameter('number_of_times_to_upsampl',1)
self.declare_parameter('model','hog')
self.number_of_times_to_upsampl = self.get_parameter('number_of_times_to_upsampl').value
self.model= self.get_parameter('model').value
订阅参数更新实现,更改服务端代码
def __init__(self):
super().__init__('face_detect_service_node')
self.service = self.create_service(FaceInterfaces,'face_detect',self.face_detect_callback)
self.bridge = CvBridge()
self.declare_parameter('number_of_times_to_upsampl',1)
self.declare_parameter('model','hog')
self.number_of_times_to_upsampl = self.get_parameter('number_of_times_to_upsampl').value
self.model= self.get_parameter('model').value
self.face_image_path = os.path.join(get_package_share_directory('demo_python_service'),'resource/face.jpg')
self.add_on_set_parameters_callback(self.parameters_callback)
self.get_logger().info('face detect service start')
def parameters_callback(self,paramters):
for paramter in paramters:
self.get_logger().info(f"{paramter.name}->{paramter.value}")
if paramter.name =='number_of_times_to_upsample':
self.number_of_times_to_upsample = paramter.value
if paramter.name== 'model':
self.model = paramter.value
return SetParametersResult('success')
在 ROS2 中,节点只要用到参数机制(比如声明参数),就会自动生成一系列 参数相关的服务,可以通过 ros2 service list 命令查看,不过需要先启动节点。
我编写客户端代码,调用其自带的参数设置服务set_Parameters进行参数的设置,其服务接口为rcl_interfaces/srv/SetParameters,可以看到其消息接口本质上是一个复合接口,除了基本的数据结构还依赖其他的消息接口, 因此在构建的时候需要加上其他消息接口的依赖,注意消息接口中调用其他消息接口只需要包名/接口名
这种形式。
import rclpy
from rclpy.node import Node
from face_interfaces.srv import FaceInterfaces
import face_recognition
import cv2
from ament_index_python.packages import get_package_share_directory
import os
from cv_bridge import CvBridge
import time
from rcl_interfaces.msg import SetParametersResult
class FaceDetectServiceNode(Node):
def __init__(self):
super().__init__('face_detect_service_node')
self.service = self.create_service(FaceInterfaces,'face_detect',self.face_detect_callback)
self.bridge = CvBridge()
self.declare_parameter('number_of_times_to_upsampl',1)
self.declare_parameter('model','hog')
self.number_of_times_to_upsampl = self.get_parameter('number_of_times_to_upsampl').value
self.model= self.get_parameter('model').value
self.face_image_path = os.path.join(get_package_share_directory('demo_python_service'),'resource/face.jpg')
self.add_on_set_parameters_callback(self.parameters_callback)
self.get_logger().info('face detect service start')
# 获取客户端传来的参数并进行设置
def parameters_callback(self,paramters):
for paramter in paramters:
self.get_logger().info(f"{paramter.name}->{paramter.value}")
if paramter.name =='number_of_times_to_upsample':
self.number_of_times_to_upsample = paramter.value
if paramter.name== 'model':
self.model = paramter.value
return SetParametersResult(successful=True)
def face_detect_callback(self,request,response):
if request.image.data:
cv_image= self.bridge.imgmsg_to_cv2(request.image)
self.get_logger().warn('image is null,use defacult')
else:
cv_image = cv2.imread(self.face_image_path)
self.get_logger().info('image is finishing')
start_time = time.time()
face_locations = face_recognition.face_locations(cv_image,number_of_times_to_upsample=self.number_of_times_to_upsampl,model=self.model)
response.use_time = time.time() - start_time
response.number = len(face_locations)
for top,right,bottom,left in face_locations:
response.top.append(top)
response.right.append(right)
response.bottom.append(bottom)
response.left.append(left)
return response
def main():
rclpy.init()
node = FaceDetectServiceNode()
rclpy.spin(node)
rclpy.shutdown()
import rclpy
from rclpy.node import Node
from face_interfaces.srv import FaceInterfaces
import face_recognition
import cv2
from ament_index_python.packages import get_package_share_directory
import os
from cv_bridge import CvBridge
import time
from rcl_interfaces.srv import SetParameters
from rcl_interfaces.msg import Parameter,ParameterType,ParameterValue
class FaceDetectClientNode(Node):
def __init__(self):
super().__init__('face_detect_client_node')
self.bridge = CvBridge()
self.client = self.create_client(FaceInterfaces,'face_detect')
self.get_logger().info('face detect client start')
self.face_image_path = os.path.join(get_package_share_directory('demo_python_service'),'resource/face.jpg')
self.image = cv2.imread(self.face_image_path)
print(type(self.image))
print(self.image is None)
# 设置模型的客户端
def call_set_parameters(self,parameters):
update_param_client = self.create_client(SetParameters,'/face_detect_service_node/set_parameters')
while update_param_client.wait_for_service(timeout_sec=1.0) is False:
self.get_logger().info('wait parameters service!')
request = SetParameters.Request()
request.parameters = parameters
future = update_param_client.call_async(request)
rclpy.spin_until_future_complete(self,future)
response = future.result()
return response
# 更新模型的参数
def update_detect_model(self,model='hog'):
param= Parameter()
param.name ='model'
param_value = ParameterValue()
param_value.string_value = model
param_value.type =ParameterType.PARAMETER_STRING
param.value = param_value
response = self.call_set_parameters([param])
for result in response.results:
self.get_logger().info(f"result:{result.successful}{result.reason}")
def send_request(self):
while self.client.wait_for_service(timeout_sec = 1.0) is False:
self.get_logger().info('wait service start')
request = FaceInterfaces.Request()
request.image =self.bridge.cv2_to_imgmsg(self.image)
future=self.client.call_async(request)
future.add_done_callback(self.result_callback)
def result_callback(self,future):
response =future.result()
self.get_logger().info(f'face number :{response.number},use time : {response.use_time}')
self.show_response(response)
def show_response(self,response):
for i in range(response.number):
top=response.top[i]
right =response.right[i]
bottom= response.bottom[i]
left=response.left[i]
cv2.rectangle(self.image,(left,top),(right,bottom),(255,0,0),4)
# cv2.imshow('Face Detecte Result',self.image)
# cv2.waitKey(0)
def main():
rclpy.init()
node = FaceDetectClientNode()
node.update_detect_model('hog')
node.send_request()
node.update_detect_model('cnn')
node.send_request()
rclpy.spin(node)
rclpy.shutdown()
C++使用参数节点
使用参数机制设置小海龟的比例系数和最大速度,代码都是差不多的就不过多写了
this->declare_parameter("k",k);
this->declare_parameter("max_speed",max_speed);
this->get_parameter("k",k);
this->get_parameter("max_speed",max_speed);
this->param_callback_handle = this->add_on_set_parameters_callback([&](const vector<Parameter> ¶meters)-> rcl_interfaces::msg::SetParametersResult{
rcl_interfaces::msg::SetParametersResult result;
result.successful=true;
for (const auto & parameter : parameters){
RCLCPP_INFO(this->get_logger(),"update param %s=%f",parameter.get_name().c_str(),parameter.as_double());
if(parameter.get_name()=="k")
k = parameter.as_double();
if(parameter.get_name()=="max_speed")
max_speed = parameter.as_double();
}
return result;
});
ROS2 Launch
Launch 是 ROS2 的启动管理系统,用来一次性启动和配置多个节点及其参数,一般使用的是python的语言
Node(
package='turtlesim', # 节点所在功能包
executable='turtlesim_node', # 要运行的可执行文件
name='sim', # 节点名称(相当于 ros2 run ... --ros-args -r __node:=sim)
namespace='my_ns', # 节点命名空间
output='screen', # 日志输出方式:screen (终端显示) / log (保存到日志文件)
emulate_tty=True, # 保证输出有颜色(常用于 Docker/终端)
parameters=[ # 节点参数
{'background_r': 255, 'background_g': 255, 'background_b': 255},
'/path/to/config.yaml'
],
remappings=[ # 话题重映射
('/turtle1/cmd_vel', '/cmd_vel'),
('/turtle1/pose', '/pose')
],
arguments=['--ros-args', '--log-level', 'INFO'], # 额外命令行参数
ros_arguments=['--remap', '__ns:=/robot1'], # 专门的 ROS 参数(新版本推荐)
respawn=True, # 节点崩溃时是否自动重启
respawn_delay=2.0, # 重启延迟时间(秒)
prefix='gdb -ex run --args', # 前缀命令,比如用 gdb/valgrind 启动节点
cwd='ros_home', # 设置工作目录
env={'MY_ENV_VAR': 'value'} # 传递环境变量
)
我们使用launch同时启动客户端和服务端两个节点
from launch import LaunchDescription
from launch_ros.actions import Node
def generate_launch_description():
return LaunchDescription([
Node(
package='demo_python_service', # 功能包名
executable='faceDetectServiceNode', # 服务端入口
name='face_detect_service_node',
output='screen'
),
Node(
package='demo_python_service',
executable='faceDetectClientNode', # 客户端入口
name='face_detect_client_node',
output='screen'
),
])
同时可以在launch中声明参数,但如果需要给节点的话需要一个转换
from launch import LaunchDescription
from launch.actions import DeclareLaunchArgument
from launch.substitutions import LaunchConfiguration
from launch_ros.actions import Node
def generate_launch_description():
return LaunchDescription([
# 声明参数
DeclareLaunchArgument(
'model',
default_value='hog',
description='Face detect model: hog or cnn'
),
DeclareLaunchArgument(
'upsample',
default_value='1',
description='Number of times to upsample'
),
# 启动服务端
Node(
package='demo_python_service',
executable='faceDetectServiceNode',
name='face_service',
parameters=[{
'model': LaunchConfiguration('model'),
'number_of_times_to_upsample': LaunchConfiguration('upsample')
}]
),
# 启动客户端
Node(
package='demo_python_service',
executable='faceDetectClientNode',
name='face_client'
)
])
launch 三大核心组件
-
LaunchDescription(启动描述)
-
作用:是整个 Launch 文件的入口,用来组织和返回所有要执行的动作。
-
特点:所有节点、参数、事件处理器等都需要被封装到
LaunchDescription([...])
中。
-
-
Action(动作)
-
作用:定义实际要做的事情,比如启动节点(
Node
)、声明参数(DeclareLaunchArgument
)、设置环境变量(SetEnvironmentVariable
)等。 -
特点:是 launch 文件中真正会被执行的“任务单元”。
-
-
Substitution(替换/变量)
-
作用:在运行时动态生成或替换值,比如包路径(
FindPackageShare
)、命令行参数(LaunchConfiguration
)、环境变量(EnvironmentVariable
)等。 -
特点:为 Action 提供灵活的参数输入。
-
LaunchDescription 管理 → Action 执行 → Substitution 提供动态参数。