在ROS 1向ROS 2迁移的过程中,有些依赖项发生了变化,这是因为ROS 2的通信框架和工具链与ROS 1不同,尤其在消息、服务和动作生成方面有了一些新的方法和库。
动作库
如果你的ROS 1包依赖于actionlib
或actionlib_msgs
,在迁移到ROS 2时,需要使用ROS 2的原生动作机制来代替。这是因为在ROS 2中,动作库(actionlib
)被重新实现,集成进了ROS 2核心的通信框架中,不再需要像ROS 1那样单独依赖actionlib_msgs
或actionlib
。以下是详细的替换和迁移步骤:
1. ROS 1中的actionlib
和actionlib_msgs
在ROS 1中,actionlib
用于处理复杂的长时间操作,特别是那些需要反馈和预先终止的操作。actionlib_msgs
包含了动作通信的标准消息类型,如:
- Goal:客户端发送到服务器的目标。
- Feedback:服务器定期发送给客户端的反馈。
- Result:服务器完成任务后发送的结果。
2. ROS 2中的原生动作机制
在ROS 2中,动作接口被直接集成进了ROS 2通信架构中,通过rclcpp_action
(C++)或rclpy_action
(Python)来处理。动作在ROS 2中作为一种独立的接口,与消息和服务一起被支持。ROS 2的动作机制改进了性能、简化了开发流程,并且通过rosidl
接口定义语言来生成动作接口。
主要区别:
- 内置支持 :在ROS 2中,动作系统是核心架构的一部分,直接使用与消息和服务相同的
rosidl
工具链。 - 生成方式 :动作文件(
.action
)可以直接与消息和服务一起通过rosidl_generate_interfaces
生成。
3. 迁移步骤
(1) 定义动作文件(.action
)
在ROS 1中,你可能有一个.action
文件。迁移到ROS 2时,这些文件不需要改变语法,但是要确保它们遵循ROS 2的生成规则。一个标准的.action
文件如下:
plaintext
# 定义目标(Goal)
int32 goal_value
---
# 定义结果(Result)
int32 result_value
---
# 定义反馈(Feedback)
float32 feedback_value
(2) 在CMakeLists.txt
中生成动作接口
在ROS 2中,你需要使用rosidl_generate_interfaces
来生成动作、消息和服务的接口。将以下内容添加到CMakeLists.txt
中:
cmake
find_package(rosidl_default_generators REQUIRED)
set(ACTION_FILES
"action/MyAction.action"
)
rosidl_generate_interfaces(${PROJECT_NAME}
${ACTION_FILES}
DEPENDENCIES std_msgs
)
ACTION_FILES
变量中列出了你的.action
文件。rosidl_generate_interfaces
会生成C++和Python代码,供你的动作服务器和客户端使用。
(3) 修改代码以使用ROS 2的动作API
在ROS 1中,你可能用过actionlib::SimpleActionClient
或actionlib::SimpleActionServer
。在ROS 2中,需要改用rclcpp_action::Client
和rclcpp_action::Server
(C++),或者用rclpy.action.ActionClient
和rclpy.action.ActionServer
(Python)。
C++动作客户端(ROS 2)示例:
cpp
#include "rclcpp/rclcpp.hpp"
#include "rclcpp_action/rclcpp_action.hpp"
#include "my_package/action/my_action.hpp"
class MyActionClient : public rclcpp::Node {
public:
using MyAction = my_package::action::MyAction;
using GoalHandleMyAction = rclcpp_action::ClientGoalHandle<MyAction>;
MyActionClient() : Node("my_action_client") {
this->client_ = rclcpp_action::create_client<MyAction>(this, "my_action");
// 检查服务器是否可用
if (!this->client_->wait_for_action_server(std::chrono::seconds(10))) {
RCLCPP_ERROR(this->get_logger(), "Action server not available after waiting");
return;
}
// 发送目标
auto goal_msg = MyAction::Goal();
goal_msg.goal_value = 42;
auto goal_options = rclcpp_action::Client<MyAction>::SendGoalOptions();
goal_options.result_callback = std::bind(&MyActionClient::result_callback, this, std::placeholders::_1);
this->client_->async_send_goal(goal_msg, goal_options);
}
private:
rclcpp_action::Client<MyAction>::SharedPtr client_;
void result_callback(const GoalHandleMyAction::WrappedResult & result) {
RCLCPP_INFO(this->get_logger(), "Result received: %d", result.result->result_value);
}
};
int main(int argc, char ** argv) {
rclcpp::init(argc, argv);
auto node = std::make_shared<MyActionClient>();
rclcpp::spin(node);
rclcpp::shutdown();
return 0;
}
C++动作服务器(ROS 2)示例:
cpp
#include "rclcpp/rclcpp.hpp"
#include "rclcpp_action/rclcpp_action.hpp"
#include "my_package/action/my_action.hpp"
class MyActionServer : public rclcpp::Node {
public:
using MyAction = my_package::action::MyAction;
using GoalHandleMyAction = rclcpp_action::ServerGoalHandle<MyAction>;
MyActionServer() : Node("my_action_server") {
this->server_ = rclcpp_action::create_server<MyAction>(
this,
"my_action",
std::bind(&MyActionServer::handle_goal, this, std::placeholders::_1, std::placeholders::_2),
std::bind(&MyActionServer::handle_cancel, this, std::placeholders::_1),
std::bind(&MyActionServer::handle_accepted, this, std::placeholders::_1)
);
}
private:
rclcpp_action::Server<MyAction>::SharedPtr server_;
rclcpp_action::GoalResponse handle_goal(
const rclcpp_action::GoalUUID & uuid,
std::shared_ptr<const MyAction::Goal> goal) {
RCLCPP_INFO(this->get_logger(), "Received goal request with value %d", goal->goal_value);
return rclcpp_action::GoalResponse::ACCEPT_AND_EXECUTE;
}
rclcpp_action::CancelResponse handle_cancel(
const std::shared_ptr<GoalHandleMyAction> goal_handle) {
RCLCPP_INFO(this->get_logger(), "Received request to cancel goal");
return rclcpp_action::CancelResponse::ACCEPT;
}
void handle_accepted(const std::shared_ptr<GoalHandleMyAction> goal_handle) {
std::thread{std::bind(&MyActionServer::execute, this, goal_handle)}.detach();
}
void execute(const std::shared_ptr<GoalHandleMyAction> goal_handle) {
RCLCPP_INFO(this->get_logger(), "Executing goal");
// 在这里执行动作,并发送反馈或结果
auto result = std::make_shared<MyAction::Result>();
result->result_value = 42;
goal_handle->succeed(result);
}
};
int main(int argc, char ** argv) {
rclcpp::init(argc, argv);
auto node = std::make_shared<MyActionServer>();
rclcpp::spin(node);
rclcpp::shutdown();
return 0;
}
4. 更新依赖项
在package.xml
中,确保你已经包含了ROS 2的相关依赖项,例如:
xml
<depend>rclcpp_action</depend>
<depend>rosidl_default_generators</depend>
错误信息:
使用 colcon build
构建过程中遇到的错误提示表明了 scene_manager_msgs
这个 ROS 2 包的 package.xml
文件存在一个特定问题。错误信息指出你的包缺少一个必需的声明,这是定义自定义消息、服务的包所必需的。
Packages installing interfaces must include
'<member_of_group>rosidl_interface_packages</member_of_group>' in their
package.xml
告诉我们需要在 package.xml
中声明该包是 rosidl_interface_packages
组的一部分。因为在ROS 2中,负责定义消息、服务和动作接口的包必须向构建系统声明它们是接口包的一部分。这是为了让构建工具正确处理这些自定义接口,确保生成消息、服务和动作所需的代码。
如何修复
你需要在 package.xml
中添加一个 member_of_group
标签,将你的包包含在 rosidl_interface_packages
组中。这样可以告诉 ROS 构建工具你的包提供了需要处理的自定义接口。
- 打开你的
package.xml
文件。 - 在
<package>
标签内添加以下行:
xml
<member_of_group>rosidl_interface_packages</member_of_group>
这一行应该添加在顶级 <package>
标签内部,但在任何特定依赖声明之外。
更新后的 package.xml
示例
在修改后,你的 package.xml
部分内容可能如下所示:
xml
<?xml version="1.0"?>
<package format="3">
<name>scene_manager_msgs</name>
<version>0.0.1</version>
<description>用于场景管理的消息和服务的包</description>
<maintainer email="your-email@example.com">你的名字</maintainer>
<license>BSD</license>
<!-- 将此包包含在 rosidl_interface_packages 组中 -->
<member_of_group>rosidl_interface_packages</member_of_group>
<buildtool_depend>ament_cmake</buildtool_depend>
<depend>std_msgs</depend>
<depend>geometry_msgs</depend>
<depend>builtin_interfaces</depend>
<depend>rosidl_default_generators</depend>
<exec_depend>rosidl_default_runtime</exec_depend>
</package>
修改后的步骤
一旦你添加了这一行,应该:
- 保存
package.xml
文件。 - 重新运行构建命令:
bash
colcon build --packages-select scene_manager_msgs
这应该会解决构建错误,允许 colcon
构建工具正确识别并处理你包的自定义接口。
详细解释:
-
rosidl_interface_packages
组的作用 :在ROS 2中,
rosidl
(ROS Interface Definition Language)负责处理消息、服务和动作的定义和生成。所有涉及定义这些接口的包,都需要被归类为rosidl_interface_packages
组的成员。通过将包声明为该组的一部分,ROS 2的构建系统可以识别该包包含自定义接口,并生成必要的消息、服务或动作文件。 -
生成自定义接口的流程 :
当你在包中定义了消息(
.msg
)、服务(.srv
)或动作(.action
)文件时,构建工具会通过rosidl_generate_interfaces
命令生成这些接口所需的C++或Python代码。然而,为了让构建系统知道你的包中包含这些接口文件,必须在package.xml
中通过<member_of_group>
标签将该包添加到rosidl_interface_packages
组。 -
缺少声明的后果 :
如果不将包加入到
rosidl_interface_packages
组,构建系统在处理这些消息和服务文件时将无法识别它们,进而无法生成必要的代码。这就会导致构建失败或在运行时无法找到相应的消息和服务接口。因此,缺少这个声明会导致类似于你之前遇到的错误。
包含消息和服务的包
从你提供的 CMakeLists.txt
文件来看,scene_manager_msgs
包定义了消息(msg/Layout.msg
)和服务(如 srv/ModifyObject.srv
等),但并没有定义动作文件(.action
文件)。因此,这个包不是一个动作包,而是一个包含消息和服务的包。
动作包的定义:
-
动作包 (Action Package)通常会定义
.action
文件,这些文件用于定义长时间运行的任务,通常需要反馈机制。动作在ROS 2中可以使用rclcpp_action
或rclpy_action
库进行操作。 -
如果这个包是一个动作包,你的
CMakeLists.txt
文件中应该有对.action
文件的定义,比如:cmakeset(ACTION_FILES "action/MyAction.action" ) rosidl_generate_interfaces(${PROJECT_NAME} ${MSG_FILES} ${SRV_FILES} ${ACTION_FILES} DEPENDENCIES std_msgs geometry_msgs builtin_interfaces )
当前的 CMakeLists.txt
说明:
- 你定义了消息文件
msg/Layout.msg
和服务文件srv/ModifyObject.srv
、srv/SelectObjects.srv
、srv/MoveTo.srv
。 - 没有提到
.action
文件,因此该包没有动作接口。
总结:
- 不是动作包 :由于没有定义动作文件(
.action
),这个包目前是一个消息和服务的包,而不是动作包。 - 如何添加动作 :如果你希望将其扩展为一个动作包,你需要定义
.action
文件,并在CMakeLists.txt
中增加动作生成的配置。
消息(.msg
)和服务(.srv
)文件本身通常不需要修改其定义结构,因为ROS 2仍然使用相同的格式来定义这些文件
在将ROS 1软件包迁移到ROS 2时,消息(.msg
)和服务(.srv
)文件本身通常不需要修改其定义结构,因为ROS 2仍然使用相同的格式来定义这些文件。然而,你可能需要进行一些更新以确保这些定义与ROS 2的接口兼容和一致。
需要考虑的修改点:
-
依赖检查:
- 确保消息和服务文件中使用的类型在ROS 2中是有效的。例如,如果使用了
geometry_msgs/PoseStamped
,确保ROS 2中包含此类型。 - 检查是否所有依赖的包都已经被迁移到ROS 2并在
package.xml
和CMakeLists.txt
中正确引用。
- 确保消息和服务文件中使用的类型在ROS 2中是有效的。例如,如果使用了
-
包和类型的更新:
- 在ROS 2中,某些消息类型可能已经更新或替换。尽管大部分标准消息类型如
std_msgs
和geometry_msgs
在ROS 2中仍然可用,但一些特定类型或属性可能已经有所改变。 - 确保使用的类型与ROS 2的命名和结构标准一致。
- 在ROS 2中,某些消息类型可能已经更新或替换。尽管大部分标准消息类型如
-
服务定义的格式:
- 服务文件在ROS 1和ROS 2中的定义方式保持一致,格式为
请求部分
,一个---
分割符,然后是响应部分
。 - 确认服务文件的请求和响应部分是否符合你的应用需求,并考虑是否需要进行更新以利用ROS 2提供的新特性或改进。
- 服务文件在ROS 1和ROS 2中的定义方式保持一致,格式为