目录
- 0 专栏介绍
- 1 话题通信模型
- 2 话题模型实现(C++)
- 3 话题模型实现(Python)
- 4 自定义消息
0 专栏介绍
本专栏旨在通过对ROS2的系统学习,掌握ROS2底层基本分布式原理,并具有机器人建模和应用ROS2进行实际项目的开发和调试的工程能力。
🚀详情:《ROS2从入门到精通》
1 话题通信模型
话题是一种单向通信通道,数据或信息以定义的格式传递其中。机器人组件可以订阅主题以接收信息,或者发布信息到主题以与其他组件分享。其中发布信息的节点称为发布者,接受信息的节点称为订阅者
举个例子,想象一个移动机器人,其中有一个负责读取传感器数据的组件,另一个负责控制电机。传感器组件可以在名为sensor_readings
的话题中发布传感器读数信息。同时,控制组件可以订阅此话题以接收该信息并用于控制电机。
在 ROS 2 中,话题由中间件管理以确保消息可靠高效地传输。话题中的消息可以是任何可序列化的数据类型,例如整数、浮点数、字符串、矩阵等。
ROS 2 中的话题具有多对多通信的灵活性和可扩展性。例如,可以有多个组件订阅同一话题,或者多个组件发布数据到同一主题。此外,可以使用线程来连接单个机器人内部的组件,或者在网络中连接不同机器人之间的组件。
2 话题模型实现(C++)
实验目标:发布者发布控制消息到
/turtle1/cmd_vel
,控制乌龟其做圆周运动;订阅者订阅/turtle1/cmd_vel
,在终端显示乌龟实时的位置坐标。
-
发布者
class PublisherNode : public rclcpp::Node { public: PublisherNode() : Node("lab_topic_pub") { publisher_ = create_publisher<geometry_msgs::msg::Twist>("/turtle1/cmd_vel", 10); } void publish(geometry_msgs::msg::Twist msg) { publisher_->publish(msg); } private: rclcpp::Publisher<geometry_msgs::msg::Twist>::SharedPtr publisher_; }; int main(int argc, char * argv[]) { rclcpp::init(argc, argv); auto node = std::make_shared<PublisherNode>(); rclcpp::Rate loop_rate(10); while(rclcpp::ok()) { auto msg = geometry_msgs::msg::Twist(); msg.linear.x = 0.5; msg.angular.z = 0.2; node->publish(msg); RCLCPP_INFO(node->get_logger(), "Publishing: x: %.2f, z: %.2f", msg.linear.x, msg.angular.z); loop_rate.sleep(); } rclcpp::shutdown(); return 0; }
-
订阅者(订阅的话题存在消息即触发回调函数)
class SubscriberNode : public rclcpp::Node { public: SubscriberNode() : Node("lab_topic_sub") { subscriber_ = create_subscription<geometry_msgs::msg::Twist>( "/turtle1/cmd_vel", 10, std::bind(&SubscriberNode::OnPoseCallback, this, _1)); } private: void OnPoseCallback(const geometry_msgs::msg::Twist & msg) const { RCLCPP_INFO(get_logger(), "Publishing: x: %.2f, z: %.2f", msg.linear.x, msg.angular.z); } rclcpp::Subscription<geometry_msgs::msg::Twist>::SharedPtr subscriber_; }; int main(int argc, char * argv[]) { rclcpp::init(argc, argv); rclcpp::spin(std::make_shared<SubscriberNode>()); rclcpp::shutdown(); return 0; }
话题通信的效果如下所示:
计算图可视化为:
3 话题模型实现(Python)
实验目标:发布者发布控制消息到
/turtle1/cmd_vel
,控制乌龟其做圆周运动;订阅者订阅/turtle1/cmd_vel
,在终端显示乌龟实时的位置坐标。
- 发布者
class PublisherNode(Node): def __init__(self, name): super().__init__(name) self.publisher_ = self.create_publisher(Twist, '/turtle1/cmd_vel', 10) def publish(self, msg: Twist): self.publisher_.publish(msg)
- 订阅者
class SubscriberNode(Node): def __init__(self, name): super().__init__(name) self.subscirber_ = self.create_subscription( Twist, '/turtle1/cmd_vel', self.OnPoseCallback, 10 ) def OnPoseCallback(self, msg): self.get_logger().info(f"Publishing: x: {msg.linear.x:.2f}, z: {msg.angular.z:.2f}") def main(args=None): rclpy.init(args=args) node = SubscriberNode("lab_topic_sub") rclpy.spin(node) node.destroy_node() rclpy.shutdown()
话题通信的效果如下所示:
4 自定义消息
ROS2系统通过std_msgs
封装了一些常用的原生数据类型,比如String
、Int32
、Int64
等,对于一些复杂数据应用场景,往往需要在std_msgs
或其他消息库的基础上继续封装更高级的数据类型
自定义消息的通用流程如下:
- 功能包下新建
msg
文件夹,在其中添加自定义消息xxx.msg
- 功能包
package.xml
中添加编译依赖与执行依赖<buildtool_depend>rosidl_default_generators</buildtool_depend> <exec_depend>rosidl_default_runtime</exec_depend> <member_of_group>rosidl_interface_packages</member_of_group>
- 功能包
CMakeLists.txt
中添加编译消息相关依赖find_package(rosidl_default_generators REQUIRED) rosidl_generate_interfaces(${PROJECT_NAME} "xxx.msg" DEPENDENCIES xxx_msgs ) ament_export_dependencies(rosidl_default_runtime)
- 编译自定义消息,在
install/<pkg_name>/include
中生成由xxx.msg
编译的C++可识别的xxx.hpp
头文件- 引入
xxx.hpp
即可调用自定义消息
下面给出一个实例:
添加如下自定义消息,并按上面步骤配置依赖
# Person.msg
string name
string gender
uint8 age
geometry_msgs/Point position
float64 x
float64 y
float64 z
定义一个发布者、一个订阅者测试自定义消息
- 发布者
// 初始化名为personPub的ROS节点, 该节点应在CMakeLists.txt中被构建为可执行文件 ros::init(argc, argv, "personPub"); // 创建节点句柄 ros::NodeHandle pubNode; // 创建发布者, 该发布者属于pubNode节点, 发布话题为"/person/info", // 消息类型为"msgtest::Person", 发布队列长度为10 ros::Publisher pub = pubNode.advertise<msg_lab::Person>("/person/info", 10); // 设置发布频率 ros::Rate loopRate(10); while(ros::ok()) { // 设置消息 msg_lab::Person msg; msg.name = "winter"; msg.gender = "man"; msg.age = 20; msg.position.x = 10; msg.position.y = 20; msg.position.z = 30; // 发布消息 pub.publish(msg); ROS_INFO("Publish Person Info[name: %s gender: %s age: %d pos: x-%.2f y-%.2f z-%.2f]", msg.name.c_str(), msg.gender.c_str(), msg.age, msg.position.x, msg.position.y, msg.position.z); // 按循环频率延时 loopRate.sleep(); }
- 订阅者
void personInfoCallBack(const msg_lab::Person::ConstPtr &msg) { ROS_INFO("Subscribe Person Info[name: %s gender: %s age: %d pos: x-%.2f y-%.2f z-%.2f]", msg->name.c_str(), msg->gender.c_str(), msg->age, msg->position.x, msg->position.y, msg->position.z); } int main(int argc, char** argv) { // 初始化名为personSub的ROS节点, 该节点应在CMakeLists.txt中被构建为可执行文件 ros::init(argc, argv, "personSub"); // 创建节点句柄 ros::NodeHandle subNode; // 创建订阅者, 该订阅者属于subNode节点, 订阅话题为"/person/info", // 订阅队列长度为10, 收到订阅消息后出发回调函数personInfoCallBack ros::Subscriber sub = subNode.subscribe("/person/info", 10, personInfoCallBack); // 循环等待回调函数 ros::spin(); return 0; }
实测效果如下:
完整代码通过下方博主名片联系获取
🔥 更多精彩专栏:
- 《ROS从入门到精通》
- 《Pytorch深度学习实战》
- 《机器学习强基计划》
- 《运动规划实战精讲》
- …