ROS2 接口文档
本文档详细描述了 GR-Navigation 导航系统的所有ROS2接口,包括话题、服务和动作。
特别注意:
如果导航系统在自定义 namespace 下运行,那么所有 node、topic、service、action 都会增加 namespace 前缀,但是消息体结构不会发生变化。
e.g.
| 无 namespace | 有 namespace(robot1) |
|---|---|
| /slam/set_mode | /robot1/slam/set_mode |
ros2 service call /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 /slam/mode_status | ros2 topic echo /robot1/slam/mode_status |
1. 建图定位模式切换
1.1 /slam/set_mode (服务)
在建图模式和定位模式之间切换
服务类型: fourier_msgs/srv/SetMode
调用示例:
# 切换到建图模式
ros2 service call /slam/set_mode fourier_msgs/srv/SetMode "{mode: 'mapping'}"
# 切换到定位模式
ros2 service call /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;
}
1.2 /slam/mode_status (话题)
发布当前系统模式状态
消息类型: std_msgs/msg/String
消息内容:
"mapping"- 建图模式"localization"- 定位模式
订阅示例:
ros2 topic echo /slam/mode_status
2. 建图模式接口
2.1 /clear_map (话题)
清除当前地图并重启建图
消息类型: std_msgs/msg/String
功能说明:
- 如果处于定位模式:切换到建图模式并清除当前缓存地图
- 如果处于建图模式:重启建图,清除所有地图数据
发布示例:
ros2 topic pub /clear_map std_msgs/msg/String "{data: ''}" --once
Python发布:
from std_msgs.msg import String
import rclpy
from rclpy.node import Node
class ClearMapPublisher(Node):
def __init__(self):
super().__init__('clear_map_publisher')
self.publisher = self.create_publisher(String, '/clear_map', 10)
def clear(self):
msg = String()
msg.data = ''
self.publisher.publish(msg)
self.get_logger().info('Clear map command sent')
rclpy.init()
node = ClearMapPublisher()
node.clear()
C++发布:
#include <rclcpp/rclcpp.hpp>
#include <std_msgs/msg/string.hpp>
class ClearMapPublisher : public rclcpp::Node {
public:
ClearMapPublisher() : Node("clear_map_publisher") {
publisher_ = create_publisher<std_msgs::msg::String>("/clear_map", 10);
}
void clear() {
auto msg = std_msgs::msg::String();
msg.data = "";
publisher_->publish(msg);
RCLCPP_INFO(get_logger(), "Clear map command sent");
}
private:
rclcpp::Publisher<std_msgs::msg::String>::SharedPtr publisher_;
};
2.2 /map (话题)
发布2D占用栅格地图数据
消息类型: nav_msgs/msg/OccupancyGrid
数据来源:
- 建图模式:建图定位模块发布实时 2D 占用栅格地图
- 定位模式:地图服务发布已加载的 2D 占用栅格地图
配置参数:
| 参数 | 值 | 说明 |
|---|---|---|
| 分辨率 | 0.05m | 每个栅格单元5cm |
| Frame ID | map | 地图坐标系 |
订阅示例:
ros2 topic echo /map
2.3 /cloud_registered_gravity (话题)
发布3D点云地图数据(重力对齐后)
消息类型: sensor_msgs/msg/PointCloud2
说明: 由建图定位模块输出的重力对齐点云数据。
订阅示例:
ros2 topic echo /cloud_registered_gravity
2.4 /optimize_map (话题)
发布优化后的2D地图数据
消息类型: nav_msgs/msg/OccupancyGrid
说明: 经过降噪处理的2D占用栅格地图,移除了孤立像素和噪声点。仅在定位模式下发布。
订阅示例:
ros2 topic echo /optimize_map
2.5 /slam/save_map (服务)
保存3D点云地图
服务类型: fourier_msgs/srv/SaveMap
保存内容:
- 3D地图:
global.pcd- PCL二进制压缩格式点云
调用示例:
# 保存到默认位置 ./data/my_map/
ros2 service call /slam/save_map fourier_msgs/srv/SaveMap "{map_id: 'my_map'}"
# 保存到指定绝对路径
ros2 service call /slam/save_map fourier_msgs/srv/SaveMap "{map_id: '/home/user/maps/office'}"
Python调用:
from fourier_msgs.srv import SaveMap
import rclpy
from rclpy.node import Node
class MapSaver(Node):
def __init__(self):
super().__init__('map_saver')
self.client = self.create_client(SaveMap, '/slam/save_map')
self.client.wait_for_service()
def save(self, map_id: str):
request = SaveMap.Request()
request.map_id = map_id
future = self.client.call_async(request)
rclpy.spin_until_future_complete(self, future)
result = future.result()
return result.response == 0 # 0 表示成功
rclpy.init()
saver = MapSaver()
success = saver.save('/home/user/maps/my_map')
print(f"Save {'succeeded' if success else 'failed'}")
C++调用:
#include <rclcpp/rclcpp.hpp>
#include <fourier_msgs/srv/save_map.hpp>
class MapSaver : public rclcpp::Node {
public:
MapSaver() : Node("map_saver") {
client_ = create_client<fourier_msgs::srv::SaveMap>("/slam/save_map");
client_->wait_for_service();
}
bool save(const std::string& map_id) {
auto request = std::make_shared<fourier_msgs::srv::SaveMap::Request>();
request->map_id = map_id;
auto future = client_->async_send_request(request);
if (rclcpp::spin_until_future_complete(shared_from_this(), future) ==
rclcpp::FutureReturnCode::SUCCESS) {
return future.get()->response == 0;
}
return false;
}
private:
rclcpp::Client<fourier_msgs::srv::SaveMap>::SharedPtr client_;
};
2.6 保存2D地图
通过地图保存工具保存2D占用栅格地图
命令行工具: map_saver_cli
保存内容:
- 2D地图:
map.pgm+map.yaml- 标准2D栅格地图格式
调用示例:
# 指定话题和自由空间阈值
map_saver_cli --free 0.196 -t /map -f /path/to/save/map
参数说明:
| 参数 | 说明 |
|---|---|
-f / --output | 输出文件路径(不含扩展名) |
-t / --topic | 地图话题名称,默认 /map |
--free | 自由空间阈值 (0.0-1.0),默认 0.25 |
--occupied | 占用阈值 (0.0-1.0),默认 0.65 |
3. 定位模式接口
3.1 /initialpose (话题)
设置机器人初始位姿
消息类型: geometry_msgs/msg/PoseWithCovarianceStamped
发布示例:
# 设置初始位姿:位置(1.0, 2.0, 0.0),朝向yaw=90度(四元数表示)
ros2 topic pub /initialpose geometry_msgs/msg/PoseWithCovarianceStamped \
"{header: {frame_id: 'map'}, pose: {pose: {position: {x: 1.0, y: 2.0, z: 0.0}, orientation: {x: 0.0, y: 0.0, z: 0.707, w: 0.707}}}}" --once
Python发布:
from geometry_msgs.msg import PoseWithCovarianceStamped
import rclpy
from rclpy.node import Node
import math
class InitialPosePublisher(Node):
def __init__(self):
super().__init__('initialpose_publisher')
self.publisher = self.create_publisher(
PoseWithCovarianceStamped, '/initialpose', 10)
def set_pose(self, x: float, y: float, yaw: float):
msg = PoseWithCovarianceStamped()
msg.header.frame_id = 'map'
msg.header.stamp = self.get_clock().now().to_msg()
msg.pose.pose.position.x = x
msg.pose.pose.position.y = y
msg.pose.pose.position.z = 0.0
# yaw转四元数
msg.pose.pose.orientation.x = 0.0
msg.pose.pose.orientation.y = 0.0
msg.pose.pose.orientation.z = math.sin(yaw / 2.0)
msg.pose.pose.orientation.w = math.cos(yaw / 2.0)
self.publisher.publish(msg)
rclpy.init()
node = InitialPosePublisher()
node.set_pose(1.0, 2.0, 1.57) # x=1, y=2, yaw=90度
C++发布:
#include <rclcpp/rclcpp.hpp>
#include <geometry_msgs/msg/pose_with_covariance_stamped.hpp>
#include <cmath>
class InitialPosePublisher : public rclcpp::Node {
public:
InitialPosePublisher() : Node("initialpose_publisher") {
publisher_ = create_publisher<geometry_msgs::msg::PoseWithCovarianceStamped>(
"/initialpose", 10);
}
void set_pose(double x, double y, double yaw) {
auto msg = geometry_msgs::msg::PoseWithCovarianceStamped();
msg.header.frame_id = "map";
msg.header.stamp = now();
msg.pose.pose.position.x = x;
msg.pose.pose.position.y = y;
msg.pose.pose.position.z = 0.0;
// yaw转四元数
msg.pose.pose.orientation.x = 0.0;
msg.pose.pose.orientation.y = 0.0;
msg.pose.pose.orientation.z = std::sin(yaw / 2.0);
msg.pose.pose.orientation.w = std::cos(yaw / 2.0);
publisher_->publish(msg);
}
private:
rclcpp::Publisher<geometry_msgs::msg::PoseWithCovarianceStamped>::SharedPtr publisher_;
};
3.2 /slam/load_map (服务)
加载地图并切换到定位模式
服务类型: fourier_msgs/srv/LoadMap
调用示例:
ros2 service call /slam/load_map fourier_msgs/srv/LoadMap \
"{map_path: '/home/user/maps/office', x: 0.0, y: 0.0, z: 0.0, yaw: 0.0}"
Python调用:
from fourier_msgs.srv import LoadMap
import rclpy
from rclpy.node import Node
class MapLoader(Node):
def __init__(self):
super().__init__('map_loader')
self.client = self.create_client(LoadMap, '/slam/load_map')
self.client.wait_for_service()
def load(self, path: str, x=0.0, y=0.0, z=0.0, yaw=0.0):
request = LoadMap.Request()
request.map_path = path
request.x = x
request.y = y
request.z = z
request.yaw = yaw
future = self.client.call_async(request)
rclpy.spin_until_future_complete(self, future)
result = future.result()
return result.result == 0 # 0 表示成功
rclpy.init()
loader = MapLoader()
success = loader.load('/home/user/maps/office', x=1.0, y=2.0, yaw=1.57)
C++调用:
#include <rclcpp/rclcpp.hpp>
#include <fourier_msgs/srv/load_map.hpp>
class MapLoader : public rclcpp::Node {
public:
MapLoader() : Node("map_loader") {
client_ = create_client<fourier_msgs::srv::LoadMap>("/slam/load_map");
client_->wait_for_service();
}
bool load(const std::string& path, double x=0.0, double y=0.0,
double z=0.0, double yaw=0.0) {
auto request = std::make_shared<fourier_msgs::srv::LoadMap::Request>();
request->map_path = path;
request->x = x;
request->y = y;
request->z = z;
request->yaw = yaw;
auto future = client_->async_send_request(request);
if (rclcpp::spin_until_future_complete(shared_from_this(), future) ==
rclcpp::FutureReturnCode::SUCCESS) {
return future.get()->result == 0;
}
return false;
}
private:
rclcpp::Client<fourier_msgs::srv::LoadMap>::SharedPtr client_;
};
3.3 /robot_pose (话题)
发布机器人3D位姿(6DOF)
消息类型: geometry_msgs/msg/PoseStamped
说明: 由定位系统发布的机器人在map坐标系下的完整3D位姿,包含x, y, z位置和完整的四元数朝向。相比2D导航常用的位姿,此话题提供了完整的6自由度位姿信息。
发布频率: 可配置,默认20Hz(通过 pose_pub_period 参数设置)
Frame ID: map
订阅示例:
ros2 topic echo /robot_pose
Python订阅:
from geometry_msgs.msg import PoseStamped
import rclpy
from rclpy.node import Node
class PoseSubscriber(Node):
def __init__(self):
super().__init__('pose_subscriber')
self.subscription = self.create_subscription(
PoseStamped, '/robot_pose', self.pose_callback, 10)
def pose_callback(self, msg):
pos = msg.pose.position
ori = msg.pose.orientation
print(f"Position: x={pos.x:.3f}, y={pos.y:.3f}, z={pos.z:.3f}")
print(f"Orientation: x={ori.x:.3f}, y={ori.y:.3f}, z={ori.z:.3f}, w={ori.w:.3f}")
rclpy.init()
node = PoseSubscriber()
rclpy.spin(node)
C++订阅:
#include <rclcpp/rclcpp.hpp>
#include <geometry_msgs/msg/pose_stamped.hpp>
class PoseSubscriber : public rclcpp::Node {
public:
PoseSubscriber() : Node("pose_subscriber") {
subscription_ = create_subscription<geometry_msgs::msg::PoseStamped>(
"/robot_pose", 10,
[this](geometry_msgs::msg::PoseStamped::SharedPtr msg) {
RCLCPP_INFO(get_logger(), "Position: [%.3f, %.3f, %.3f]",
msg->pose.position.x, msg->pose.position.y, msg->pose.position.z);
});
}
private:
rclcpp::Subscription<geometry_msgs::msg::PoseStamped>::SharedPtr subscription_;
};
3.4 /odom (话题)
发布机器人里程计数据
消息类型: nav_msgs/msg/Odometry
说明: 包含机器人的位置、姿态和速度信息。
订阅示例:
ros2 topic echo /odom
3.5 /odom_status_code (话题)
发布定位系统状态码
消息类型: std_msgs/msg/Int8
说明: 实时发布定位系统的运行状态,用于监控定位过程和诊断定位问题。
状态码定义:
|| 状态码 | 名称 | 说明 | || ------ | ---- | ---- | || 0 | IDLE | 空闲状态 | || 1 | INITIALIZING | 初始化中 | || 2 | GOOD | 正常定位 | || 3 | FOLLOWING_DR | 定位异常(降级为航位推算) | || 4 | FAIL | 定位失败 |
订阅示例:
ros2 topic echo /odom_status_code
Python订阅:
from std_msgs.msg import Int8
import rclpy
from rclpy.node import Node
class OdomStatusSubscriber(Node):
# 状态码常量
IDLE = 0
INITIALIZING = 1
GOOD = 2
FOLLOWING_DR = 3
FAIL = 4
STATUS_NAMES = {
0: "IDLE",
1: "INITIALIZING",
2: "GOOD",
3: "FOLLOWING_DR",
4: "FAIL"
}
def __init__(self):
super().__init__('odom_status_subscriber')
self.subscription = self.create_subscription(
Int8, '/odom_status_code', self.status_callback, 10)
def status_callback(self, msg):
status_name = self.STATUS_NAMES.get(msg.data, "UNKNOWN")
print(f"定位状态: {status_name} (code: {msg.data})")
if msg.data == self.GOOD:
print("定位正常运行")
elif msg.data == self.FOLLOWING_DR:
print("警告: 定位异常,使用航位推算")
elif msg.data == self.FAIL:
print("错误: 定位失败")
rclpy.init()
node = OdomStatusSubscriber()
rclpy.spin(node)
C++订阅:
#include <rclcpp/rclcpp.hpp>
#include <std_msgs/msg/int8.hpp>
class OdomStatusSubscriber : public rclcpp::Node {
public:
// 状态码常量
static constexpr int8_t IDLE = 0;
static constexpr int8_t INITIALIZING = 1;
static constexpr int8_t GOOD = 2;
static constexpr int8_t FOLLOWING_DR = 3;
static constexpr int8_t FAIL = 4;
OdomStatusSubscriber() : Node("odom_status_subscriber") {
subscription_ = create_subscription<std_msgs::msg::Int8>(
"/odom_status_code", 10,
[this](std_msgs::msg::Int8::SharedPtr msg) {
std::string status_name = getStatusName(msg->data);
RCLCPP_INFO(get_logger(), "定位状态: %s (code: %d)",
status_name.c_str(), msg->data);
if (msg->data == GOOD) {
RCLCPP_INFO(get_logger(), "定位正常运行");
} else if (msg->data == FOLLOWING_DR) {
RCLCPP_WARN(get_logger(), "定位异常,使用航位推算");
} else if (msg->data == FAIL) {
RCLCPP_ERROR(get_logger(), "定位失败");
}
});
}
private:
std::string getStatusName(int8_t code) {
switch(code) {
case IDLE: return "IDLE";
case INITIALIZING: return "INITIALIZING";
case GOOD: return "GOOD";
case FOLLOWING_DR: return "FOLLOWING_DR";
case FAIL: return "FAIL";
default: return "UNKNOWN";
}
}
rclcpp::Subscription<std_msgs::msg::Int8>::SharedPtr subscription_;
};
3.6 /odom_status_score (话题)
发布定位置信度分数
消息类型: std_msgs/msg/Int8
说明: 发布定位系统的置信度评分,用于评估定位质量。仅当定位状态为GOOD,FOLLOWING_DR,FAIL时,该话题发布有效的置信度值;其他状态下该值为0。
数据说明:
- 当
/odom_status_code= (2 || 3 || 4)时:data= 定位置信度 (0-100) - 当
/odom_status_code= (0 || 1) 时:data= 0
订阅示例:
ros2 topic echo /odom_status_score
Python订阅(联合状态码使用):
from std_msgs.msg import Int8, Float32
import rclpy
from rclpy.node import Node
class OdomMonitor(Node):
def __init__(self):
super().__init__('odom_monitor')
self.status_code = 0
self.status_score = 0.0
self.code_sub = self.create_subscription(
Int8, '/odom_status_code', self.code_callback, 10)
self.score_sub = self.create_subscription(
Float32, '/odom_status_score', self.score_callback, 10)
def code_callback(self, msg):
self.status_code = msg.data
self.print_status()
def score_callback(self, msg):
self.status_score = msg.data
self.print_status()
def print_status(self):
if self.status_code == 2: # GOOD
print(f"定位正常 - 置信度: {self.status_score:.1f}/100")
if self.status_score < 50:
print("警告: 置信度较低")
else:
print(f"定位状态异常 (code: {self.status_code}), score: {self.status_score}")
rclpy.init()
node = OdomMonitor()
rclpy.spin(node)
C++订阅(联合状态码使用):
#include <rclcpp/rclcpp.hpp>
#include <std_msgs/msg/int8.hpp>
#include <std_msgs/msg/float32.hpp>
class OdomMonitor : public rclcpp::Node {
public:
OdomMonitor() : Node("odom_monitor"), status_code_(0), status_score_(0.0) {
code_sub_ = create_subscription<std_msgs::msg::Int8>(
"/odom_status_code", 10,
[this](std_msgs::msg::Int8::SharedPtr msg) {
status_code_ = msg->data;
print_status();
});
score_sub_ = create_subscription<std_msgs::msg::Int8>(
"/odom_status_score", 10,
[this](std_msgs::msg::Int8::SharedPtr msg) {
status_score_ = msg->data;
print_status();
});
}
private:
void print_status() {
if (status_code_ == 2) { // GOOD
RCLCPP_INFO(get_logger(), "定位正常 - 置信度: %.1f/100",
status_score_);
if (status_score_ < 50) {
RCLCPP_WARN(get_logger(), "置信度较低");
}
} else {
RCLCPP_INFO(get_logger(), "定位状态异常 (code: %d), score: %.1f",
status_code_, status_score_);
}
}
rclcpp::Subscription<std_msgs::msg::Int8>::SharedPtr code_sub_;
rclcpp::Subscription<std_msgs::msg::Float32>::SharedPtr score_sub_;
int8_t status_code_;
float status_score_;
};
3.7 重定位
定位丢失或位姿不确定时,在已加载地图中重新估计机器人位姿。对外提供全局/局部重定位服务及状态话题。
3.7.1 /slam/global_relocalization (服务)
触发全局重定位
服务类型: std_srvs/srv/Empty
调用示例:
ros2 service call /slam/global_relocalization std_srvs/srv/Empty
3.7.2 /slam/trigger_local_relocalization (服务)
在地图多边形区域内触发局部重定位;
polygon_vertices为空时等同全局重定位
服务类型: fourier_msgs/srv/VPRLocalRelocalization
调用示例:
ros2 service call /slam/trigger_local_relocalization \
fourier_msgs/srv/VPRLocalRelocalization \
"{polygon_vertices: [{x: 1.0, y: 1.0, z: 0.0}, {x: 4.0, y: 1.0, z: 0.0}, {x: 4.0, y: 4.0, z: 0.0}, {x: 1.0, y: 4.0, z: 0.0}]}"
3.7.6 /reloc_status (话题)
重定位是否进行中;
true表示流程尚未结束
消息类型: std_msgs/msg/Bool
4. 导航相关接口
4.1 navigate_to_pose (动作)
导航到指定目标点
动作类型: nav2_msgs/action/NavigateToPose
Goal(目标请求)
| 字段 | 类型 | 描述 |
|---|---|---|
| pose | geometry_msgs/PoseStamped | 目标位姿,包含位置和朝向 |
| behavior_tree | string | 可选配置文件路径,留空使用默认配置 |
Result(执行结果)
| 字段 | 类型 | 描述 |
|---|---|---|
| result | std_msgs/Empty | 空结果(成功时) |
| error_code | uint16 | 错误码 |
| error_msg | string | 错误描述信息 |
错误码定义:
| 错误码 | 名称 | 描述 |
|---|---|---|
| 0 | NONE | 无错误,导航成功 |
| 9001 | UNKNOWN | 未知错误 |
| 9002 | FAILED_TO_LOAD_BEHAVIOR_TREE | 加载配置失败 |
| 9003 | TF_ERROR | TF变换错误 |
| 9004 | GOAL_CHECKER_ERROR | 目标检查器错误 |
| 9005 | PREEMPTED | 被新目标抢占 |
| 9006 | NO_VALID_PATH | 无法找到有效路径 |
Feedback(实时反馈)
| 字段 | 类型 | 描述 |
|---|---|---|
| current_pose | geometry_msgs/PoseStamped | 机器人当前位姿 |
| navigation_time | builtin_interfaces/Duration | 已导航时间 |
| estimated_time_remaining | builtin_interfaces/Duration | 预计剩余时间 |
| number_of_recoveries | int16 | 恢复行为执行次数 |
| distance_remaining | float32 | 距目标剩余距离(米) |
调用示例
命令行:
# 导航到位置(1.0, 2.0),朝向yaw=45度
ros2 action send_goal /navigate_to_pose nav2_msgs/action/NavigateToPose \
"{pose: {header: {frame_id: 'map'}, pose: {position: {x: 1.0, y: 2.0, z: 0.0}, orientation: {x: 0.0, y: 0.0, z: 0.383, w: 0.924}}}}"
# 带反馈信息
ros2 action send_goal /navigate_to_pose nav2_msgs/action/NavigateToPose \
"{pose: {header: {frame_id: 'map'}, pose: {position: {x: 1.0, y: 2.0, z: 0.0}, orientation: {x: 0.0, y: 0.0, z: 0.383, w: 0.924}}}}" --feedback
Python调用:
from geometry_msgs.msg import PoseStamped
from nav2_msgs.action import NavigateToPose
import math
import rclpy
from rclpy.action import ActionClient
from rclpy.node import Node
class NavigateClient(Node):
def __init__(self):
super().__init__('navigate_client')
self.client = ActionClient(self, NavigateToPose, '/navigate_to_pose')
def send_goal(self, x: float, y: float, yaw: float):
goal = NavigateToPose.Goal()
goal.pose = PoseStamped()
goal.pose.header.frame_id = 'map'
goal.pose.header.stamp = self.get_clock().now().to_msg()
goal.pose.pose.position.x = x
goal.pose.pose.position.y = y
goal.pose.pose.orientation.z = math.sin(yaw / 2.0)
goal.pose.pose.orientation.w = math.cos(yaw / 2.0)
self.client.wait_for_server()
return self.client.send_goal_async(goal)
rclpy.init()
node = NavigateClient()
node.send_goal(1.0, 2.0, 0.785)
rclpy.spin(node)
C++:
#include <rclcpp/rclcpp.hpp>
#include <rclcpp_action/rclcpp_action.hpp>
#include <nav2_msgs/action/navigate_to_pose.hpp>
#include <cmath>
using NavigateToPose = nav2_msgs::action::NavigateToPose;
using GoalHandleNavigateToPose = rclcpp_action::ClientGoalHandle<NavigateToPose>;
class NavigationClient : public rclcpp::Node {
public:
NavigationClient() : Node("navigation_client") {
client_ = rclcpp_action::create_client<NavigateToPose>(
this, "/navigate_to_pose");
client_->wait_for_action_server();
}
void navigate_to(double x, double y, double yaw) {
// 构造目标
auto goal = NavigateToPose::Goal();
goal.pose.header.frame_id = "map";
goal.pose.header.stamp = now();
goal.pose.pose.position.x = x;
goal.pose.pose.position.y = y;
goal.pose.pose.position.z = 0.0;
goal.pose.pose.orientation.x = 0.0;
goal.pose.pose.orientation.y = 0.0;
goal.pose.pose.orientation.z = std::sin(yaw / 2.0);
goal.pose.pose.orientation.w = std::cos(yaw / 2.0);
// 设置回调
auto send_goal_options = rclcpp_action::Client<NavigateToPose>::SendGoalOptions();
// 反馈回调
send_goal_options.feedback_callback =
[this](GoalHandleNavigateToPose::SharedPtr,
const std::shared_ptr<const NavigateToPose::Feedback> feedback) {
RCLCPP_INFO(get_logger(), "距离目标: %.2fm, 恢复次数: %d",
feedback->distance_remaining, feedback->number_of_recoveries);
};
// 结果回调
send_goal_options.result_callback =
[this](const GoalHandleNavigateToPose::WrappedResult& result) {
switch (result.code) {
case rclcpp_action::ResultCode::SUCCEEDED:
RCLCPP_INFO(get_logger(), "导航成功!");
break;
case rclcpp_action::ResultCode::ABORTED:
RCLCPP_ERROR(get_logger(), "导航中止");
break;
case rclcpp_action::ResultCode::CANCELED:
RCLCPP_WARN(get_logger(), "导航取消");
break;
default:
RCLCPP_ERROR(get_logger(), "未知结果");
break;
}
};
// 发送目标
client_->async_send_goal(goal, send_goal_options);
}
void cancel_navigation() {
client_->async_cancel_all_goals();
}
private:
rclcpp_action::Client<NavigateToPose>::SharedPtr client_;
};
int main(int argc, char** argv) {
rclcpp::init(argc, argv);
auto client = std::make_shared<NavigationClient>();
client->navigate_to(1.0, 2.0, 0.785); // x=1, y=2, yaw=45度
rclcpp::spin(client);
rclcpp::shutdown();
return 0;
}
4.2 /plan (话题)
发布全局规划路径
消息类型: nav_msgs/msg/Path
说明: 由导航系统生成的全局路径。
订阅示例:
ros2 topic echo /plan
4.3 /cmd_vel (话题)
机器人速度命令
消息类型: geometry_msgs/msg/Twist
订阅示例:
ros2 topic echo /cmd_vel
4.4 cancel_current_action (服务)
取消当前正在执行的导航动作
服务类型: fourier_msgs/srv/CancelCurrentAction
调用示例:
ros2 service call /cancel_current_action fourier_msgs/srv/CancelCurrentAction
Python调用:
from fourier_msgs.srv import CancelCurrentAction
import rclpy
from rclpy.node import Node
class ActionCanceller(Node):
def __init__(self):
super().__init__('action_canceller')
self.client = self.create_client(CancelCurrentAction, '/cancel_current_action')
self.client.wait_for_service()
def cancel(self):
request = CancelCurrentAction.Request()
future = self.client.call_async(request)
rclpy.spin_until_future_complete(self, future)
result = future.result()
return result.success
rclpy.init()
canceller = ActionCanceller()
if canceller.cancel():
print("Action cancelled successfully")
C++调用:
#include <rclcpp/rclcpp.hpp>
#include <fourier_msgs/srv/cancel_current_action.hpp>
class ActionCanceller : public rclcpp::Node {
public:
ActionCanceller() : Node("action_canceller") {
client_ = create_client<fourier_msgs::srv::CancelCurrentAction>(
"/cancel_current_action");
client_->wait_for_service();
}
bool cancel() {
auto request = std::make_shared<fourier_msgs::srv::CancelCurrentAction::Request>();
auto future = client_->async_send_request(request);
if (rclcpp::spin_until_future_complete(shared_from_this(), future) ==
rclcpp::FutureReturnCode::SUCCESS) {
return future.get()->success;
}
return false;
}
private:
rclcpp::Client<fourier_msgs::srv::CancelCurrentAction>::SharedPtr client_;
};
4.5 get_current_action (服务)
获取当前正在执行的动作信息
服务类型: fourier_msgs/srv/GetCurrentAction
调用示例:
ros2 service call /get_current_action fourier_msgs/srv/GetCurrentAction
Python调用:
from fourier_msgs.srv import GetCurrentAction
import rclpy
from rclpy.node import Node
class ActionMonitor(Node):
def __init__(self):
super().__init__('action_monitor')
self.client = self.create_client(GetCurrentAction, '/get_current_action')
self.client.wait_for_service()
def get_status(self):
request = GetCurrentAction.Request()
future = self.client.call_async(request)
rclpy.spin_until_future_complete(self, future)
result = future.result()
if result.success:
print(f"Action: {result.action_name}")
print(f"Status: {result.status_description}")
return result
rclpy.init()
monitor = ActionMonitor()
monitor.get_status()
C++调用:
#include <rclcpp/rclcpp.hpp>
#include <fourier_msgs/srv/get_current_action.hpp>
class ActionMonitor : public rclcpp::Node {
public:
ActionMonitor() : Node("action_monitor") {
client_ = create_client<fourier_msgs::srv::GetCurrentAction>(
"/get_current_action");
client_->wait_for_service();
}
void get_status() {
auto request = std::make_shared<fourier_msgs::srv::GetCurrentAction::Request>();
auto future = client_->async_send_request(request);
if (rclcpp::spin_until_future_complete(shared_from_this(), future) ==
rclcpp::FutureReturnCode::SUCCESS) {
auto result = future.get();
if (result->success) {
RCLCPP_INFO(get_logger(), "Action: %s, Status: %s",
result->action_name.c_str(),
result->status_description.c_str());
}
}
}
private:
rclcpp::Client<fourier_msgs::srv::GetCurrentAction>::SharedPtr client_;
};
4.6 action_status (话题)
发布当前动作执行状态
消息类型: fourier_msgs/msg/ActionStatus
发布频率: 1 Hz
订阅示例:
ros2 topic echo /action_status