

前面已经介绍了仿真软件的启动,接下来开始介绍抓取的代码start_grasp.launch.py
第一步,先看generate_launch_description代码#
def generate_launch_description():
## 生成启动描述的主函数
## 该函数定义了所有需要声明的参数和启动设置
## 声明参数列表(当前为空,可根据需要添加参数)
declared_arguments = []
## 返回LaunchDescription对象,包含所有声明的参数和启动设置函数
return LaunchDescription(declared_arguments + [OpaqueFunction(function=launch_setup)])python这里没什么内容,跳过
第二步,看launch_setup函数代码#
def launch_setup(context, *args, **kwargs):
## 启动设置函数,用于配置和启动抓取演示相关的节点
## 包含抓取演示的launch文件
## 该launch文件来自ur5e_gripper_control包,用于演示机器人抓取功能
grasp_launch = IncludeLaunchDescription(
PythonLaunchDescriptionSource(
[FindPackageShare("ur5e_gripper_control"), "/launch", "/demo.launch.py"]
),
)
## 组装需要启动的节点列表
nodes_to_launch = [
grasp_launch,
]
## 返回待启动的节点列表
return nodes_to_launchpython这里调用了ur5e_gripper_control下面的launch目录下的demo.launch.py代码。该部分的代码如下:
def generate_launch_description():
## 构造机器人运动学配置文件路径
## 该文件包含机器人逆运动学求解器的配置参数
robot_description_kinematics = PathJoinSubstitution(
[FindPackageShare("ur5e_gripper_moveit_config"), "config", "kinematics.yaml"]
)
## 获取目标位姿列表配置文件路径
## 该文件定义了机器人需要到达的一系列目标位姿
target_pose_list = os.path.join(
get_package_share_directory('ur5e_gripper_control'),
'config',
'target_pose_list.yaml'
)
## 返回LaunchDescription对象,包含需要启动的节点列表
return LaunchDescription([
## 启动演示节点,用于控制机器人执行预定义的抓取任务
Node(
package='ur5e_gripper_control', ## 节点所属的功能包名称
executable='demo', ## 要执行的可执行文件名称
name='demo_node', ## 节点名称
parameters=[{ ## 节点参数列表
"use_sim_time":True, ## 使用仿真时间
},
robot_description_kinematics, ## 机器人运动学参数文件
target_pose_list ## 目标位姿列表配置文件
],
output='screen' ## 输出方式:打印到屏幕
),
])python-
首先加载了机器人逆运动学求解器的配置参数,这个参数文件是在通过使用Setup assistant生成的文件,可以参考博客 ↗
-
这里定义了机器人需要到达的一系列目标位姿,也就是将6个正方体移动的目标位置
-
上述两个文件是通过创建节点调用demo.cpp,从而传入到demo.cpp文件中,可以理解demo.launch.py是一个中介。
-
返回需要启动的节点列表和参数
接下来重点来了!
Node(
package='ur5e_gripper_control', ## 节点所属的功能包
executable='demo', ## 可执行文件名
name='demo_node', ## 节点名称
parameters=[{ ## 节点参数列表
"use_sim_time": True, ## 使用仿真时间
},
robot_description_kinematics, ## 机器人运动学参数
target_pose_list ## 目标位姿列表
],
output='screen' ## 输出方式:打印到屏幕
)python这个配置指定了:
- 功能包: ur5e_gripper_control
- 可执行文件: demo
- 节点名称: demo_node
-
构建配置:CMakeLists.txt 在ur5e_gripper_control包的CMakeLists.txt中,定义了demo可执行文件的构建规则:
pythonadd_executable(demo src/demo.cpp src/ur5e_gripper.cpp) ament_target_dependencies(demo rclcpp moveit_ros_planning_interface tf2 moveit_core moveit_ros_planning control_msgs)这表明demo可执行文件由demo.cpp和ur5e_gripper.cpp两个源文件编译而成
-
可执行文件安装 同样在CMakeLists.txt中,定义了demo可执行文件的安装路径
pythoninstall(TARGETS demo DESTINATION lib/${PROJECT_NAME} )这表示demo可执行文件将被安装到lib/ur5e_gripper_control/目录下。
-
实际执行流程 a. ROS 2 launch系统加载并执行start_grasp.launch.py b. start_grasp.launch.py包含并执行demo.launch.py c. demo.launch.py创建并启动demo_node节点 d. ROS 2系统在功能包ur5e_gripper_control的lib目录中查找名为demo的可执行文件 e. 系统找到并执行demo可执行文件(由demo.cpp编译生成) f. demo.cpp中的main函数开始执行,控制机械臂完成抓取和放置任务
详细介绍demo.cpp文件,代码如下:
/**
* @brief UR5e机器人抓取演示主函数
*
* 该函数实现了完整的机器人抓取和放置流程,包括:
* 1. 初始化ROS 2节点和机器人控制接口
* 2. 获取预设的放置位置列表
* 3. 通过TF获取立方体的位置信息
* 4. 控制机器人依次抓取立方体并放置到指定位置
* 5. 完成任务后回到准备位置
*
* @param argc 命令行参数数量
* @param argv 命令行参数数组
* @return int 程序退出状态码
*/
int main(int argc, char **argv) {
// 初始化ROS 2客户端库
rclcpp::init(argc, argv);
// 创建节点选项并设置自动声明参数
rclcpp::NodeOptions node_options;
node_options.automatically_declare_parameters_from_overrides(true);
// 创建UR5e机器人控制节点实例
auto node = std::make_shared<UR5eGripper>(node_options);
// 初始化机器人控制接口
node->init();
// 创建单线程执行器并添加节点
rclcpp::executors::SingleThreadedExecutor executor;
executor.add_node(node);
// 启动独立线程运行执行器
std::thread([&executor]() { executor.spin(); }).detach();
// 获取预设的目标放置位置列表
std::vector<std::vector<double>> target_pose_list;
node->get_target_pose_list(target_pose_list);
// 设置坐标变换参考帧
std::string from_frame = "base_link";
// 定义需要抓取的立方体坐标系名称列表
std::vector<std::string> to_frame_list = {"cube1", "cube2", "cube3", "cube4", "cube5", "cube6"};
// 存储立方体抓取位置的列表
std::vector<std::vector<double>> cube_pose_list;
// 遍历所有立方体,获取它们的位置信息并调整为抓取位置
for (size_t i = 0; i < to_frame_list.size(); i++) {
std::vector<double> cube_pose;
// 通过TF获取立方体相对于base_link的位姿
node->get_cube_pose(from_frame, to_frame_list[i], cube_pose);
// 检查是否成功获取位姿信息
if (cube_pose.empty()) {
RCLCPP_WARN(rclcpp::get_logger("demo4"), "Failed to get pose for %s, skipping", to_frame_list[i].c_str());
continue;
}
// 调整抓取位置,确保机器人从立方体上方抓取
cube_pose[0] -= 0.012 ; // 微调X坐标
cube_pose[1] += 0.01; // 微调Y坐标
//cube_pose[2] += 0.14; // 注释掉的Z轴调整
cube_pose[2] += 0.14; // Z轴增加0.14米,确保从上方抓取
cube_pose[3] = 0.0; // 设置roll角度为0
cube_pose[4] = M_PI; // 设置pitch角度为π(180度),使夹爪向下
cube_pose[5] = 0.0; // 设置yaw角度为0
// 打印调整后的立方体抓取位置
RCLCPP_INFO(rclcpp::get_logger("demo4"), "Adjusted cube pose for %s: x=%f, y=%f, z=%f",
to_frame_list[i].c_str(), cube_pose[0], cube_pose[1], cube_pose[2]);
// 将调整后的抓取位置添加到列表中
cube_pose_list.push_back(cube_pose);
}
// 循环执行抓取和放置操作,最多处理6个立方体
for (size_t i = 0; i < std::min<size_t>(6, cube_pose_list.size()); i++) {
// 控制机器人移动到立方体抓取位置
bool grasp_success = node->plan_and_execute(cube_pose_list[i]);
// 如果移动失败,则跳过当前立方体
if (!grasp_success) {
continue;
}
// 闭合夹爪抓取立方体,0.36为夹爪闭合位置
node->grasp(0.36);
// 等待1秒确保夹爪完全闭合
rclcpp::sleep_for(std::chrono::seconds(1));
// 如果还有预设的放置位置,则执行放置操作
if (i < target_pose_list.size()) {
// 控制机器人移动到预设的放置位置
bool place_success = node->plan_and_execute(target_pose_list[i]);
// 如果移动成功,则执行放置操作
if (place_success) {
// 打开夹爪释放立方体,0为夹爪完全打开位置
node->grasp(0);
// 等待1秒确保立方体稳定放置
rclcpp::sleep_for(std::chrono::seconds(1));
}
}
}
// 完成所有抓取放置任务后,回到准备位置
node->go_to_ready_position();
// 关闭ROS 2客户端库
rclcpp::shutdown();
// 程序正常退出
return 0;
}cpp接下来进行解释代码:
- 初始化ROS2
rclcpp::init(argc, argv);cpp- 作用:初始化 ROS 2 客户端库。
- 所有 ROS 2 C++ 程序都必须先调用此函数。
- 它会解析命令行参数(如 —ros-args),启动通信中间件(如 DDS)
- 设置节点选项
rclcpp::NodeOptions node_options;
node_options.automatically_declare_parameters_from_overrides(true);cpp- 创建一个 NodeOptions 对象,用于配置节点行为。
- automatically_declare_parameters_from_overrides(true):
- 表示允许通过命令行直接传入参数(如 -p some_param:=value),而无需在代码中预先声明。
- 提高灵活性,便于调试和配置。
- 创建机器人控制节点
auto node = std::make_shared<UR5eGripper>(node_options);cpp- 使用智能指针创建一个 UR5eGripper 类的实例。
- UR5eGripper 是一个继承自 rclcpp::Node 的类,封装了 UR5e 机械臂和夹爪的所有控制逻辑。
- 传入 node_options 配置参数自动声明功能。
- 初始化机器人控制接口
node->init();cpp- 调用自定义类的初始化函数
- 创建单线程执行器并运行
rclcpp::executors::SingleThreadedExecutor executor;
executor.add_node(node);
std::thread([&executor]() { executor.spin(); }).detach();cpp- 让 ROS 2 节点开始持续处理回调函数(如订阅消息、服务请求、定时器等)
- 获取预设的放置位置列表
std::vector<std::vector<double>> target_pose_list;
node->get_target_pose_list(target_pose_list);cpp- target_pose_list 是一个二维数组,存储多个“放置点”的位姿。
- 每个位姿是一个长度为 6 的 double 数组:[x, y, z, roll, pitch, yaw]
- 设置坐标参考帧
std::string from_frame = "base_link";cpp- base_link 是 UR5e 机器人的基座坐标系,是整个机器人系统的参考原点。
- 所有其他坐标系(如 tool0, camera_link, cube1)都是相对于 base_link 定义的
- 定义要抓取的立方体名称列表
std::vector<std::string> to_frame_list = {"cube1", "cube2", "cube3", "cube4", "cube5", "cube6"};cpp- 视觉系统已经检测出最多 6 个立方体,并通过 TF 发布了它们的坐标系:cube1, cube2, …, cube6
- 这些坐标系是通过前面 Python 节点广播的(如 camera_link → cube1)
- 存储立方体抓取位置的列表
std::vector<std::vector<double>> cube_pose_list;cpp- 用于保存所有立方体调整后的抓取位姿(包含位置和姿态)。
- 后续会遍历这个列表,依次抓取每个立方体。
- 核心循环 1:获取并调整立方体位置
for (size_t i = 0; i < to_frame_list.size(); i++) {
std::vector<double> cube_pose;
// 通过TF获取立方体相对于base_link的位姿
node->get_cube_pose(from_frame, to_frame_list[i], cube_pose);
if (cube_pose.empty()) {
RCLCPP_WARN(rclcpp::get_logger("demo4"), "Failed to get pose for %s, skipping", to_frame_list[i].c_str());
continue;
}
// 调整抓取位置
cube_pose[0] -= 0.012 ; // 微调X坐标
cube_pose[1] += 0.01; // 微调Y坐标
cube_pose[2] += 0.14; // Z轴抬高0.14米,确保从上方垂直抓取
cube_pose[3] = 0.0; // roll = 0°
cube_pose[4] = M_PI; // pitch = 180°(夹爪向下)
cube_pose[5] = 0.0; // yaw = 0°
RCLCPP_INFO(...); // 打印日志
cube_pose_list.push_back(cube_pose);
}cpp- node->get_cube_pose(…)
- 该函数内部使用 tf2_ros::Buffer 和 TransformListener 查询 from_frame 到 to_frame 的变换。
- 例如:查询 base_link → cube1 的平移和旋转。
- 返回一个 6 维向量:[x, y, z, roll, pitch, yaw]
- 核心循环 2:抓取与放置
for (size_t i = 0; i < std::min<size_t>(6, cube_pose_list.size()); i++) {cpp- 最多处理 6 个立方体。
- 使用 std::min 防止越界(比如只检测到 3 个立方体)。
步骤一:移动到抓取位置
bool grasp_success = node->plan_and_execute(cube_pose_list[i]);cpp- plan_and_execute():
- 使用 MoveIt 2 规划一条从当前位姿到目标位姿的无碰撞路径。
- 执行该路径,控制机械臂运动。
- 返回 true 表示成功,false 表示规划失败(如路径被阻挡)。
步骤二:闭合夹爪抓取
node->grasp(0.36);
rclcpp::sleep_for(std::chrono::seconds(1));cpp- grasp(0.36):控制夹爪闭合到 0.36,刚好夹住立方体。
- sleep_for(1s):等待夹爪完全闭合,确保夹紧。
步骤三:移动到放置位置
if (i < target_pose_list.size()) {
bool place_success = node->plan_and_execute(target_pose_list[i]);
if (place_success) {
node->grasp(0); // 打开夹爪
rclcpp::sleep_for(std::chrono::seconds(1));
}
}cpp- 移动到第 i 个预设放置点。
- 成功后打开夹爪(grasp(0)),释放立方体。
- 等待 1 秒确保物体稳定落下。
- 任务完成:返回准备位置
node->go_to_ready_position();cpp- 调用预设的“准备位姿”(Home Position),通常是安全抬高的位置。
- 避免下次启动时发生碰撞。
- 结束程序
rclcpp::shutdown();
return 0;cpp整体流程
[程序启动]
↓
rclcpp::init() → 初始化 ROS 2
↓
创建 UR5eGripper 节点
↓
node->init() → 初始化 MoveIt 和夹爪
↓
启动 executor.spin()(后台线程)
↓
获取放置点列表 target_pose_list
↓
循环遍历 cube1~cube6:
├─ 通过 TF 查询 cube_i 相对于 base_link 的位置
├─ 调整为抓取位姿(抬高 Z,设置 pitch=π)
└─ 存入 cube_pose_list
↓
再次循环抓取每个立方体:
├─ plan_and_execute(抓取位姿)
├─ grasp(0.36) // 抓
├─ plan_and_execute(放置位姿)
└─ grasp(0) // 放
↓
go_to_ready_position() // 回家
↓
rclcpp::shutdown() → 关闭系统python补充 target_pose_list.yaml内容如下时:
/**:
ros__parameters:
target_pose_list:
## x, y, z, roll, pitch, yaw, grasp
- "0.5, -0.52, 0.2, 0, 3.14, 0.0" ## 2
- "0.5, -0.45, 0.2, 0, 3.14, 0.0" ## 1
- "0.5, -0.38, 0.28, 0, 3.14, 0.0" ## 4
- "0.5, 0.38, 0.2, 0, 3.14, 0.0" ## 3
- "0.5, 0.45, 0.28, 0, 3.14, 0.0" ## 5
- "0.5, 0.52, 0.35, 0, 3.14, 0.0" ## 6yaml执行的效果如下:
浅浅将x的0.5都改为0.3,执行的效果如下:
效果不太好,掉了一个~
抓取期间生成的日志内容如下,ROS2日志的解析参考博客 ↗:
举个例子:
