int main(int argc, char** argv)
{
rclcpp::init(argc, argv);
rclcpp::NodeOptions node_options;
node_options.automatically_declare_parameters_from_overrides(true);
auto move_group_node = rclcpp::Node::make_shared("move_group_interface_tutorial", node_options);
// We spin up a SingleThreadedExecutor for the current state monitor to get information
// about the robot's state.
rclcpp::executors::SingleThreadedExecutor executor;
executor.add_node(move_group_node);
std::thread([&executor]() { executor.spin(); }).detach();
这段代码是基于 ROS 2(Robot Operating System 2)的 C++ 程序。它使用了 ROS 2 的核心 API 来初始化节点、设置参数选项,并通过单线程执行器(SingleThreadedExecutor
)来处理节点的任务。在这种架构中,ROS 2 节点处理和执行异步任务,如监听传感器数据或执行机器人控制操作。让我们逐行解释它的功能:
1. int main(int argc, char** argv)
这行定义了程序的入口点,main
函数是程序执行的开始。argc
和 argv
代表从命令行传递给程序的参数,ROS 使用这些参数来处理一些标准化的初始化配置(如日志、命名空间等)。
2. rclcpp::init(argc, argv);
这一行调用了 rclcpp::init()
,这是 ROS 2 程序的初始化函数。它初始化 ROS 2 的运行时环境,包括通信、节点管理等。argc
和 argv
将传递命令行参数,以便 ROS 2 解析一些通用的配置选项。
3. rclcpp::NodeOptions node_options;
这行创建了一个 NodeOptions
对象,用于配置节点的选项。NodeOptions
是 ROS 2 节点配置的一部分,允许用户定义一些节点的特定行为。
4. node_options.automatically_declare_parameters_from_overrides(true);
这一行设置了 NodeOptions
的一个选项,允许节点自动从参数覆盖中声明参数。ROS 2 节点可以动态接收参数配置,这一选项确保即使在启动时未明确声明的参数也会被自动接受并应用到节点中。
5. auto move_group_node = rclcpp::Node::make_shared("move_group_interface_tutorial", node_options);
rclcpp::Node::make_shared
是一个工厂函数,创建并返回一个 ROS 2 节点对象。"move_group_interface_tutorial"
是节点的名称,这个名称在 ROS 网络中唯一标识该节点。node_options
是我们在上一步配置的节点选项。这个节点会使用该选项配置。
该行代码创建了一个共享指针类型的 ROS 2 节点,用于处理机器人运动组的接口操作(从名字 move_group_interface_tutorial
可以推测)。
6. rclcpp::executors::SingleThreadedExecutor executor;
这行创建了一个单线程的执行器(SingleThreadedExecutor
),用于处理节点的回调函数。ROS 2 通过执行器管理不同节点之间的通信和数据处理。单线程执行器意味着它会以顺序方式处理回调,而不是并行处理。
7. executor.add_node(move_group_node);
这行将刚刚创建的 move_group_node
节点添加到执行器中,意味着执行器会管理和执行该节点的任务(如订阅话题、发布消息等)。
8. std::thread([&executor]() { executor.spin(); }).detach();
这行代码启动了一个新的线程,用于异步执行节点的任务:
[&executor]()
:这是一个捕获executor
引用的lambda 表达式,该表达式会调用executor.spin()
。executor.spin()
:会进入一个循环,持续处理节点的回调函数(如传感器数据、定时器事件等),直到节点被关闭。std::thread(...).detach()
:创建一个独立的线程来运行executor.spin()
。detach()
将这个线程与主线程分离,使它独立运行。这样主线程可以继续执行其他任务,而这个新线程会负责管理和处理节点的回调事件。
代码的整体功能:
- 初始化 ROS 2 环境,并创建一个名为
move_group_interface_tutorial
的节点。 - 配置节点参数,使其能够自动接受参数覆盖。
- 使用单线程执行器,负责处理节点的任务和回调函数(如处理机器人状态的监听)。
- 启动一个新线程,在后台持续执行节点的任务,而主线程则不会被阻塞。
线程用于处理ROS 2节点的执行,并维持对机器人状态的实时监控。
1. 执行器和线程的使用
rclcpp::executors::SingleThreadedExecutor executor;
executor.add_node(move_group_node);
std::thread([&executor]() { executor.spin(); }).detach();
-
SingleThreadedExecutor
:这是一个用于管理和执行ROS 2节点(例如这里的move_group_node
)回调函数的单线程执行器。该执行器确保节点的回调函数(如订阅、计时器、服务请求等)能够按顺序被处理。 -
线程的创建:使用
std::thread
创建了一个新线程,传入了一个运行executor.spin()
方法的lambda函数。spin()
方法进入一个循环,持续处理节点的回调函数,直到它被明确停止。 -
分离线程:通过调用
.detach()
使线程分离,意味着它将独立于主程序运行。主线程可以继续执行而无需等待这个线程结束。这样做的目的是确保机器人状态能在后台持续更新和监控,而主程序可以同时处理其他任务,如规划和可视化。
2. 线程的目的
-
状态监控:在独立线程中运行的执行器允许程序接收并处理关于机器人状态的实时更新(例如关节位置、速度、传感器数据)。对于机器人应用程序而言,持续的反馈非常重要,可以用于调整运动或对环境的变化作出反应。
-
非阻塞操作:通过在单独的线程中运行执行器,主程序不会被阻塞。主程序可以执行其他操作,比如规划路径或执行机器人动作,而后台线程则处理ROS通信。
为什么使用分离线程?
线程被分离是因为执行器需要独立、持续地运行,贯穿整个程序的生命周期。通过分离线程,主程序可以在不等待执行器线程结束的情况下,执行设置姿态目标、可视化机器人运动等任务。这种方式确保了执行器线程和主程序可以同时执行各自的任务,互不干扰。