ROS2 接口文档
本文档详细描述了 GR-Navigation 导航系统的所有ROS2接口,包括话题、服务和动作。
环境准备
请在每个新开的终端中,先初始化 ROS 2 与机器人 DDS 发现环境,再执行本文的 ros2 命令、Python 示例或 C++ 示例:
source /opt/fftai/fourierdds/shells/discovery_env.sh --ros2
export ROS_DOMAIN_ID=123
该脚本会加载 ROS 2 Humble 环境、配置机器人 DDS 发现,并设置 ROS 2 使用的 Fast DDS 中间件;ROS_DOMAIN_ID 需要按机器人当前配置显式设置。执行成功后,本文的标准消息类型和已部署的傅利叶 ROS 2 接口包(例如 fourier_msgs、map_manager)都会从 ROS 2 环境加载。
如果执行 ros2 interface show fourier_msgs/srv/GetCurrentAction 或其他本文引用的傅利叶接口时提示找不到包,说明机器人尚未部署 ROS 2 接口包;请先完成对应机器人软件版本的部署,再运行本文示例。
GRW 机器人默认在设备 namespace 下运行。先将实际 namespace 写入环境变量(例如本机可从 ros2 topic list 看到 /GRW01AA0001/...):
export ROBOT_NAMESPACE=/GRW01AA0001 # 替换为实际机器人 namespace
本文 Python/C++ 示例全部使用相对接口名。运行时为进程设置 namespace,即可直接访问该机器人的接口:
python3 example.py --ros-args -r __ns:=$ROBOT_NAMESPACE
./example --ros-args -r __ns:=$ROBOT_NAMESPACE
命令行示例中的接口名则须显式加此前缀,例如 $ROBOT_NAMESPACE/slam/set_mode。若机器人没有 namespace,可将 ROBOT_NAMESPACE 设为空并去掉命令中的多余 /。
如果仅需要在不连接机器人的环境中编译或运行本地 ROS 2 程序,可执行:
source /opt/ros/humble/setup.bash
export ROS_DOMAIN_ID=123
特别注意:
如果导航系统在自定义 namespace 下运行,那么所有 node、topic、service、action 都会增加 namespace 前缀,但是消息体结构不会发生变化。
e.g.
| 无 namespace | 有 namespace(robot1) |
|---|---|
| /slam/set_mode | /robot1/slam/set_mode |
ros2 service call $ROBOT_NAMESPACE/slam/set_mode fourier_msgs/srv/SetMode "{mode: 'mapping'}" | ros2 service call /robot1/slam/set_mode fourier_msgs/srv/SetMode "{mode: 'mapping'}" |
| /slam/mode_status | /robot1/slam/mode_status |
| ros2 topic echo $ROBOT_NAMESPACE/slam/mode_status | ros2 topic echo /robot1/slam/mode_status |
1. 建图定位模式切换
1.1 /slam/set_mode (服务)
在建图模式和定位模式之间切换
服务类型: fourier_msgs/srv/SetMode
调用示例:
# 切换到建图模式
ros2 service call $ROBOT_NAMESPACE/slam/set_mode fourier_msgs/srv/SetMode "{mode: 'mapping'}"
# 切换到定位模式
ros2 service call $ROBOT_NAMESPACE/slam/set_mode fourier_msgs/srv/SetMode "{mode: 'localization'}"
Python调用:
from fourier_msgs.srv import SetMode
import rclpy
from rclpy.node import Node
class ModeClient(Node):
def __init__(self):
super().__init__('mode_client')
self.client = self.create_client(SetMode, 'slam/set_mode')
self.client.wait_for_service()
def switch_mode(self, mode: str):
request = SetMode.Request()
request.mode = mode
future = self.client.call_async(request)
rclpy.spin_until_future_complete(self, future)
return future.result()
# 使用示例
rclpy.init()
client = ModeClient()
result = client.switch_mode('localization')
print(f"Success: {result.success}, Message: {result.message}")
C++调用:
#include <rclcpp/rclcpp.hpp>
#include <fourier_msgs/srv/set_mode.hpp>
class ModeClient : public rclcpp::Node {
public:
ModeClient() : Node("mode_client") {
client_ = create_client<fourier_msgs::srv::SetMode>("slam/set_mode");
client_->wait_for_service();
}
bool switch_mode(const std::string& mode) {
auto request = std::make_shared<fourier_msgs::srv::SetMode::Request>();
request->mode = mode;
auto future = client_->async_send_request(request);
if (rclcpp::spin_until_future_complete(shared_from_this(), future) ==
rclcpp::FutureReturnCode::SUCCESS) {
auto result = future.get();
RCLCPP_INFO(get_logger(), "Result: %s", result->message.c_str());
return result->success;
}
return false;
}
private:
rclcpp::Client<fourier_msgs::srv::SetMode>::SharedPtr client_;
};
// 使用示例
int main(int argc, char** argv) {
rclcpp::init(argc, argv);
auto client = std::make_shared<ModeClient>();
client->switch_mode("localization");
rclcpp::shutdown();
return 0;
}
2. 建图模式接口
2.1 /clear_map (话题)
清除当前地图并重启建图
消息类型: fourier_msgs_pod/msg/ShortString
功能说明:
- 消息内容不参与解析;收到任意合法
ShortString后,导航模块清除当前地图。 - 此接口只清除当前运行时地图数据,不会删除已保存的复合地图归档。删除已保存地图请使用 /clear_composite_map。
发布示例:
ros2 topic pub $ROBOT_NAMESPACE/clear_map fourier_msgs_pod/msg/ShortString "{}" --once
Python发布:
from fourier_msgs_pod.msg import ShortString
import rclpy
from rclpy.node import Node
from rclpy.qos import DurabilityPolicy, HistoryPolicy, QoSProfile, ReliabilityPolicy
class ClearMapPublisher(Node):
def __init__(self):
super().__init__('clear_map_publisher')
qos = QoSProfile(
history=HistoryPolicy.KEEP_LAST,
depth=10,
reliability=ReliabilityPolicy.BEST_EFFORT,
durability=DurabilityPolicy.TRANSIENT_LOCAL,
)
self.publisher = self.create_publisher(ShortString, 'clear_map', qos)
def clear(self):
msg = ShortString()
self.publisher.publish(msg)
self.get_logger().info('Clear map command sent')
rclpy.init()
node = ClearMapPublisher()
try:
# 等待 DDS 端点发现;立即退出会使一次性命令尚未匹配订阅端。
rclpy.spin_once(node, timeout_sec=1.0)
node.clear()
rclpy.spin_once(node, timeout_sec=1.0)
finally:
node.destroy_node()
rclpy.shutdown()
C++发布:
#include <chrono>
#include <memory>
#include <rclcpp/rclcpp.hpp>
#include <thread>
#include <fourier_msgs_pod/msg/short_string.hpp>
class ClearMapPublisher : public rclcpp::Node {
public:
ClearMapPublisher() : Node("clear_map_publisher") {
const auto qos = rclcpp::QoS(rclcpp::KeepLast(10))
.best_effort()
.transient_local();
publisher_ = create_publisher<fourier_msgs_pod::msg::ShortString>("clear_map", qos);
}
void clear() {
auto msg = fourier_msgs_pod::msg::ShortString();
publisher_->publish(msg);
RCLCPP_INFO(get_logger(), "Clear map command sent");
}
private:
rclcpp::Publisher<fourier_msgs_pod::msg::ShortString>::SharedPtr publisher_;
};
int main(int argc, char** argv) {
rclcpp::init(argc, argv);
auto node = std::make_shared<ClearMapPublisher>();
rclcpp::spin_some(node);
std::this_thread::sleep_for(std::chrono::seconds(1));
node->clear();
std::this_thread::sleep_for(std::chrono::seconds(1));
rclcpp::shutdown();
return 0;
}
2.2 /map (话题)
发布2D占用栅格地图数据
消息类型: nav_msgs/msg/OccupancyGrid
数据来源:
- 建图模式:导航模块发布当前的 2D 占用栅格地图。
- 定位模式:导航模块发布当前已加载地图的 2D 占用栅格地图。
数据约定:
| 参数 | 值 | 说明 |
|---|---|---|
| Frame ID | map | 地图坐标系 |
| data | -1 / 0 / 100 | 分别表示未知、空闲、占用 |
| 分辨率、尺寸与原点 | 运行时地图数据 | 由当前地图决定,不应在客户端写死 |
订阅示例:
ros2 topic echo $ROBOT_NAMESPACE/map