把代码修改成python版本:
#include <chrono>
#include <functional>
#include <memory>
#include <string>
#include <rclcpp/rclcpp.hpp>
#include <geometry_msgs/msg/twist.hpp>
using namespace std::chrono_literals;
class TurtlePublisher : public rclcpp::Node
{
public:
TurtlePublisher()
: Node("turtle_publisher")
{
// 创建 Publisher:发布到 "/turtle1/cmd_vel",队列大小10
publisher_ = this->create_publisher<geometry_msgs::msg::Twist>("/turtle1/cmd_vel", 10);
// 创建定时器:每500ms调用一次回调函数
timer_ = this->create_wall_timer(
500ms, std::bind(&TurtlePublisher::timer_callback, this));
}
private:
void timer_callback()
{
auto message = geometry_msgs::msg::Twist();
message.linear.x = 2.0; // 前进速度
message.angular.z = 1.0; // 旋转速度 → 画圆!
RCLCPP_INFO(this->get_logger(), "Publishing: linear=%.2f, angular=%.2f",
message.linear.x, message.angular.z);
publisher_->publish(message);
}
rclcpp::TimerBase::SharedPtr timer_;
rclcpp::Publisher<geometry_msgs::msg::Twist>::SharedPtr publisher_;
};
int main(int argc, char * argv[])
{
rclcpp::init(argc, argv); // 初始化 ROS 2
rclcpp::spin(std::make_shared<TurtlePublisher>()); // 进入事件循环
rclcpp::shutdown(); // 关闭
return 0;
}
下面给出与 C++ 版本 行为完全一致 的 Python 实现(ROS 2 rclpy)。
保存为
保存为
turtle_publisher.py,放到 src/ 目录即可,CMakeLists.txt 里把 turtle_publisher 也改成 Python 安装即可使用。Python
复制
#!/usr/bin/env python3
import rclpy
from rclpy.node import Node
from geometry_msgs.msg import Twist # 等价于 C++ 的 geometry_msgs/msg/twist.hpp
class TurtlePublisher(Node):
def __init__(self):
super().__init__('turtle_publisher') # 节点名
# 创建 Publisher:话题 /turtle1/cmd_vel,队列 10
self.publisher_ = self.create_publisher(Twist, '/turtle1/cmd_vel', 10)
# 创建定时器:每 500 ms 触发一次 timer_callback
self.timer = self.create_timer(0.5, self.timer_callback)
def timer_callback(self):
msg = Twist()
msg.linear.x = 2.0 # 前进速度
msg.angular.z = 1.0 # 旋转速度 → 画圆!
self.get_logger().info(f'Publishing: linear={msg.linear.x:.2f}, angular={msg.angular.z:.2f}')
self.publisher_.publish(msg)
def main(args=None):
rclpy.init(args=args) # 初始化 ROS 2
node = TurtlePublisher()
rclpy.spin(node) # 保持节点运行,直到 Ctrl-C
node.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()与 C++ 关键差异对照(帮你快速迁移)
| C++ | Python |
|---|---|
rclcpp::Node | rclpy.node.Node |
create_publisher<T>(...) | self.create_publisher(T, ...) |
create_wall_timer(500ms, ...) | self.create_timer(0.5, ...) 单位秒 |
RCLCPP_INFO(...) | self.get_logger().info(...) |
rclcpp::spin(node) | rclpy.spin(node) |
使用步骤(Python 版)
- 给脚本加可执行权限bash复制
chmod +x ~/ros2_ws/src/pub_sub/src/turtle_publisher.py - 在
CMakeLists.txt里再加一行(或把原来的 C++ 二进制换成这个)cmake复制install(PROGRAMS src/turtle_publisher.py DESTINATION lib/${PROJECT_NAME} RENAME turtle_publisher) - 重新构建bash复制
cd ~/ros2_ws colcon build --packages-select pub_sub source install/setup.bash - 运行(launch 文件不用改,可执行名仍是
turtle_publisher)bash复制ros2 launch pub_sub turtle_circle.launch.py
现在你就拥有 纯 Python 实现 的“画圆”节点了!
把这个改成c++版本:
#!/usr/bin/env python3
import rclpy
from rclpy.node import Node
from turtlesim.msg import Pose # 注意:turtlesim 自定义消息
class TurtleSubscriber(Node):
def __init__(self):
super().__init__('turtle_subscriber')
# 创建 Subscriber:订阅 "/turtle1/pose"
self.subscription = self.create_subscription(
Pose, # 消息类型
'/turtle1/pose', # 话题名
self.listener_callback, # 回调函数
10) # 队列大小
self.subscription # 防止被 Python 垃圾回收
def listener_callback(self, msg):
# msg 是 Pose 类型,包含 x, y, theta
self.get_logger().info(f'Received pose: x={msg.x:.2f}, y={msg.y:.2f}, theta={msg.theta:.2f}')
def main(args=None):
rclpy.init(args=args)
node = TurtleSubscriber()
rclpy.spin(node) # 进入事件循环
node.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()
下面给出与 Python 版本行为完全一致的 C++ 实现(ROS 2 rclcpp)。
保存为
保存为
turtle_subscriber.cpp,放到 src/ 目录即可。cpp
复制
#include <rclcpp/rclcpp.hpp>
#include <turtlesim/msg/pose.hpp> // 对应 Python 的 turtlesim.msg.Pose
class TurtleSubscriber : public rclcpp::Node
{
public:
TurtleSubscriber()
: Node("turtle_subscriber")
{
// 创建 Subscriber:话题 /turtle1/pose,队列 10
subscription_ = this->create_subscription<turtlesim::msg::Pose>(
"/turtle1/pose",
10,
std::bind(&TurtleSubscriber::listener_callback, this, std::placeholders::_1));
}
private:
// 回调函数,参数是 Pose 消息的智能指针
void listener_callback(const turtlesim::msg::Pose::SharedPtr msg)
{
RCLCPP_INFO(this->get_logger(),
"Received pose: x=%.2f, y=%.2f, theta=%.2f",
msg->x, msg->y, msg->theta);
}
rclcpp::Subscription<turtlesim::msg::Pose>::SharedPtr subscription_;
};
int main(int argc, char * argv[])
{
rclcpp::init(argc, argv); // 初始化 ROS 2
rclcpp::spin(std::make_shared<TurtleSubscriber>()); // 进入事件循环
rclcpp::shutdown(); // 清理
return 0;
}与 Python 关键差异对照
| Python | C++ |
|---|---|
create_subscription(Pose, ..., callback, 10) | create_subscription<turtlesim::msg::Pose>(..., 绑定回调, 10) |
self.get_logger().info(f"...") | RCLCPP_INFO(this->get_logger(), "...") |
rclpy.spin(node) | rclcpp::spin(std::make_shared<Node>()) |
使用步骤(C++ 版)
- 把文件放进
~/ros2_ws/src/pub_sub/src/turtle_subscriber.cpp - 在
CMakeLists.txt里加可执行节点与安装规则(已存在可跳过):
cmake
复制
# 找到 turtlesim 消息包
find_package(turtlesim REQUIRED)
add_executable(turtle_subscriber src/turtle_subscriber.cpp)
ament_target_dependencies(turtle_subscriber rclcpp turtlesim)
install(TARGETS turtle_subscriber
DESTINATION lib/${PROJECT_NAME})- 构建 & 运行
bash
复制
cd ~/ros2_ws
colcon build --packages-select pub_sub
source install/setup.bash
ros2 run pub_sub turtle_subscriber现在你就拥有 纯 C++ 实现 的“打印海龟位姿”节点了!
我现在有四个源文件了,分别是publisher和subscriber的c++和python实现。cmakelists和构建运行会不会混乱?
不会乱,只要告诉 CMakeLists.txt “谁是谁、怎么装” 就行。
下面给你一份可直接套用的模板,把 4 个节点全部装进同一个
下面给你一份可直接套用的模板,把 4 个节点全部装进同一个
pub_sub 包,互不冲突。1. 文件清单(放对位置即可)
plain
复制
pub_sub/
├── CMakeLists.txt
├── package.xml
├── launch/
│ └── turtle_circle.launch.py # 可四选一或全开
├── src/
│ ├── turtle_publisher.cpp # C++ 画圆
│ ├── turtle_subscriber.cpp # C++ 打印位姿
│ ├── turtle_publisher.py # Python 画圆
│ └── turtle_subscriber.py # Python 打印位姿2. CMakeLists.txt(一份管4个节点)
cmake
复制
cmake_minimum_required(VERSION 3.8)
project(pub_sub)
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
add_compile_options(-Wall -Wextra -Wpedantic)
endif()
# ---------- 依赖 ----------
find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(rclpy REQUIRED)
find_package(geometry_msgs REQUIRED)
find_package(turtlesim REQUIRED) # 为了 Pose 消息
# ---------- C++ 节点 ----------
add_executable(turtle_publisher_cpp src/turtle_publisher.cpp)
add_executable(turtle_subscriber_cpp src/turtle_subscriber.cpp)
ament_target_dependencies(turtle_publisher_cpp rclcpp geometry_msgs)
ament_target_dependencies(turtle_subscriber_cpp rclcpp turtlesim)
# ---------- Python 节点 ----------
find_package(ament_cmake_python REQUIRED)
install(PROGRAMS
src/turtle_publisher.py
src/turtle_subscriber.py
DESTINATION lib/${PROJECT_NAME}
RENAME turtle_publisher_py turtle_subscriber_py # 去掉 .py 并区分名字
)
# ---------- 安装 ----------
install(TARGETS
turtle_publisher_cpp
turtle_subscriber_cpp
DESTINATION lib/${PROJECT_NAME}
)
install(DIRECTORY launch/
DESTINATION share/${PROJECT_NAME})
ament_package()3. 可执行名对照表(ros2 run 时用)
| 语言 | 节点名 | 运行命令 |
|---|---|---|
| C++ | turtle_publisher_cpp | ros2 run pub_sub turtle_publisher_cpp |
| C++ | turtle_subscriber_cpp | ros2 run pub_sub turtle_subscriber_cpp |
| Python | turtle_publisher_py | ros2 run pub_sub turtle_publisher_py |
| Python | turtle_subscriber_py | ros2 run pub_sub turtle_subscriber_py |
4. launch 文件示例(四选一,想全开就再写 4 个 Node)
Python
复制
from launch import LaunchDescription
from launch_ros.actions import Node
def generate_launch_description():
return LaunchDescription([
Node(package='turtlesim', executable='turtlesim_node', name='sim'),
# 下面只启动 C++ 版,想换 Python就把 executable 换成 *_py
Node(package='pub_sub', executable='turtle_publisher_cpp', name='cpp_pub'),
Node(package='pub_sub', executable='turtle_subscriber_cpp', name='cpp_sub'),
])5. 构建 & 运行(和原来完全一样)
bash
复制
cd ~/ros2_ws
colcon build --packages-select pub_sub
source install/setup.bash
# 任意挑一个组合
ros2 run pub_sub turtle_publisher_cpp # C++ 画圆
ros2 run pub_sub turtle_subscriber_py # Python 打印6. 不会乱的核心原因
- 可执行名不同:
*_cppvs*_py。 - 安装路径相同都没关系,因为文件名唯一。
colcon只是把它们分别拷到install/pub_sub/lib/pub_sub/;ROS 2 按名字找,互不影响。
一句话总结
把名字区分好,一份
CMakeLists.txt 就能同时管 C++ 和 Python;构建、运行、launch 全部零冲突。讲解图片中turtle这些量各自代表的含义
下面把截图里出现的 所有带
(按“你在终端/代码里看到的名字 → 它到底是什么” 的顺序讲,保证一眼能对上号)
turtle 的量 逐个拆开说明:(按“你在终端/代码里看到的名字 → 它到底是什么” 的顺序讲,保证一眼能对上号)
1. turtle_publisher_cpp
- 本质:一个可执行文件的名字,编译后出现在
install/pub_sub/lib/pub_sub/turtle_publisher_cpp - 来源:
CMakeLists.txt里add_executable(turtle_publisher_cpp src/turtle_publisher.cpp)
把源文件turtle_publisher.cpp编译成二进制并取了这个名字。 - 用途:
ros2 run pub_sub turtle_publisher_cpp时,系统就去上面路径找这个二进制并启动。
2. turtle_subscriber_cpp
- 同上,只是源文件换成
turtle_subscriber.cpp,功能变成打印海龟位姿。
3. turtle_publisher_py / turtle_subscriber_py
- 本质:同样是可执行文件的名字,但它们是Python 脚本。
- 来源:
CMakeLists.txt里cmake复制install(PROGRAMS src/turtle_publisher.py src/turtle_subscriber.py DESTINATION lib/${PROJECT_NAME} RENAME turtle_publisher_py turtle_subscriber_py)把.py文件拷过去并顺手去掉后缀,再统一加_py后缀以便和 C++ 版区分。 - 用途:
ros2 run pub_sub turtle_publisher_py时,系统去同名路径找脚本并用python3解释执行。
4. turtle_publisher(Node 名)
- 出现位置:C++ 构造函数 / Python 父类初始化里cpp复制
Node("turtle_publisher")Python复制super().__init__('turtle_publisher') - 本质:节点名(node name),不是文件名!
它决定了:ros2 node list里看到的名字;- 日志里的
[turtle_publisher]前缀; - 参数、命名空间、重映射时的默认前缀。
- 注意:节点名可以相同,因为 ROS 2 会自动加数字后缀(如
turtle_publisher_1)来避免冲突。
5. turtle1(命名空间 / 话题前缀)
- 出现位置:话题字符串
/turtle1/cmd_vel、/turtle1/pose - 本质:turtlesim 里默认的海龟命名空间。
如果你再 spawn 一只海龟,话题会变成/turtle2/cmd_vel等。 - 作用:让同一条 ROS 网络里可以有多只海龟,互不干扰。
6. /turtle1/cmd_vel、/turtle1/pose
- 本质:话题(Topic)全名,不是代码变量,而是 ROS 2 网络里的“信道”。
- 对应关系:
cmd_vel:你发速度 → 海龟动;pose:海龟广播自己当前坐标 → 你订阅后就能打印。
7. 小结速查表(看到哪个词就查哪行)
| 你看到的名字 | 到底是啥 | 出现地方 |
|---|---|---|
turtle_publisher_cpp | 可执行文件名(二进制) | ros2 run / install/.../lib |
turtle_publisher_py | 可执行文件名(脚本) | ros2 run / install/.../lib |
turtle_publisher | 节点名(node name) | 代码里 Node("turtle_publisher") |
turtle1 | 默认海龟命名空间 | 话题 /turtle1/... |
/turtle1/cmd_vel | 话题(发速度) | ros2 topic pub / rostopic list |
/turtle1/pose | 话题(收位姿) | ros2 topic echo |
记住一句话:
*_cpp / *_py 是给“操作系统”找文件的;turtle_publisher 是给“ROS 2 网络”认节点的;turtle1 是 turtlesim 自带的前缀,方便多海龟共存。好的,谢谢。
你刚才说的:4. launch 文件示例(四选一,想全开就再写 4 个 Node)
是什么意思?全写4个node不是冲突了吗?
不会冲突,因为:
- 节点名字(
name=)可以任意取,只要在同一命名空间内不重复即可; - 话题完全一样反而正是 ROS 2 的设计——多个节点同时订阅或发布同一话题是允许的,消息会被所有订阅者各自收到,发布者则按到达顺序被订阅者接收(队列大小以内)。
举个例子:把 4 个节点全部启动
Python
复制
from launch import LaunchDescription
from launch_ros.actions import Node
def generate_launch_description():
return LaunchDescription([
# 1. 海龟仿真器
Node(package='turtlesim', executable='turtlesim_node', name='sim'),
# 2. C++ 画圆
Node(package='pub_sub', executable='turtle_publisher_cpp', name='cpp_pub'),
# 3. C++ 打印位姿
Node(package='pub_sub', executable='turtle_subscriber_cpp', name='cpp_sub'),
# 4. Python 画圆(同样发 /turtle1/cmd_vel)
Node(package='pub_sub', executable='turtle_publisher_py', name='py_pub'),
# 5. Python 打印位姿(同样订阅 /turtle1/pose)
Node(package='pub_sub', executable='turtle_subscriber_py', name='py_sub'),
])实际效果
| 场景 | 结果 |
|---|---|
两个 publisher 同时发 Twist 到 /turtle1/cmd_vel | 海龟会交替执行两条指令,速度叠加/覆盖,看上去“抖动”或“更快”——不报错,只是逻辑奇怪。 |
两个 subscriber 同时订阅 /turtle1/pose | 两条日志并行打印,互不影响;你能看到 C++ 和 Python 版本各自输出一次。 |
结论
- ROS 2 允许“多发布者、多订阅者”同话题;
- 是否冲突取决于业务逻辑而非系统限制;
- 如果想纯观察,全开 4 个节点完全没问题;
- 如果想只让一只海龟听话,launch 里只保留一个 publisher 即可(这就是前面说的“四选一”)。