3.8 通信机制实操
本节主要介绍通信机制相关的一些练习,这些练习基于turtlesim功能包,练习类型覆盖了话题、服务、动作、参数这四种通信机制。
准备
终端下进入工作空间的src目录,调用如下命令创建C++功能包。
ros2 pkg create cpp07_exercise --build-type ament_cmake --dependencies rclcpp turtlesim base_interfaces_demo geometry_msgs rclcpp_action
功能包下新建launch目录以备用。
3.8.1 话题通信案例分析
1.案例需求
需求:启动两个turtlesim_node节点,节点2中的乌龟自动调头180°,我们可以通过键盘控制节点1中的乌龟运动,但是不能控制节点2的乌龟,需要自实现功能:可以根据乌龟1的速度生成并发布控制乌龟2运动的速度指令,最终两只乌龟做镜像运动。

2.案例分析
在上述案例中,主要需要关注的问题有三个:
如何创建两个turtlesim_node节点,且需要具有不同的节点名称、话题名称。
如何控制乌龟掉头?
核心实现是如何订阅乌龟1的速度并生成发布控制乌龟2运动的速度指令的?
思路:
问题1我们可以通过为turtlesim_node设置namespace解决;
问题2可以通过调用turtlesim_node内置的action功能来实现乌龟航向的设置;
问题3是整个案例的核心,需要编码实现,需要订阅乌龟1的位姿相关话题来获取乌龟1的速度,并且按照“镜像运动”的需求生成乌龟2的速度指令,并且该节点需要在掉头完毕后启动。
最后,整个案例涉及到多个节点,我们可以通过launch文件集成这些节点。
3.流程简介
主要步骤如下:
编写速度订阅与发布实现;
编写launch文件集成多个节点;
编辑配置文件;
编译;
执行。
3.8.2 话题通信实现
1.速度订阅与发布
功能包cpp07_exercise的src目录下,新建C++文件exe01_pub_sub.cpp,并编辑文件,输入如下内容:

2.launch文件
功能包cpp07_exercise的launch目录下,新建launch文件exe01_pub_sub.launch.py,并编辑文件,输入如下内容:
from launch import LaunchDescription
from launch_ros.actions import Node
from launch.actions import ExecuteProcess,RegisterEventHandler
from launch.event_handlers import OnProcessExit
def generate_launch_description():
# 1.创建两个 turtlesim_node 节点
t1 = Node(package="turtlesim",executable="turtlesim_node")
t2 = Node(package="turtlesim",executable="turtlesim_node",namespace="t2")
# 2.让第二只乌龟掉头
rotate = ExecuteProcess(
cmd=["ros2 action send_goal /t2/turtle1/rotate_absolute turtlesim/action/RotateAbsolute \"{'theta': 3.14}\""],
output="both",
shell=True
)
# 3.自实现的订阅发布实现
pub_sub = Node(package="cpp07_exercise",executable="exe01_pub_sub")
# 4.乌龟掉头完毕后,开始执行步骤3
rotate_exit_event = RegisterEventHandler(
event_handler=OnProcessExit(
target_action=rotate,
on_exit=pub_sub
)
)
return LaunchDescription([t1,t2,rotate,rotate_exit_event])
3.编辑配置文件
1.package.xml
在创建功能包时,所依赖的功能包已经自动配置了,配置内容如下:

2.CMakeLists.txt
CMakeLists.txt 中发布和订阅程序核心配置如下:

4.编译
终端中进入当前工作空间,编译功能包:
colcon build --packages-select cpp07_exercise
5.执行
当前工作空间下,启动终端输入如下指令:
. install/setup.bash
ros2 launch cpp07_exercise exe01_pub_sub.launch.py
指令执行后,将生成两个turtlesim_node节点对应的窗口,并且其中一个窗口的乌龟开始调头。
再启动一个终端,输入如下指令:
ros2 run turtlesim turtle_teleop_key
待乌龟调头完毕,就可以通过键盘控制乌龟运动了,最终运行结果与演示案例类似。
3.8.3 服务通信案例分析
1.案例需求
需求:在turtlesim_node节点的窗体中在指定位置生成一只新乌龟并可以输出两只乌龟之间的直线距离。

2.案例分析
在上述案例中,需要关注的问题有两个:
如何在指定位置生成一只新乌龟?
计算两只乌龟的距离应该使用何种通信模式又如何实现?
思路:
问题1可以通过调用turtlesim_node内置的名称为/spawn的服务功能来实现新乌龟的创建;
问题2可以通过服务通信来实现,客户端发送新生成的乌龟的位姿到服务端,服务端根据该坐标以及原生乌龟的坐标计算距离并响应。当然如果使用服务通信,还需要自定义服务接口。
最后,整个案例涉及到多个节点,我们可以通过launch文件集成这些节点。
3.流程简介
主要步骤如下:
编写服务接口文件;
编写服务端实现;
编写客户端实现;
编写launch文件;
编辑配置文件;
编译;
执行。
3.8.4 服务通信实现
1.服务接口文件
功能包base_interfaces_demo的srv目录下,新建srv文件Distance.srv,并编辑文件,输入如下内容:

2.服务端实现
功能包cpp07_exercise的src目录下,新建C++文件exe02_server.cpp,并编辑文件,输入如下内容:
/*
需求:处理请求发送的目标点,计算乌龟与目标点之间的直线距离。
步骤:
1.包含头文件;
2.初始化 ROS2 客户端;
3.定义节点类;
3-1.创建乌龟姿态订阅方,回调函数中获取x坐标与y坐标; 3-2.创建服务端;
3-3.解析目标值,计算距离并反馈结果。
4.调用spin函数,并传入节点对象指针;
5.释放资源。
*/
// 1.包含头文件;
using namespace std::chrono_literals;
// 3.定义节点类;
class ExeDistanceServer:
public rclcpp::Node {public:
ExeDistanceServer():Node("exe_distance_server"),turtle1_x(0.0),turtle1_y(0.0){ // 3-1.创建乌龟姿态订阅方,回调函数中获取x坐标与y坐标;
pose_sub = this->create_subscription<turtlesim::msg::Pose>("/turtle1/pose",10,std::bind(&ExeDistanceServer::poseCallBack, this, std::placeholders::_1));
// 3-2.创建服务端;
distance_server = this->create_service<base_interfaces_demo::srv::Distance>("distance",std::bind(&ExeDistanceServer::distanceCallBack, this, std::placeholders::_1, std::placeholders::_2));
}
private:
void poseCallBack(const turtlesim::msg::Pose::SharedPtr pose){ turtle1_x = pose->x;
turtle1_y = pose->y;
}
// 3-3.解析目标值,计算距离并反馈结果。
void distanceCallBack(const base_interfaces_demo::srv::Distance_Request::SharedPtr request,
base_interfaces_demo::srv::Distance_Response::SharedPtr response
){
// 解析目标值
float goal_x = request->x;
float goal_y = request->y;
// 距离计算
float x = goal_x - turtle1_x;
float y = goal_y - turtle1_y;
// 将结果设置到响应
response->distance = std::sqrt(x * x + y * y);
RCLCPP_INFO(this->get_logger(),"目标坐标:(%.2f,%.2f),距离:%.2f",goal_x,goal_y,response->distance);
}
rclcpp::Subscription<turtlesim::msg::Pose>::SharedPtr pose_sub;
rclcpp::Service<base_interfaces_demo::srv::Distance>::SharedPtr distance_server;
float turtle1_x;
float turtle1_y;
};
int main(int argc, char const *argv[])
{
// 2.初始化 ROS2 客户端;
rclcpp::init(argc,argv);
// 4.调用spin函数,并传入节点对象指针;
rclcpp::spin(std::make_shared<ExeDistanceServer>());
// 5.释放资源。
rclcpp::shutdown();
return 0;
}
3.客户端实现
功能包cpp07_exercise的src目录下,新建C++文件exe03_client.cpp,并编辑文件,输入如下内容:
……

B站有完整的ros系列教程视频,可以观看完整内容ros课程ROS2理论与实践
更多内容将在猛狮知识星球社区更新最新课程,后续将推出更多优质内容——详情可关注猛狮集训营公众号和猛狮集训营官方网站。