Skip to main content

ROS2 API Reference

This document describes all ROS 2 interfaces exposed by GR-Navigation, including topics, services, and actions.


Important Notes​

When the navigation system runs in a custom namespace, the namespace is prefixed to every node, topic, service, and action. Message structures remain unchanged.

e.g.

Without a namespaceWith the robot1 namespace
/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_statusros2 topic echo /robot1/slam/mode_status

1. Switching Between Mapping and Localization​

1.1 /slam/set_mode (Service)​

Switch between mapping and localization modes.

Service Type: fourier_msgs/srv/SetMode

Example Call:

Bash
# Switch to mapping mode
ros2 service call /slam/set_mode fourier_msgs/srv/SetMode "{mode: 'mapping'}"

# Switch to localization mode
ros2 service call /slam/set_mode fourier_msgs/srv/SetMode "{mode: 'localization'}"

Python:

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()

# Example
rclpy.init()
client = ModeClient()
result = client.switch_mode('localization')
print(f"Success: {result.success}, Message: {result.message}")

C++:

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_;
};

// Example
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 (Topic)​

Publish the current operating mode.

Message Type: std_msgs/msg/String

Message Values:

  • "mapping" - Mapping mode
  • "localization" - Localization mode

Subscription Example:

Bash
ros2 topic echo /slam/mode_status

2. Mapping Mode APIs​

2.1 /clear_map (Topic)​

Clear the current map and restart mapping.

Message Type: std_msgs/msg/String

Behavior:

  • In localization mode, switch to mapping mode and clear the cached map.
  • In mapping mode, restart mapping and clear all map data.

Publishing Example:

Bash
ros2 topic pub /clear_map std_msgs/msg/String "{data: ''}" --once

Python publisher:

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++ publisher:

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 (Topic)​

Publish the optimized 2D map.

Message Type: nav_msgs/msg/OccupancyGrid

Data Source:

  • Mapping mode: the mapping and localization module publishes the live 2D occupancy grid.
  • Localization mode: the map service publishes the loaded 2D occupancy grid.

Configuration Parameters:

ParameterValueDescription
Resolution0.05 mEach grid cell represents 5 cm.
Frame IDmapMap coordinate frame.

Subscription Example:

Bash
ros2 topic echo /map

2.3 /cloud_registered_gravity (Topic)​

Publish gravity-aligned 3D point-cloud map data.

Message Type: sensor_msgs/msg/PointCloud2

Description: Gravity-aligned point-cloud data produced by the mapping and localization module.

Subscription Example:

Bash
ros2 topic echo /cloud_registered_gravity

2.4 /optimize_map (Topic)​

Publish the optimized 2D map.

Message Type: nav_msgs/msg/OccupancyGrid

Description: A denoised 2D occupancy grid with isolated pixels and noise removed. This topic is published only in localization mode.

Subscription Example:

Bash
ros2 topic echo /optimize_map

2.5 /slam/save_map (Service)​

Save the 3D point-cloud map.

Service Type: fourier_msgs/srv/SaveMap

Saved Data:

  • 3D map: global.pcd, stored as a PCL binary-compressed point cloud.

Example Call:

Bash
# Save to the default location: ./data/my_map/
ros2 service call /slam/save_map fourier_msgs/srv/SaveMap "{map_id: 'my_map'}"

# Save to an absolute path
ros2 service call /slam/save_map fourier_msgs/srv/SaveMap "{map_id: '/home/user/maps/office'}"

Python:

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 indicates success

rclpy.init()
saver = MapSaver()
success = saver.save('/home/user/maps/my_map')
print(f"Save {'succeeded' if success else 'failed'}")

C++:

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 Saving a 2D Map​

Save the 2D occupancy grid with the map-saving utility.

Command-line tool: map_saver_cli

Saved Data:

  • 2D map: map.pgm and map.yaml in the standard 2D occupancy-grid format.

Example Call:

Bash
# Specify the topic and free-space threshold
map_saver_cli --free 0.196 -t /map -f /path/to/save/map

Parameters:

OptionDescription
-f / --outputOutput path without a file extension.
-t / --topicMap topic name; defaults to /map.
--freeFree-space threshold from 0.0 to 1.0; defaults to 0.25.
--occupiedOccupied-space threshold from 0.0 to 1.0; defaults to 0.65.

3. Localization Mode APIs​

3.1 /initialpose (Topic)​

Set the robot initial pose

Message Type: geometry_msgs/msg/PoseWithCovarianceStamped

Publishing Example:

Bash
# Set position to (1.0, 2.0, 0.0) and yaw to 90 degrees as a quaternion
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 publisher:

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
# Convert yaw to a quaternion
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 degrees

C++ publisher:

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;
// Convert yaw to a quaternion
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 (Service)​

Load a map and switch to localization mode

Service Type: fourier_msgs/srv/LoadMap

Example Call:

Bash
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:

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 indicates success

rclpy.init()
loader = MapLoader()
success = loader.load('/home/user/maps/office', x=1.0, y=2.0, yaw=1.57)

C++:

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 (Topic)​

Publish the full 3D robot pose (6 DoF).

Message Type: geometry_msgs/msg/PoseStamped

Description: The localization system publishes the robot's complete 3D pose in the map frame, including x, y, and z position and quaternion orientation. Unlike the planar pose commonly used for 2D navigation, this topic provides all six degrees of freedom.

Publication rate: Configurable; 20 Hz by default through the pose_pub_period parameter.

Frame ID: map

Subscription Example:

Bash
ros2 topic echo /robot_pose

Python subscriber:

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++ subscriber:

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 (Topic)​

Publish robot odometry data

Message Type: nav_msgs/msg/Odometry

Description: Contains the robot position, orientation, and velocity.

Subscription Example:

Bash
ros2 topic echo /odom

3.5 /odom_status_code (Topic)​

Publish the localization status code

Message Type: std_msgs/msg/Int8

Description: Publishes the localization system state in real time for monitoring and diagnostics.

Status codes:

CodeNameDescription
0IDLEIdle.
1INITIALIZINGInitialization in progress.
2GOODLocalization is operating normally.
3FOLLOWING_DRLocalization has degraded to dead reckoning.
4FAILLocalization failed.

Subscription Example:

Bash
ros2 topic echo /odom_status_code

Python subscriber:

Python
from std_msgs.msg import Int8
import rclpy
from rclpy.node import Node

class OdomStatusSubscriber(Node):
# Status-code constants
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"Localization status: {status_name} (code: {msg.data})")

if msg.data == self.GOOD:
print("Localization is operating normally")
elif msg.data == self.FOLLOWING_DR:
print("Warning: localization degraded; using dead reckoning")
elif msg.data == self.FAIL:
print("Error: localization failed")

rclpy.init()
node = OdomStatusSubscriber()
rclpy.spin(node)

C++ subscriber:

C++
#include <rclcpp/rclcpp.hpp>
#include <std_msgs/msg/int8.hpp>

class OdomStatusSubscriber : public rclcpp::Node {
public:
// Status-code constants
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(), "Localization status: %s (code: %d)",
status_name.c_str(), msg->data);

if (msg->data == GOOD) {
RCLCPP_INFO(get_logger(), "Localization is operating normally");
} else if (msg->data == FOLLOWING_DR) {
RCLCPP_WARN(get_logger(), "Localization degraded; using dead reckoning");
} else if (msg->data == FAIL) {
RCLCPP_ERROR(get_logger(), "Localization failed");
}
});
}

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 (Topic)​

Publish the localization confidence score

Message Type: std_msgs/msg/Int8

Description: Publishes a confidence score for evaluating localization quality. The value is valid when the localization state is GOOD, FOLLOWING_DR, or FAIL; it is zero in other states.

Data interpretation:

  • When /odom_status_code is 2, 3, or 4, data is the localization confidence from 0 to 100.
  • When /odom_status_code is 0 or 1, data is 0.

Subscription Example:

Bash
ros2 topic echo /odom_status_score

Python subscriber (used with the status code):

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"Localization is healthy - confidence: {self.status_score:.1f}/100")
if self.status_score < 50:
print("Warning: localization confidence is low")
else:
print(f"Localization status is abnormal (code: {self.status_code}), score: {self.status_score}")

rclpy.init()
node = OdomMonitor()
rclpy.spin(node)

C++ subscriber (used with the status code):

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(), "Localization is healthy - confidence: %.1f/100",
status_score_);
if (status_score_ < 50) {
RCLCPP_WARN(get_logger(), "Localization confidence is low");
}
} else {
RCLCPP_INFO(get_logger(), "Localization state is abnormal (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 Relocalization​

Re-estimate the robot pose in the loaded map when localization is lost or uncertain. The system exposes global and local relocalization services and a status topic.

3.7.1 /slam/global_relocalization (Service)​

Trigger global relocalization

Service Type: std_srvs/srv/Empty

Example Call:

Bash
ros2 service call /slam/global_relocalization std_srvs/srv/Empty

3.7.2 /slam/trigger_local_relocalization (Service)​

Trigger local relocalization within a polygon in the map frame. An empty polygon_vertices array triggers global relocalization.

Service Type: fourier_msgs/srv/VPRLocalRelocalization

Example Call:

Bash
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 (Topic)​

Indicates whether relocalization is in progress; true means the process has not finished.

Message Type: std_msgs/msg/Bool


4. Navigation APIs​

4.1 navigate_to_pose (Action)​

Navigate to a specified goal

Action Type: nav2_msgs/action/NavigateToPose

Goal Request​

FieldTypeDescription
posegeometry_msgs/PoseStampedTarget pose, including position and orientation.
behavior_treestringOptional behavior-tree file path; leave empty to use the default configuration.

Result​

FieldTypeDescription
resultstd_msgs/EmptyEmpty result payload on success.
error_codeuint16Error code
error_msgstringHuman-readable error description.

Error code:

Error codeNameDescription
0NONENavigation succeeded without an error.
9001UNKNOWNUnknown error.
9002FAILED_TO_LOAD_BEHAVIOR_TREEFailed to load the behavior tree.
9003TF_ERRORTransform error.
9004GOAL_CHECKER_ERRORGoal-checker error.
9005PREEMPTEDThe action was preempted.
9006NO_VALID_PATHNo valid path was found.

Live Feedback​

FieldTypeDescription
current_posegeometry_msgs/PoseStampedCurrent robot pose.
navigation_timebuiltin_interfaces/DurationElapsed navigation time.
estimated_time_remainingbuiltin_interfaces/DurationEstimated time remaining.
number_of_recoveriesint16Number of recovery attempts.
distance_remainingfloat32Remaining distance in meters.

Example Call​

Command line:

Bash
# Navigate to position (1.0, 2.0) with a yaw of 45 degrees
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}}}}"

# Include feedback
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:

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++:

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) {
// Build the goal
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);

// Configure callbacks
auto send_goal_options = rclcpp_action::Client<NavigateToPose>::SendGoalOptions();

// Feedback callback
send_goal_options.feedback_callback =
[this](GoalHandleNavigateToPose::SharedPtr,
const std::shared_ptr<const NavigateToPose::Feedback> feedback) {
RCLCPP_INFO(get_logger(), "Distance remaining: %.2f m, recoveries: %d",
feedback->distance_remaining, feedback->number_of_recoveries);
};

// Result callback
send_goal_options.result_callback =
[this](const GoalHandleNavigateToPose::WrappedResult& result) {
switch (result.code) {
case rclcpp_action::ResultCode::SUCCEEDED:
RCLCPP_INFO(get_logger(), "Navigation succeeded");
break;
case rclcpp_action::ResultCode::ABORTED:
RCLCPP_ERROR(get_logger(), "Navigation aborted");
break;
case rclcpp_action::ResultCode::CANCELED:
RCLCPP_WARN(get_logger(), "Navigation canceled");
break;
default:
RCLCPP_ERROR(get_logger(), "Unknown result");
break;
}
};

// Send the goal
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 degrees
rclcpp::spin(client);
rclcpp::shutdown();
return 0;
}

4.2 /plan (Topic)​

Publish the global plan

Message Type: nav_msgs/msg/Path

Description: Global path generated by the navigation system.

Subscription Example:

Bash
ros2 topic echo /plan

4.3 /cmd_vel (Topic)​

Robot velocity command

Message Type: geometry_msgs/msg/Twist

Subscription Example:

Bash
ros2 topic echo /cmd_vel

4.4 cancel_current_action (Service)​

Cancel the navigation action currently in progress

Service Type: fourier_msgs/srv/CancelCurrentAction

Example Call:

Bash
ros2 service call /cancel_current_action fourier_msgs/srv/CancelCurrentAction

Python:

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++:

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 (Service)​

Get information about the action currently in progress

Service Type: fourier_msgs/srv/GetCurrentAction

Example Call:

Bash
ros2 service call /get_current_action fourier_msgs/srv/GetCurrentAction

Python:

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++:

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 (Topic)​

Publish the current action execution status

Message Type: fourier_msgs/msg/ActionStatus

Publication rate: 1 Hz

Subscription Example:

Bash
ros2 topic echo /action_status

5. Sensor Data APIs​

5.1 /scan (Topic)​

Publish 2D laser-scan data.

Message Type: sensor_msgs/msg/LaserScan

Description: A 2D scan generated from 3D LiDAR data for use by the 2D navigation stack.

Subscription Example:

Bash
ros2 topic echo /scan

5.2 /segmented_groundless_points (Topic)​

Publish a ground-removed 3D point cloud.

Message Type: sensor_msgs/msg/PointCloud2

Description: Point-cloud data after ground segmentation, used for obstacle detection.

Subscription Example:

Bash
ros2 topic echo /segmented_groundless_points

5.3 /rslidar_points (Topic)​

Publish raw RoboSense LiDAR point-cloud data.

Message Type: sensor_msgs/msg/PointCloud2

Subscription Example:

Bash
ros2 topic echo /rslidar_points

5.4 /imu (Topic)​

Publish IMU sensor data.

Message Type: sensor_msgs/msg/Imu

Description: Contains acceleration and angular-velocity measurements used by mapping and localization.

Subscription Example:

Bash
ros2 topic echo /imu

6. Camera APIs​

The camera subsystem provides color images, depth images, and point-cloud data. Camera data is used for environmental perception, obstacle detection, and navigation.

6.1 Camera Configuration​

The system supports multiple cameras. Camera parameters are configured in the launch file.

Configuration notes:

  • Multiple cameras are supported.
  • Camera names follow the pattern camera_01, camera_02, camera_03, and so on.
  • Each camera has an independent USB connection, frame, and topic namespace.
  • Camera numbering starts at 01 and increments sequentially.

Topic naming:

  • Camera 1 topics use the /camera_01/ prefix.
  • Camera 2 topics use the /camera_02/ prefix.
  • Examples: /camera_01/color/image_raw and /camera_02/depth/points.

6.2 Camera Driver APIs (CAMERA_SDK_ROS2)​

6.2.1 /camera_01/color/image_raw (Topic)​

Publish color camera images

Message Type: sensor_msgs/msg/Image

Description: Publishes uncompressed color images, usually encoded as RGB.

Subscription Example:

Bash
ros2 topic echo /camera_01/color/image_raw

6.2.2 /camera_01/color/camera_info (Topic)​

Publish color camera calibration information

Message Type: sensor_msgs/msg/CameraInfo

Description: Contains camera intrinsic parameters, distortion coefficients, and calibration information.

Subscription Example:

Bash
ros2 topic echo /camera_01/color/camera_info

6.2.3 /camera_01/depth/image_raw (Topic)​

Publish depth camera images

Message Type: sensor_msgs/msg/Image

Description: Publishes depth images whose pixel values represent distance, usually in millimeters or meters depending on the configured encoding.

Subscription Example:

Bash
ros2 topic echo /camera_01/depth/image_raw

6.2.4 /camera_01/depth/camera_info (Topic)​

Publish depth camera calibration information

Message Type: sensor_msgs/msg/CameraInfo

Description: Contains camera intrinsic parameters, distortion coefficients, and calibration information.

Subscription Example:

Bash
ros2 topic echo /camera_01/depth/camera_info

6.2.5 /camera_01/depth/points (Topic)​

Publish depth point-cloud data

Message Type: sensor_msgs/msg/PointCloud2

Description: Organized 3D point-cloud data in the camera optical frame, typically camera_01_color_optical_frame.

Subscription Example:

Bash
ros2 topic echo /camera_01/depth/points

6.3 Camera Preprocessing APIs (camera_preprocessing)​

The camera preprocessing node synchronizes sensor data, interpolates the robot pose, and publishes fused and filtered outputs.

6.3.1 /camera_01/fused_data (Topic)​

Publish fused camera data containing an RGB image, point cloud, and robot pose.

Message Type: fourier_msgs/msg/UnifiedCameraData

Message Values:

  • header: Message header containing the camera timestamp and coordinate frame.
  • camera_name: Camera identifier used to distinguish multiple cameras.
  • rgb_image: RGB image data. image data. image data. image data. image data.
  • point_cloud: Raw point cloud in the camera frame.
  • robot_pose: Robot pose interpolated at the camera timestamp.
  • camera_transform: Transform from camera_link to base_link.

Behavior:

  • Synchronizes the RGB image and point cloud.
  • Interpolates the robot pose at the sensor timestamp.
  • Packages all synchronized data in one message.
  • Supports downstream perception, mapping, and navigation modules.

Subscription Example:

Bash
ros2 topic echo /camera_01/fused_data

Python subscriber:

Python
from fourier_msgs.msg import UnifiedCameraData
import rclpy
from rclpy.node import Node

class CameraDataSubscriber(Node):
def __init__(self):
super().__init__('camera_data_subscriber')
self.subscription = self.create_subscription(
UnifiedCameraData,
'/camera_01/fused_data',
self.callback,
10)

def callback(self, msg):
print(f"Camera: {msg.camera_name}")
print(f"Image size: {msg.rgb_image.width}x{msg.rgb_image.height}")
print(f"Point cloud points: {msg.point_cloud.width * msg.point_cloud.height}")
print(f"Robot pose: x={msg.robot_pose.position.x:.3f}, "
f"y={msg.robot_pose.position.y:.3f}, "
f"z={msg.robot_pose.position.z:.3f}")

rclpy.init()
node = CameraDataSubscriber()
rclpy.spin(node)

C++ subscriber:

C++
#include <rclcpp/rclcpp.hpp>
#include <fourier_msgs/msg/unified_camera_data.hpp>

class CameraDataSubscriber : public rclcpp::Node {
public:
CameraDataSubscriber() : Node("camera_data_subscriber") {
subscription_ = create_subscription<fourier_msgs::msg::UnifiedCameraData>(
"/camera_01/fused_data", 10,
[this](fourier_msgs::msg::UnifiedCameraData::SharedPtr msg) {
RCLCPP_INFO(get_logger(), "Camera: %s", msg->camera_name.c_str());
RCLCPP_INFO(get_logger(), "Image size: %dx%d",
msg->rgb_image.width, msg->rgb_image.height);
RCLCPP_INFO(get_logger(), "Point cloud points: %d",
msg->point_cloud.width * msg->point_cloud.height);
RCLCPP_INFO(get_logger(), "Robot pose: [%.3f, %.3f, %.3f]",
msg->robot_pose.position.x,
msg->robot_pose.position.y,
msg->robot_pose.position.z);
});
}

private:
rclcpp::Subscription<fourier_msgs::msg::UnifiedCameraData>::SharedPtr subscription_;
};

6.3.2 /camera_01/filtered_pointcloud (Topic)​

Publish filtered point-cloud data

Message Type: sensor_msgs/msg/PointCloud2

Behavior:

  • Applies the configured point-cloud filters:
    • Voxel downsampling (VoxelGridFilter): reduces point-cloud density.
    • Region filtering (RegionFilter): keeps points inside the configured 3D region.
    • Ground filtering (GroundFilter): removes ground points.
    • Frame transformation (TransformFilter): transforms the point cloud to the target frame.
  • Publishes the filtered point cloud for obstacle detection and navigation.

Coordinate frame: base_link, or the frame configured for the transform filter.

Subscription Example:

Bash
ros2 topic echo /camera_01/filtered_pointcloud

Python subscriber:

Python
from sensor_msgs.msg import PointCloud2
import rclpy
from rclpy.node import Node

class FilteredCloudSubscriber(Node):
def __init__(self):
super().__init__('filtered_cloud_subscriber')
self.subscription = self.create_subscription(
PointCloud2,
'/camera_01/filtered_pointcloud',
self.callback,
10)

def callback(self, msg):
point_count = msg.width * msg.height
print(f"Filtered point cloud: {point_count} points")
print(f"Frame ID: {msg.header.frame_id}")
print(f"Timestamp: {msg.header.stamp.sec}.{msg.header.stamp.nanosec}")

rclpy.init()
node = FilteredCloudSubscriber()
rclpy.spin(node)

C++ subscriber:

C++
#include <rclcpp/rclcpp.hpp>
#include <sensor_msgs/msg/point_cloud2.hpp>

class FilteredCloudSubscriber : public rclcpp::Node {
public:
FilteredCloudSubscriber() : Node("filtered_cloud_subscriber") {
subscription_ = create_subscription<sensor_msgs::msg::PointCloud2>(
"/camera_01/filtered_pointcloud", 10,
[this](sensor_msgs::msg::PointCloud2::SharedPtr msg) {
int point_count = msg->width * msg->height;
RCLCPP_INFO(get_logger(), "Filtered point cloud: %d points", point_count);
RCLCPP_INFO(get_logger(), "Frame ID: %s", msg->header.frame_id.c_str());
});
}

private:
rclcpp::Subscription<sensor_msgs::msg::PointCloud2>::SharedPtr subscription_;
};

7. Health Monitoring APIs​

The health-monitoring interfaces aggregate component status and report active warnings and errors.

7.1 /Humanoid_nav/health (Topic)​

Publish aggregated system health status

Message Type: fourier_msgs/msg/HealthInfo

Publication rate: 10 Hz by default (configurable).

Subscription Example:

Bash
ros2 topic echo /Humanoid_nav/health

Each error includes a severity and error code defined by BaseErrorInfo.

7.2 Python Subscription Example​

Python
from fourier_msgs.msg import HealthInfo
import rclpy
from rclpy.node import Node

class HealthSubscriber(Node):
def __init__(self):
super().__init__('health_subscriber')
self.subscription = self.create_subscription(
HealthInfo, '/Humanoid_nav/health', self.health_callback, 10)

def health_callback(self, msg):
if msg.has_fatal:
print("Fatal error")
elif msg.has_error:
print("An error is active")
elif msg.has_warning:
print("A warning is active")
else:
print("System healthy")

for error in msg.errors:
print(f" [{hex(error.error_code)}] {error.message}")

rclpy.init()
node = HealthSubscriber()
rclpy.spin(node)

7.3 C++ Subscription Example​

C++
#include <rclcpp/rclcpp.hpp>
#include <fourier_msgs/msg/health_info.hpp>

class HealthSubscriber : public rclcpp::Node {
public:
HealthSubscriber() : Node("health_subscriber") {
subscription_ = create_subscription<fourier_msgs::msg::HealthInfo>(
"/Humanoid_nav/health", 10,
[this](fourier_msgs::msg::HealthInfo::SharedPtr msg) {
if (msg->has_fatal) {
RCLCPP_FATAL(get_logger(), "Fatal error");
} else if (msg->has_error) {
RCLCPP_ERROR(get_logger(), "An error is active");
} else if (msg->has_warning) {
RCLCPP_WARN(get_logger(), "A warning is active");
}

for (const auto& error : msg->errors) {
RCLCPP_INFO(get_logger(), "[0x%08X] %s",
error.error_code, error.message.c_str());
}
});
}

private:
rclcpp::Subscription<fourier_msgs::msg::HealthInfo>::SharedPtr subscription_;
};

8. Event Notification APIs​

The event interfaces aggregate and publish navigation-related system events.

8.1 /Humanoid_nav/events (Topic)​

Publish aggregated system events

Message Type: fourier_msgs/msg/EventsInfo

Publication rate: 10 Hz by default (configurable).

Subscription Example:

Bash
ros2 topic echo /Humanoid_nav/events

See BaseEventInfo for the complete event-type list and trigger behavior.

8.1.1 goal_in_forbidden_area (Navigation Goal in a Forbidden Area)​

During navigate_to_pose goal validation, Map Manager checks whether the requested goal lies inside a forbidden-area polygon. If it does, navigation is rejected and this event is published.

FieldValue
event_typegoal_in_forbidden_area
sourcenavigation
messageGoal pose (x, y) lies inside forbidden area id=N on floor '1F'.

Difference from enter_forbidden_area: goal_in_forbidden_area validates the requested goal when navigation starts. enter_forbidden_area monitors the robot's current position while it is moving and is published by monitor.

Recommended handling: Ask the user to choose another goal, or update the forbidden areas through Map Manager before starting navigation again.

8.2 Python Subscription Example​

Python
from fourier_msgs.msg import EventsInfo
import rclpy
from rclpy.node import Node

class EventsSubscriber(Node):
def __init__(self):
super().__init__('events_subscriber')
self.subscription = self.create_subscription(
EventsInfo, '/Humanoid_nav/events', self.events_callback, 10)

def events_callback(self, msg):
for event in msg.events:
print(f"[{event.event_type}] {event.message} (from: {event.source})")

# Handle specific event types
if event.event_type == "obstacle_blocked":
print("Warning: path is blocked; replanning may be required")
elif event.event_type == "near_obstacle":
print("Warning: a nearby obstacle was detected")
elif event.event_type == "out_of_map":
print("Warning: robot left the map boundary or entered an unknown area")
elif event.event_type == "enter_forbidden_area":
print("Warning: robot entered a forbidden area")
elif event.event_type == "goal_in_forbidden_area":
print("Error: navigation goal is inside a forbidden area; choose another goal")
elif event.event_type == "enter_dangerous_area":
print("Warning: robot entered a speed-restricted area")
elif event.event_type == "map_loop_closure":
print("Info: loop closure detected; map optimized")

rclpy.init()
node = EventsSubscriber()
rclpy.spin(node)

8.3 C++ Subscription Example​

C++
#include <rclcpp/rclcpp.hpp>
#include <fourier_msgs/msg/events_info.hpp>

class EventsSubscriber : public rclcpp::Node {
public:
EventsSubscriber() : Node("events_subscriber") {
subscription_ = create_subscription<fourier_msgs::msg::EventsInfo>(
"/Humanoid_nav/events", 10,
[this](fourier_msgs::msg::EventsInfo::SharedPtr msg) {
for (const auto& event : msg->events) {
RCLCPP_INFO(get_logger(), "[%s] %s (from: %s)",
event.event_type.c_str(),
event.message.c_str(),
event.source.c_str());

if (event.event_type == "obstacle_blocked") {
RCLCPP_WARN(get_logger(), "Path is blocked; replanning may be required");
} else if (event.event_type == "near_obstacle") {
RCLCPP_WARN(get_logger(), "A nearby obstacle was detected");
} else if (event.event_type == "out_of_map") {
RCLCPP_WARN(get_logger(), "Robot left the map boundary or entered an unknown area");
} else if (event.event_type == "enter_forbidden_area") {
RCLCPP_WARN(get_logger(), "Robot entered a forbidden area");
} else if (event.event_type == "goal_in_forbidden_area") {
RCLCPP_ERROR(get_logger(), "Navigation goal is inside a forbidden area; choose another goal");
} else if (event.event_type == "enter_dangerous_area") {
RCLCPP_WARN(get_logger(), "Robot entered a speed-restricted area");
}
}
});
}

private:
rclcpp::Subscription<fourier_msgs::msg::EventsInfo>::SharedPtr subscription_;
};

9. Map Management APIs​

Map Manager manages composite maps and floors, together with POIs, virtual walls, forbidden areas, speed-restricted areas, rooms, and semantic objects.

9.0 Namespace supports​

Map Manager supports ROS 2 namespaces for multi-robot deployments.

Service paths: Services use root paths by default. When a namespace is enabled, it is prefixed to every service path.

NamespaceService path
No namespace (default)/add_floor, /list_floors
namespace:=robot1/robot1/add_floor、/robot1/list_floors

Command-line prefix (MM):

Bash
# Single-robot mode without a namespace: services use root paths
MM=

# Multi-robot mode with a namespace such as robot1
MM=/robot1

# Use ${MM} for all subsequent service calls
ros2 service call ${MM}/load_composite_map map_manager/srv/LoadCompositeMap ...

Configuration notes:ros2 service list | grep -E "add_floor|list_floors" path

Topic paths:

Topic without a namespaceTopic under robot1
/costmap_filter_info/robot1/costmap_filter_info
/keepout_filter_mask/robot1/keepout_filter_mask

Launch examples:

Bash
# Single robot (default)
ros2 launch map_manager map_manager.launch.py

# Multiple robots with namespaces
ros2 launch map_manager map_manager.launch.py namespace:=robot1 use_namespace:=true

9.1 Message Type Definitions​

9.1.1 Floor Message​

Message Type: map_manager/msg/Floor

FieldTypeDescription
floor_idstringUnique floor identifier
namestringFloor name
levelint32Numeric floor level, for example 1, 2, or -1.
min_heightfloat64Minimum floor height (m)
max_heightfloat64Maximum floor height (m)
reference_heightfloat64(m)
origin_offsetgeometry_msgs/Pose
map_pathstringMap file path
statusuint8status (0=UNKNOWN, 1=ACTIVE, 2=INACTIVE)

9.1.2 CompositeMapInfo Message​

Message Type: map_manager/msg/CompositeMapInfo

FieldTypeDescription
map_idstringUnique map identifier.
namestringMap name
versionstring
created_atbuiltin_interfaces/Timecreate
modified_atbuiltin_interfaces/Time
origingeometry_msgs/Posemap
floorsmap_manager/msg/Floor[]List of floors
transitionsmap_manager/msg/FloorTransition[]List of floor transition points
root_pathstringmap

9.1.3 POI Message​

Message Type: map_manager/msg/POI

FieldTypeDescription
idstringPOI Unique identifier
namestringPOI Name
typestringPOI Type (door, room, charging_station )
floor_idstringFloor identifier; leave empty when the interface allows all floors.
posegeometry_msgs/Pose2D2D (x, y, theta)
heightfloat64Floor reference height (m)
propertiesstring(JSON )

9.1.4 VirtualWall Message​

Message Type: map_manager/msg/VirtualWall

FieldTypeDescription
idint32virtual wall ID
floor_idstringfloor ID
startgeometry_msgs/Point
endgeometry_msgs/Point

9.1.5 ForbiddenArea Message​

Message Type: map_manager/msg/ForbiddenArea

FieldTypeDescription
idint32forbidden area ID
boundarygeometry_msgs/Polygon

9.1.6 DangerousArea Message​

Message Type: map_manager/msg/DangerousArea

FieldTypeDescription
idint32speed-restricted area ID
boundarygeometry_msgs/Polygon
speed_limitfloat32speed limit (m/s)

9.1.7 Room Message​

Message Type: map_manager/msg/Room

FieldTypeDescription
idstringroom ID
namestringHuman-readable room name.
typestringRoom type.
boundarygeometry_msgs/Point[]
floor_heightfloat64
ceiling_heightfloat64
connected_roomsstring[]room ID list
objectsstring[]contains ID list

9.1.8 SemanticObject Message​

Message Type: map_manager/msg/SemanticObject

FieldTypeDescription
idstringID
namestringName
typestringType
categorystring
posegeometry_msgs/Pose3D
dimensionsgeometry_msgs/Vector3(x, y, z)
is_staticboolyesno

9.1.9 FloorTransition Message​

Message Type: map_manager/msg/FloorTransition

FieldTypeDescription
idstringID
namestringName
transition_typeuint8Type (0=, 1=, 2=, 3=)
from_floor_idstringfloor ID
from_posegeometry_msgs/Pose2D
to_floor_idstringfloor ID
to_posegeometry_msgs/Pose2D
bidirectionalboolyesno
costfloat64
availableboolyesno

9.2 Composite Map Services​

Note: The following examples use services without a namespace (/xxx). With a namespace, use /<namespace>/xxx. ${MM} represents the optional namespace prefix.

9.2.1 /load_composite_map (Service)​

Load a composite map from a directory or .mmap archive.

Service Type: map_manager/srv/LoadCompositeMap

Request:

FieldTypeDescription
map_pathstringMap directory or .mmap archive path.

Response:

FieldTypeDescription
successboolWhether the operation succeeded
messagestringresult message
map_infomap_manager/msg/CompositeMapInfoLoaded map information

Example Call:

Bash
# Load a map in directory format
ros2 service call ${MM}/load_composite_map map_manager/srv/LoadCompositeMap \
"{map_path: '/opt/fftai/Navigation/Map/office'}"

# Load a map from a compressed archive
ros2 service call ${MM}/load_composite_map map_manager/srv/LoadCompositeMap \
"{map_path: '/opt/fftai/Navigation/Map/office.mmap'}"

Python:

Python
from map_manager.srv import LoadCompositeMap
import rclpy
from rclpy.node import Node

class MapLoader(Node):
def __init__(self):
super().__init__('map_loader')
self.client = self.create_client(LoadCompositeMap, '/load_composite_map')
self.client.wait_for_service()

def load(self, path: str):
request = LoadCompositeMap.Request()
request.map_path = path
future = self.client.call_async(request)
rclpy.spin_until_future_complete(self, future)
result = future.result()
if result.success:
print(f"Loaded map: {result.map_info.name}")
print(f"Floors: {[f.floor_id for f in result.map_info.floors]}")
return result

rclpy.init()
loader = MapLoader()
loader.load('/opt/fftai/Navigation/Map/office')

9.2.2 /save_composite_map (Service)​

Save a composite map in directory or compressed .mmap format.

Service Type: map_manager/srv/SaveCompositeMap

Request:

FieldTypeDefaultDescription
map_pathstring-Destination path.
compressboolfalseyesno .mmap

Response:

FieldTypeDescription
successboolWhether the operation succeeded
messagestringresult message
saved_pathstringActual output path, including an added .mmap extension when applicable.

Example Call:

Bash
# Save in directory format
ros2 service call ${MM}/save_composite_map map_manager/srv/SaveCompositeMap \
"{map_path: '/opt/fftai/Navigation/Map/my_map', compress: false}"

# Save as a compressed archive
ros2 service call ${MM}/save_composite_map map_manager/srv/SaveCompositeMap \
"{map_path: '/opt/fftai/Navigation/Map/my_map', compress: true}"

# Compress automatically when the path ends in .mmap
ros2 service call ${MM}/save_composite_map map_manager/srv/SaveCompositeMap \
"{map_path: '/opt/fftai/Navigation/Map/my_map.mmap'}"

9.2.3 /clear_composite_map (Service)​

Clear the currently loaded composite map.

Service Type: map_manager/srv/ClearCompositeMap


9.3 Floor Management Services​

9.3.1 /list_floors (Service)​

List all floors

Service Type: map_manager/srv/ListFloors

Response:

FieldTypeDescription
floorsmap_manager/msg/Floor[]List of floors
successboolWhether the operation succeeded
messagestringresult message

Example Call:

Bash
ros2 service call ${MM}/list_floors map_manager/srv/ListFloors

Python:

Python
from map_manager.srv import ListFloors
import rclpy
from rclpy.node import Node

class FloorLister(Node):
def __init__(self):
super().__init__('floor_lister')
self.client = self.create_client(ListFloors, '/list_floors')
self.client.wait_for_service()

def list_floors(self):
request = ListFloors.Request()
future = self.client.call_async(request)
rclpy.spin_until_future_complete(self, future)
result = future.result()

if result.success:
print(f"Total floors: {len(result.floors)}")
for floor in result.floors:
status = ['UNKNOWN', 'ACTIVE', 'INACTIVE'][floor.status]
print(f" {floor.floor_id}: {floor.name} (level {floor.level}, {status})")
print(f" Height: {floor.min_height:.2f} ~ {floor.max_height:.2f} m")
return result

rclpy.init()
lister = FloorLister()
lister.list_floors()

C++:

C++
#include <rclcpp/rclcpp.hpp>
#include <map_manager/srv/list_floors.hpp>

class FloorLister : public rclcpp::Node {
public:
FloorLister() : Node("floor_lister") {
client_ = create_client<map_manager::srv::ListFloors>("/list_floors");
client_->wait_for_service();
}

void list_floors() {
auto request = std::make_shared<map_manager::srv::ListFloors::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(), "Total floors: %zu", result->floors.size());
for (const auto& floor : result->floors) {
const char* status[] = {"UNKNOWN", "ACTIVE", "INACTIVE"};
RCLCPP_INFO(get_logger(), " %s: %s (level %d, %s)",
floor.floor_id.c_str(), floor.name.c_str(),
floor.level, status[floor.status]);
RCLCPP_INFO(get_logger(), " Height: %.2f ~ %.2f m",
floor.min_height, floor.max_height);
}
}
}
}

private:
rclcpp::Client<map_manager::srv::ListFloors>::SharedPtr client_;
};

9.3.2 /get_current_floor (Service)​

Get the current floor

Service Type: map_manager/srv/GetCurrentFloor

Response:

FieldTypeDescription
floormap_manager/msg/FloorCurrent floor information
successboolWhether the operation succeeded
messagestringresult message

Example Call:

Bash
ros2 service call ${MM}/get_current_floor map_manager/srv/GetCurrentFloor

Python:

Python
from map_manager.srv import GetCurrentFloor
import rclpy
from rclpy.node import Node

class CurrentFloorGetter(Node):
def __init__(self):
super().__init__('current_floor_getter')
self.client = self.create_client(GetCurrentFloor, '/get_current_floor')
self.client.wait_for_service()

def get_current_floor(self):
request = GetCurrentFloor.Request()
future = self.client.call_async(request)
rclpy.spin_until_future_complete(self, future)
result = future.result()

if result.success:
floor = result.floor
print(f"Current floor: {floor.floor_id} ({floor.name})")
print(f" Level: {floor.level}")
print(f" Height range: {floor.min_height:.2f} ~ {floor.max_height:.2f} m")
return result.floor

rclpy.init()
getter = CurrentFloorGetter()
current = getter.get_current_floor()

C++:

C++
#include <rclcpp/rclcpp.hpp>
#include <map_manager/srv/get_current_floor.hpp>

class CurrentFloorGetter : public rclcpp::Node {
public:
CurrentFloorGetter() : Node("current_floor_getter") {
client_ = create_client<map_manager::srv::GetCurrentFloor>(
"/get_current_floor");
client_->wait_for_service();
}

map_manager::msg::Floor get_current_floor() {
auto request = std::make_shared<map_manager::srv::GetCurrentFloor::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(), "Current floor: %s (%s), level %d",
result->floor.floor_id.c_str(), result->floor.name.c_str(),
result->floor.level);
return result->floor;
}
}
return map_manager::msg::Floor();
}

private:
rclcpp::Client<map_manager::srv::GetCurrentFloor>::SharedPtr client_;
};

9.3.3 /switch_floor (Service)​

switchspecifiedfloor

Service Type: map_manager/srv/SwitchFloor

Request:

FieldTypeDescription
floor_idstringfloor ID
initial_posegeometry_msgs/Pose2Dswitchinitial pose
use_transitionboolyesno
transition_idstringFloor-transition ID; required when use_transition is true.

Response:

FieldTypeDescription
successboolWhether the operation succeeded
messagestringresult message
current_floormap_manager/msg/FloorFloor information after the switch.

Example Call:

Bash
# Switch floors directly
ros2 service call ${MM}/switch_floor map_manager/srv/SwitchFloor \
"{floor_id: '2F', initial_pose: {x: 0.0, y: 0.0, theta: 0.0}, use_transition: false}"

# Switch through an elevator transition
ros2 service call ${MM}/switch_floor map_manager/srv/SwitchFloor \
"{floor_id: '2F', use_transition: true, transition_id: 'elevator_1'}"

9.3.4 /add_floor (Service)​

addfloor

Service Type: map_manager/srv/AddFloor

Request:

FieldTypeDescription
floormap_manager/msg/FloorFloor information

Response:

FieldTypeDescription
successboolWhether the operation succeeded
messagestringresult message
floor_idstringaddfloor ID

Example Call:

Bash
ros2 service call ${MM}/add_floor map_manager/srv/AddFloor \
"{floor: {floor_id: '2F', name: 'Second Floor', level: 2, min_height: 3.0, max_height: 6.0, reference_height: 3.0, status: 1}}"

Python:

Python
from map_manager.srv import AddFloor
from map_manager.msg import Floor
import rclpy
from rclpy.node import Node

class FloorManager(Node):
def __init__(self):
super().__init__('floor_manager')
self.client = self.create_client(AddFloor, '/add_floor')
self.client.wait_for_service()

def add_floor(self, floor_id: str, name: str, level: int,
min_height: float = 0.0, max_height: float = 3.0):
request = AddFloor.Request()
request.floor = Floor()
request.floor.floor_id = floor_id
request.floor.name = name
request.floor.level = level
request.floor.min_height = min_height
request.floor.max_height = max_height
request.floor.reference_height = min_height
request.floor.status = 1 # ACTIVE

future = self.client.call_async(request)
rclpy.spin_until_future_complete(self, future)
result = future.result()

if result.success:
print(f"Floor added: {result.floor_id}")
else:
print(f"Failed: {result.message}")
return result.success

rclpy.init()
manager = FloorManager()
manager.add_floor('2F', 'Second Floor', 2, 3.0, 6.0)

C++:

C++
#include <rclcpp/rclcpp.hpp>
#include <map_manager/srv/add_floor.hpp>
#include <map_manager/msg/floor.hpp>

class FloorManager : public rclcpp::Node {
public:
FloorManager() : Node("floor_manager") {
client_ = create_client<map_manager::srv::AddFloor>("/add_floor");
client_->wait_for_service();
}

bool add_floor(const std::string& floor_id, const std::string& name, int32_t level,
double min_height = 0.0, double max_height = 3.0) {
auto request = std::make_shared<map_manager::srv::AddFloor::Request>();
request->floor.floor_id = floor_id;
request->floor.name = name;
request->floor.level = level;
request->floor.min_height = min_height;
request->floor.max_height = max_height;
request->floor.reference_height = min_height;
request->floor.status = 1; // ACTIVE

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(), "Floor added: %s", result->floor_id.c_str());
return true;
}
RCLCPP_ERROR(get_logger(), "Failed: %s", result->message.c_str());
}
return false;
}

private:
rclcpp::Client<map_manager::srv::AddFloor>::SharedPtr client_;
};

9.3.5 /remove_floor (Service)​

Remove a floor

Service Type: map_manager/srv/RemoveFloor

Request:

FieldTypeDescription
floor_idstringfloor ID

Response:

FieldTypeDescription
successboolWhether the operation succeeded
messagestringresult message

Example Call:

Bash
ros2 service call ${MM}/remove_floor map_manager/srv/RemoveFloor \
"{floor_id: '2F'}"

Python:

Python
from map_manager.srv import RemoveFloor
import rclpy
from rclpy.node import Node

class FloorRemover(Node):
def __init__(self):
super().__init__('floor_remover')
self.client = self.create_client(RemoveFloor, '/remove_floor')
self.client.wait_for_service()

def remove_floor(self, floor_id: str):
request = RemoveFloor.Request()
request.floor_id = floor_id
future = self.client.call_async(request)
rclpy.spin_until_future_complete(self, future)
result = future.result()
return result.success

rclpy.init()
remover = FloorRemover()
remover.remove_floor('2F')

C++:

C++
#include <rclcpp/rclcpp.hpp>
#include <map_manager/srv/remove_floor.hpp>

class FloorRemover : public rclcpp::Node {
public:
FloorRemover() : Node("floor_remover") {
client_ = create_client<map_manager::srv::RemoveFloor>("/remove_floor");
client_->wait_for_service();
}

bool remove_floor(const std::string& floor_id) {
auto request = std::make_shared<map_manager::srv::RemoveFloor::Request>();
request->floor_id = floor_id;

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<map_manager::srv::RemoveFloor>::SharedPtr client_;
};

9.3.6 /save_floor (Service)​

Save data for the current floor.

Service Type: map_manager/srv/SaveFloor

Request:

FieldTypeDescription
floor_idstringFloor identifier; leave empty to select the current floor.

Response:

FieldTypeDescription
successboolWhether the operation succeeded
messagestringresult message
saved_pathstringActual output path.

Example Call:

Bash
# Save the current floor
ros2 service call ${MM}/save_floor map_manager/srv/SaveFloor "{floor_id: ''}"

# Save a specified floor
ros2 service call ${MM}/save_floor map_manager/srv/SaveFloor "{floor_id: '1F'}"

Python:

Python
from map_manager.srv import SaveFloor
import rclpy
from rclpy.node import Node

class FloorSaver(Node):
def __init__(self):
super().__init__('floor_saver')
self.client = self.create_client(SaveFloor, '/save_floor')
self.client.wait_for_service()

def save_floor(self, floor_id: str = ''):
"""Save a floor; an empty floor_id selects the current floor."""
request = SaveFloor.Request()
request.floor_id = floor_id

future = self.client.call_async(request)
rclpy.spin_until_future_complete(self, future, timeout_sec=120.0)
result = future.result()

if result.success:
print(f"Floor saved to: {result.saved_path}")
else:
print(f"Failed: {result.message}")
return result.success

rclpy.init()
saver = FloorSaver()
saver.save_floor('1F') # Save floor 1F

C++:

C++
#include <rclcpp/rclcpp.hpp>
#include <map_manager/srv/save_floor.hpp>

class FloorSaver : public rclcpp::Node {
public:
FloorSaver() : Node("floor_saver") {
client_ = create_client<map_manager::srv::SaveFloor>("/save_floor");
client_->wait_for_service();
}

bool save_floor(const std::string& floor_id = "") {
auto request = std::make_shared<map_manager::srv::SaveFloor::Request>();
request->floor_id = floor_id;

auto future = client_->async_send_request(request);
if (rclcpp::spin_until_future_complete(shared_from_this(), future,
std::chrono::seconds(120)) == rclcpp::FutureReturnCode::SUCCESS) {
auto result = future.get();
if (result->success) {
RCLCPP_INFO(get_logger(), "Floor saved to: %s",
result->saved_path.c_str());
return true;
}
RCLCPP_ERROR(get_logger(), "Failed: %s", result->message.c_str());
}
return false;
}

private:
rclcpp::Client<map_manager::srv::SaveFloor>::SharedPtr client_;
};

9.3.7 /load_floor (Service)​

Load all data for a floor, including localization, navigation, semantic, and virtual layers. The floor must already exist.

Service Type: map_manager/srv/LoadFloor

Request:

FieldTypeDescription
floor_idstringIdentifier of the floor to load.
input_pathstringPath to the floor data.

Response:

FieldTypeDescription
successboolWhether the operation succeeded
messagestringresult message
floor_idstringloadfloor ID

Example Call:

Bash
ros2 service call ${MM}/load_floor map_manager/srv/LoadFloor \
"{floor_id: '1F', input_path: '/data/composite_map/floors/1F'}"

Python:

Python
from map_manager.srv import LoadFloor
import rclpy
from rclpy.node import Node

class FloorLoader(Node):
def __init__(self):
super().__init__('floor_loader')
self.client = self.create_client(LoadFloor, '/load_floor')
self.client.wait_for_service()

def load_floor(self, floor_id: str, input_path: str):
request = LoadFloor.Request()
request.floor_id = floor_id
request.input_path = input_path
future = self.client.call_async(request)
rclpy.spin_until_future_complete(self, future, timeout_sec=60.0)
result = future.result()
if result.success:
print(f"Floor loaded: {result.floor_id}")
else:
print(f"Failed: {result.message}")
return result.success

rclpy.init()
loader = FloorLoader()
loader.load_floor('1F', '/data/composite_map/floors/1F')

9.3.8 /clear_floor (Service)​

Clear all data associated with a specified floor without deleting the floor entry. Use remove_floor to delete the floor itself.

Service Type: map_manager/srv/ClearFloor

Request:

FieldTypeDescription
floor_idstringfloor ID

Response:

FieldTypeDescription
successboolWhether the operation succeeded
messagestringresult message

Example Call:

Bash
ros2 service call ${MM}/clear_floor map_manager/srv/ClearFloor \
"{floor_id: '1F'}"

Python:

Python
from map_manager.srv import ClearFloor
import rclpy
from rclpy.node import Node

class FloorClearer(Node):
def __init__(self):
super().__init__('floor_clearer')
self.client = self.create_client(ClearFloor, '/clear_floor')
self.client.wait_for_service()

def clear_floor(self, floor_id: str):
request = ClearFloor.Request()
request.floor_id = floor_id
future = self.client.call_async(request)
rclpy.spin_until_future_complete(self, future)
result = future.result()
if result.success:
print(f"Floor {floor_id} cleared")
else:
print(f"Failed: {result.message}")
return result.success

rclpy.init()
clearer = FloorClearer()
clearer.clear_floor('1F')

9.4 POI Management Services​

9.4.1 /add_poi (Service)​

Add a POI

Service Type: map_manager/srv/AddPOI

Request:

FieldTypeDescription
poimap_manager/msg/POIPOI data

Response:

FieldTypeDescription
successboolWhether the operation succeeded
messagestringresult message

Example Call:

Bash
ros2 service call ${MM}/add_poi map_manager/srv/AddPOI \
"{poi: {id: 'charging_1', name: 'Charging Station', type: 'charging_station', floor_id: '1F', pose: {x: 5.0, y: 3.0, theta: 1.57}}}"

Python:

Python
from map_manager.srv import AddPOI
from map_manager.msg import POI
from geometry_msgs.msg import Pose2D
import rclpy
from rclpy.node import Node

class POIManager(Node):
def __init__(self):
super().__init__('poi_manager')
self.client = self.create_client(AddPOI, '/add_poi')
self.client.wait_for_service()

def add_poi(self, poi_id: str, name: str, poi_type: str,
floor_id: str, x: float, y: float, theta: float = 0.0):
request = AddPOI.Request()
request.poi = POI()
request.poi.id = poi_id
request.poi.name = name
request.poi.type = poi_type
request.poi.floor_id = floor_id
request.poi.pose = Pose2D(x=x, y=y, theta=theta)

future = self.client.call_async(request)
rclpy.spin_until_future_complete(self, future)
result = future.result()
return result.success

# Example
rclpy.init()
manager = POIManager()
success = manager.add_poi('charging_1', 'Charging Station',
'charging_station', '1F', 5.0, 3.0, 1.57)
print(f"POI added: {success}")

C++:

C++
#include <rclcpp/rclcpp.hpp>
#include <map_manager/srv/add_poi.hpp>
#include <map_manager/msg/poi.hpp>

class POIManager : public rclcpp::Node {
public:
POIManager() : Node("poi_manager") {
client_ = create_client<map_manager::srv::AddPOI>("/add_poi");
client_->wait_for_service();
}

bool add_poi(const std::string& id, const std::string& name,
const std::string& type, const std::string& floor_id,
double x, double y, double theta = 0.0) {
auto request = std::make_shared<map_manager::srv::AddPOI::Request>();
request->poi.id = id;
request->poi.name = name;
request->poi.type = type;
request->poi.floor_id = floor_id;
request->poi.pose.x = x;
request->poi.pose.y = y;
request->poi.pose.theta = theta;

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(), "%s", result->message.c_str());
return result->success;
}
return false;
}

private:
rclcpp::Client<map_manager::srv::AddPOI>::SharedPtr client_;
};

9.4.2 /list_pois (Service)​

List POIs

Service Type: map_manager/srv/ListPOIs

Request:

FieldTypeDescription
floor_idstringFloor identifier; leave empty to select all floors.

Response:

FieldTypeDescription
poismap_manager/msg/POI[]List of POIs
successboolWhether the operation succeeded
messagestringresult message

Example Call:

Bash
# List POIs on all floors
ros2 service call ${MM}/list_pois map_manager/srv/ListPOIs "{floor_id: ''}"

# List POIs on floor 1F
ros2 service call ${MM}/list_pois map_manager/srv/ListPOIs "{floor_id: '1F'}"

Python:

Python
from map_manager.srv import ListPOIs
import rclpy
from rclpy.node import Node

class POILister(Node):
def __init__(self):
super().__init__('poi_lister')
self.client = self.create_client(ListPOIs, '/list_pois')
self.client.wait_for_service()

def list_pois(self, floor_id: str = ''):
"""List POIs; an empty floor_id selects all floors."""
request = ListPOIs.Request()
request.floor_id = floor_id

future = self.client.call_async(request)
rclpy.spin_until_future_complete(self, future)
result = future.result()

if result.success:
print(f"Found {len(result.pois)} POIs")
for poi in result.pois:
print(f" - {poi.id}: {poi.name} ({poi.type}) at "
f"({poi.pose.x:.2f}, {poi.pose.y:.2f}) on floor {poi.floor_id}")
return result

# Example
rclpy.init()
lister = POILister()
lister.list_pois('1F') # List POIs on floor 1F

C++:

C++
#include <rclcpp/rclcpp.hpp>
#include <map_manager/srv/list_pois.hpp>

class POILister : public rclcpp::Node {
public:
POILister() : Node("poi_lister") {
client_ = create_client<map_manager::srv::ListPOIs>("/list_pois");
client_->wait_for_service();
}

void list_pois(const std::string& floor_id = "") {
auto request = std::make_shared<map_manager::srv::ListPOIs::Request>();
request->floor_id = floor_id;

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(), "Found %zu POIs", result->pois.size());
for (const auto& poi : result->pois) {
RCLCPP_INFO(get_logger(), " - %s: %s (%s) at (%.2f, %.2f) on floor %s",
poi.id.c_str(), poi.name.c_str(), poi.type.c_str(),
poi.pose.x, poi.pose.y, poi.floor_id.c_str());
}
}
}
}

private:
rclcpp::Client<map_manager::srv::ListPOIs>::SharedPtr client_;
};

9.4.3 /get_poi (Service)​

get POI

Service Type: map_manager/srv/GetPOI

Request:

FieldTypeDescription
floor_idstringFloor identifier; leave empty to select all floors.
idstringPOI ID

Response:

FieldTypeDescription
poimap_manager/msg/POIPOI data
successboolWhether the operation succeeded
messagestringresult message

Example Call:

Bash
ros2 service call ${MM}/get_poi map_manager/srv/GetPOI \
"{floor_id: '1F', id: 'charging_1'}"

9.4.4 /update_poi (Service)​

Update a POI

Service Type: map_manager/srv/UpdatePOI

Request:

FieldTypeDescription
poimap_manager/msg/POIUpdated POI data; must include id and floor_id.

Response:

FieldTypeDescription
successboolWhether the operation succeeded
messagestringresult message

Example Call:

Bash
ros2 service call ${MM}/update_poi map_manager/srv/UpdatePOI \
"{poi: {id: 'charging_1', name: 'Main Charger', type: 'charging_station', floor_id: '1F', pose: {x: 5.5, y: 3.0, theta: 1.57}}}"

9.4.5 /remove_poi (Service)​

Remove a POI

Service Type: map_manager/srv/RemovePOI

Request:

FieldTypeDescription
floor_idstringFloor identifier; leave empty to select all floors.
idstringPOI ID

Response:

FieldTypeDescription
successboolWhether the operation succeeded
messagestringresult message

Example Call:

Bash
ros2 service call ${MM}/remove_poi map_manager/srv/RemovePOI \
"{floor_id: '1F', id: 'charging_1'}"

9.4.6 /remove_all_pois (Service)​

Remove all POIs

Service Type: map_manager/srv/RemoveAllPOIs

Request:

FieldTypeDescription
floor_idstringFloor identifier; leave empty to remove POIs from all floors.

Response:

FieldTypeDescription
successboolWhether the operation succeeded
messagestringresult message

Example Call:

Bash
ros2 service call ${MM}/remove_all_pois map_manager/srv/RemoveAllPOIs \
"{floor_id: '1F'}"

9.5 Virtual Wall Management Services​

9.5.1 /add_virtual_wall (Service)​

Add a virtual wall

Service Type: map_manager/srv/AddVirtualWall

Request:

FieldTypeDescription
floor_idstringfloor ID
wallmap_manager/msg/VirtualWallVirtual wall data

Response:

FieldTypeDescription
successboolWhether the operation succeeded
messagestringresult message
wall_idint32addvirtual wall ID

Example Call:

Bash
ros2 service call ${MM}/add_virtual_wall map_manager/srv/AddVirtualWall \
"{floor_id: '1F', wall: {id: 1, start: {x: 0.0, y: 0.0, z: 0.0}, end: {x: 2.0, y: 0.0, z: 0.0}}}"

Python:

Python
from map_manager.srv import AddVirtualWall
from map_manager.msg import VirtualWall
from geometry_msgs.msg import Point
import rclpy
from rclpy.node import Node

class VirtualWallManager(Node):
def __init__(self):
super().__init__('virtual_wall_manager')
self.client = self.create_client(AddVirtualWall, '/add_virtual_wall')
self.client.wait_for_service()

def add_wall(self, floor_id: str, wall_id: int,
start_x: float, start_y: float,
end_x: float, end_y: float):
request = AddVirtualWall.Request()
request.floor_id = floor_id
request.wall = VirtualWall()
request.wall.id = wall_id
request.wall.floor_id = floor_id
request.wall.start = Point(x=start_x, y=start_y, z=0.0)
request.wall.end = Point(x=end_x, y=end_y, z=0.0)

future = self.client.call_async(request)
rclpy.spin_until_future_complete(self, future)
result = future.result()

if result.success:
print(f"Virtual wall added with ID: {result.wall_id}")
else:
print(f"Failed: {result.message}")
return result.success

# Example
rclpy.init()
manager = VirtualWallManager()
# Add a virtual wall from (0, 0) to (5, 0)
manager.add_wall('1F', 1, 0.0, 0.0, 5.0, 0.0)

C++:

C++
#include <rclcpp/rclcpp.hpp>
#include <map_manager/srv/add_virtual_wall.hpp>
#include <map_manager/msg/virtual_wall.hpp>

class VirtualWallManager : public rclcpp::Node {
public:
VirtualWallManager() : Node("virtual_wall_manager") {
client_ = create_client<map_manager::srv::AddVirtualWall>(
"/add_virtual_wall");
client_->wait_for_service();
}

bool add_wall(const std::string& floor_id, int32_t wall_id,
double start_x, double start_y,
double end_x, double end_y) {
auto request = std::make_shared<map_manager::srv::AddVirtualWall::Request>();
request->floor_id = floor_id;
request->wall.id = wall_id;
request->wall.floor_id = floor_id;
request->wall.start.x = start_x;
request->wall.start.y = start_y;
request->wall.start.z = 0.0;
request->wall.end.x = end_x;
request->wall.end.y = end_y;
request->wall.end.z = 0.0;

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(), "Virtual wall added with ID: %d",
result->wall_id);
return true;
}
RCLCPP_ERROR(get_logger(), "Failed: %s", result->message.c_str());
}
return false;
}

private:
rclcpp::Client<map_manager::srv::AddVirtualWall>::SharedPtr client_;
};

9.5.2 /list_virtual_walls (Service)​

List virtual walls

Service Type: map_manager/srv/ListVirtualWalls

Request:

FieldTypeDescription
floor_idstringFloor identifier; leave empty to select all floors.

Response:

FieldTypeDescription
wallsmap_manager/msg/VirtualWall[]List of virtual walls
successboolWhether the operation succeeded
messagestringresult message

Example Call:

Bash
# List virtual walls on all floors
ros2 service call ${MM}/list_virtual_walls map_manager/srv/ListVirtualWalls "{floor_id: ''}"

# List virtual walls on floor 1F
ros2 service call ${MM}/list_virtual_walls map_manager/srv/ListVirtualWalls "{floor_id: '1F'}"

Python:

Python
from map_manager.srv import ListVirtualWalls
import rclpy
from rclpy.node import Node

class VirtualWallLister(Node):
def __init__(self):
super().__init__('virtual_wall_lister')
self.client = self.create_client(ListVirtualWalls, '/list_virtual_walls')
self.client.wait_for_service()

def list_walls(self, floor_id: str = ''):
request = ListVirtualWalls.Request()
request.floor_id = floor_id

future = self.client.call_async(request)
rclpy.spin_until_future_complete(self, future)
result = future.result()

if result.success:
print(f"Found {len(result.walls)} virtual walls")
for wall in result.walls:
print(f" Wall {wall.id}: ({wall.start.x:.2f}, {wall.start.y:.2f}) -> "
f"({wall.end.x:.2f}, {wall.end.y:.2f})")
return result

rclpy.init()
lister = VirtualWallLister()
lister.list_walls('1F')

C++:

C++
#include <rclcpp/rclcpp.hpp>
#include <map_manager/srv/list_virtual_walls.hpp>

class VirtualWallLister : public rclcpp::Node {
public:
VirtualWallLister() : Node("virtual_wall_lister") {
client_ = create_client<map_manager::srv::ListVirtualWalls>(
"/list_virtual_walls");
client_->wait_for_service();
}

void list_walls(const std::string& floor_id = "") {
auto request = std::make_shared<map_manager::srv::ListVirtualWalls::Request>();
request->floor_id = floor_id;

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(), "Found %zu virtual walls", result->walls.size());
for (const auto& wall : result->walls) {
RCLCPP_INFO(get_logger(),
" Wall %d: (%.2f, %.2f) -> (%.2f, %.2f)",
wall.id, wall.start.x, wall.start.y, wall.end.x, wall.end.y);
}
}
}
}

private:
rclcpp::Client<map_manager::srv::ListVirtualWalls>::SharedPtr client_;
};

9.5.3 /get_virtual_wall (Service)​

getvirtual wall

Service Type: map_manager/srv/GetVirtualWall

Request:

FieldTypeDescription
floor_idstringFloor identifier; leave empty to select all floors.
idint32virtual wall ID

Response:

FieldTypeDescription
wallmap_manager/msg/VirtualWallVirtual wall data
successboolWhether the operation succeeded
messagestringresult message

Example Call:

Bash
ros2 service call ${MM}/get_virtual_wall map_manager/srv/GetVirtualWall \
"{floor_id: '1F', id: 1}"

9.5.4 /update_virtual_wall (Service)​

Update a virtual wall

Service Type: map_manager/srv/UpdateVirtualWall

Request:

FieldTypeDescription
wallmap_manager/msg/VirtualWallUpdated virtual-wall data; must include id and floor_id.

Response:

FieldTypeDescription
successboolWhether the operation succeeded
messagestringresult message

Example Call:

Bash
ros2 service call ${MM}/update_virtual_wall map_manager/srv/UpdateVirtualWall \
"{wall: {id: 1, floor_id: '1F', start: {x: 0.0, y: 0.0, z: 0.0}, end: {x: 3.0, y: 0.0, z: 0.0}}}"

9.5.5 /remove_virtual_wall (Service)​

Remove a virtual wall.

Service Type: map_manager/srv/RemoveVirtualWall

Request:

FieldTypeDescription
floor_idstringFloor identifier; leave empty to select all floors.
wall_idint32virtual wall ID

Response:

FieldTypeDescription
successboolWhether the operation succeeded
messagestringresult message

Example Call:

Bash
ros2 service call ${MM}/remove_virtual_wall map_manager/srv/RemoveVirtualWall \
"{floor_id: '1F', wall_id: 1}"

9.5.6 /remove_virtual_walls (Service)​

Remove all virtual walls from a specified floor.

Service Type: map_manager/srv/RemoveVirtualWalls

Request:

FieldTypeDescription
floor_idstringFloor identifier; leave empty to remove entries from all floors.

Response:

FieldTypeDescription
successboolWhether the operation succeeded
messagestringresult message
countint32Number of virtual walls removed.

Example Call:

Bash
ros2 service call ${MM}/remove_virtual_walls map_manager/srv/RemoveVirtualWalls \
"{floor_id: '1F'}"

9.6 Forbidden Area Services​

9.6.1 /add_forbidden_area (Service)​

Add a forbidden area()

Service Type: map_manager/srv/AddForbiddenArea

Request:

FieldTypeDescription
floor_idstringfloor ID
areamap_manager/msg/ForbiddenAreaForbidden-area data()

Response:

FieldTypeDescription
successboolWhether the operation succeeded
messagestringresult message
area_idint32forbidden area ID
wall_idsint32[]virtual wall ID list

Example Call:

Bash
# Add a 2 m x 2 m square forbidden area
ros2 service call ${MM}/add_forbidden_area map_manager/srv/AddForbiddenArea \
"{floor_id: '1F', area: {id: 1, boundary: {points: [{x: 0.0, y: 0.0, z: 0.0}, {x: 2.0, y: 0.0, z: 0.0}, {x: 2.0, y: 2.0, z: 0.0}, {x: 0.0, y: 2.0, z: 0.0}]}}}"

Python:

Python
from map_manager.srv import AddForbiddenArea
from map_manager.msg import ForbiddenArea
from geometry_msgs.msg import Polygon, Point32
import rclpy
from rclpy.node import Node

class ForbiddenAreaManager(Node):
def __init__(self):
super().__init__('forbidden_area_manager')
self.client = self.create_client(AddForbiddenArea, '/add_forbidden_area')
self.client.wait_for_service()

def add_forbidden_area(self, floor_id: str, area_id: int, points: list):
"""
Add a forbidden area
points: Polygon vertices, for example [(x1, y1), (x2, y2), (x3, y3), ...]
"""
request = AddForbiddenArea.Request()
request.floor_id = floor_id
request.area = ForbiddenArea()
request.area.id = area_id
request.area.boundary = Polygon()
request.area.boundary.points = [
Point32(x=float(p[0]), y=float(p[1]), z=0.0) for p in points
]

future = self.client.call_async(request)
rclpy.spin_until_future_complete(self, future)
result = future.result()

if result.success:
print(f"Forbidden area added with ID: {result.area_id}")
print(f"Generated virtual walls: {result.wall_ids}")
else:
print(f"Failed: {result.message}")
return result.success

# Example
rclpy.init()
manager = ForbiddenAreaManager()
# Add a square forbidden area
manager.add_forbidden_area('1F', 1, [(0.0, 0.0), (2.0, 0.0), (2.0, 2.0), (0.0, 2.0)])
# Add a triangular forbidden area
manager.add_forbidden_area('1F', 2, [(5.0, 5.0), (7.0, 5.0), (6.0, 7.0)])

C++:

C++
#include <rclcpp/rclcpp.hpp>
#include <map_manager/srv/add_forbidden_area.hpp>
#include <map_manager/msg/forbidden_area.hpp>

class ForbiddenAreaManager : public rclcpp::Node {
public:
ForbiddenAreaManager() : Node("forbidden_area_manager") {
client_ = create_client<map_manager::srv::AddForbiddenArea>(
"/add_forbidden_area");
client_->wait_for_service();
}

bool add_forbidden_area(const std::string& floor_id, int32_t area_id,
const std::vector<std::pair<double, double>>& points) {
auto request = std::make_shared<map_manager::srv::AddForbiddenArea::Request>();
request->floor_id = floor_id;
request->area.id = area_id;

// Build the polygon boundary
for (const auto& p : points) {
geometry_msgs::msg::Point32 point;
point.x = p.first;
point.y = p.second;
point.z = 0.0;
request->area.boundary.points.push_back(point);
}

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(), "Forbidden area added with ID: %d",
result->area_id);
RCLCPP_INFO(get_logger(), "Generated %zu virtual walls",
result->wall_ids.size());
return true;
}
RCLCPP_ERROR(get_logger(), "Failed: %s", result->message.c_str());
}
return false;
}

private:
rclcpp::Client<map_manager::srv::AddForbiddenArea>::SharedPtr client_;
};

// Example
int main(int argc, char** argv) {
rclcpp::init(argc, argv);
auto manager = std::make_shared<ForbiddenAreaManager>();
// Add a square forbidden area
manager->add_forbidden_area("1F", 1, {{0.0, 0.0}, {2.0, 0.0}, {2.0, 2.0}, {0.0, 2.0}});
rclcpp::shutdown();
return 0;
}

9.6.2 /list_forbidden_areas (Service)​

List forbidden areas

Service Type: map_manager/srv/ListForbiddenAreas

Request:

FieldTypeDescription
floor_idstringFloor identifier; leave empty to select all floors.

Response:

FieldTypeDescription
areasmap_manager/msg/ForbiddenArea[]List of forbidden areas
floor_idsstring[]forbidden areafloor ID
successboolWhether the operation succeeded
messagestringresult message

Example Call:

Bash
ros2 service call ${MM}/list_forbidden_areas map_manager/srv/ListForbiddenAreas \
"{floor_id: '1F'}"

Python:

Python
from map_manager.srv import ListForbiddenAreas
import rclpy
from rclpy.node import Node

class ForbiddenAreaLister(Node):
def __init__(self):
super().__init__('forbidden_area_lister')
self.client = self.create_client(ListForbiddenAreas, '/list_forbidden_areas')
self.client.wait_for_service()

def list_areas(self, floor_id: str = ''):
request = ListForbiddenAreas.Request()
request.floor_id = floor_id

future = self.client.call_async(request)
rclpy.spin_until_future_complete(self, future)
result = future.result()

if result.success:
print(f"Found {len(result.areas)} forbidden areas")
for i, area in enumerate(result.areas):
floor = result.floor_ids[i] if i < len(result.floor_ids) else 'unknown'
print(f" Area {area.id} on floor {floor}:")
print(f" Vertices: {len(area.boundary.points)}")
for j, point in enumerate(area.boundary.points):
print(f" {j}: ({point.x:.2f}, {point.y:.2f})")
return result

rclpy.init()
lister = ForbiddenAreaLister()
lister.list_areas('1F')

C++:

C++
#include <rclcpp/rclcpp.hpp>
#include <map_manager/srv/list_forbidden_areas.hpp>

class ForbiddenAreaLister : public rclcpp::Node {
public:
ForbiddenAreaLister() : Node("forbidden_area_lister") {
client_ = create_client<map_manager::srv::ListForbiddenAreas>(
"/list_forbidden_areas");
client_->wait_for_service();
}

void list_areas(const std::string& floor_id = "") {
auto request = std::make_shared<map_manager::srv::ListForbiddenAreas::Request>();
request->floor_id = floor_id;

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(), "Found %zu forbidden areas", result->areas.size());
for (size_t i = 0; i < result->areas.size(); ++i) {
const auto& area = result->areas[i];
const std::string& floor = (i < result->floor_ids.size()) ?
result->floor_ids[i] : "unknown";
RCLCPP_INFO(get_logger(), " Area %d on floor %s: %zu vertices",
area.id, floor.c_str(), area.boundary.points.size());
}
}
}
}

private:
rclcpp::Client<map_manager::srv::ListForbiddenAreas>::SharedPtr client_;
};

9.6.3 /get_forbidden_area (Service)​

getforbidden area

Service Type: map_manager/srv/GetForbiddenArea

Request:

FieldTypeDescription
floor_idstringFloor identifier; leave empty to select all floors.
idint32forbidden area ID

Response:

FieldTypeDescription
areamap_manager/msg/ForbiddenAreaForbidden-area data
area_floor_idstringforbidden areafloor ID
successboolWhether the operation succeeded
messagestringresult message

Example Call:

Bash
ros2 service call ${MM}/get_forbidden_area map_manager/srv/GetForbiddenArea \
"{floor_id: '1F', id: 1}"

9.6.4 /update_forbidden_area (Service)​

Update a forbidden area

Service Type: map_manager/srv/UpdateForbiddenArea

Request:

FieldTypeDescription
floor_idstringFloor identifier (required).
areamap_manager/msg/ForbiddenAreaUpdated forbidden-area data; must include id.

Response:

FieldTypeDescription
successboolWhether the operation succeeded
messagestringresult message
wall_idsint32[]virtual wall ID list

Example Call:

Bash
ros2 service call ${MM}/update_forbidden_area map_manager/srv/UpdateForbiddenArea \
"{floor_id: '1F', area: {id: 1, boundary: {points: [{x: 0.0, y: 0.0, z: 0.0}, {x: 3.0, y: 0.0, z: 0.0}, {x: 3.0, y: 3.0, z: 0.0}, {x: 0.0, y: 3.0, z: 0.0}]}}}"

9.6.5 /remove_forbidden_area (Service)​

Remove a forbidden area

Service Type: map_manager/srv/RemoveForbiddenArea

Request:

FieldTypeDescription
floor_idstringFloor identifier; leave empty to select all floors.
area_idint32forbidden area ID

Response:

FieldTypeDescription
successboolWhether the operation succeeded
messagestringresult message
walls_removedint32Number of associated virtual walls removed.

Example Call:

Bash
ros2 service call ${MM}/remove_forbidden_area map_manager/srv/RemoveForbiddenArea \
"{floor_id: '1F', area_id: 1}"

9.6.6 /remove_forbidden_areas (Service)​

Remove forbidden areas in bulk. An empty floor_id selects all floors.

Service Type: map_manager/srv/RemoveForbiddenAreas

Request:

FieldTypeDescription
floor_idstringFloor identifier; leave empty to remove entries from all floors.

Response:

FieldTypeDescription
successboolWhether the operation succeeded
messagestringresult message
countint32removeforbidden areacount

Example Call:

Bash
ros2 service call ${MM}/remove_forbidden_areas map_manager/srv/RemoveForbiddenAreas \
"{floor_id: '1F'}"

9.6.7 /remove_all_forbidden_areas (Service)​

Remove every forbidden area on every floor. The request has no fields.

Service Type: map_manager/srv/RemoveAllForbiddenAreas

Request: No fields.

Response:

FieldTypeDescription
successboolWhether the operation succeeded
messagestringresult message
countint32removeforbidden areacount

Example Call:

Bash
ros2 service call ${MM}/remove_all_forbidden_areas map_manager/srv/RemoveAllForbiddenAreas "{}"

9.7 Speed-Restricted Area Services​

9.7.1 /add_dangerous_area (Service)​

Add a speed-restricted area

Service Type: map_manager/srv/AddDangerousArea

Request:

FieldTypeDescription
floor_idstringfloor ID
areamap_manager/msg/DangerousAreaSpeed-restricted-area data, including speed_limit.

Response:

FieldTypeDescription
successboolWhether the operation succeeded
messagestringresult message
area_idint32speed-restricted area ID

Example Call:

Bash
# Add a 3 m x 3 m speed-restricted area with a 0.3 m/s limit
ros2 service call ${MM}/add_dangerous_area map_manager/srv/AddDangerousArea \
"{floor_id: '1F', area: {id: 1, boundary: {points: [{x: 0.0, y: 0.0, z: 0.0}, {x: 3.0, y: 0.0, z: 0.0}, {x: 3.0, y: 3.0, z: 0.0}, {x: 0.0, y: 3.0, z: 0.0}]}, speed_limit: 0.3}}"

Python:

Python
from map_manager.srv import AddDangerousArea
from map_manager.msg import DangerousArea
from geometry_msgs.msg import Polygon, Point32
import rclpy
from rclpy.node import Node

class DangerousAreaManager(Node):
def __init__(self):
super().__init__('dangerous_area_manager')
self.client = self.create_client(AddDangerousArea, '/add_dangerous_area')
self.client.wait_for_service()

def add_dangerous_area(self, floor_id: str, area_id: int,
points: list, speed_limit: float):
"""
Add a speed-restricted area
points: Polygon vertices, for example [(x1, y1), (x2, y2), ...]
speed_limit: Speed limit in meters per second; 0.3 means 0.3 m/s
"""
request = AddDangerousArea.Request()
request.floor_id = floor_id
request.area = DangerousArea()
request.area.id = area_id
request.area.boundary = Polygon()
request.area.boundary.points = [
Point32(x=float(p[0]), y=float(p[1]), z=0.0) for p in points
]
request.area.speed_limit = speed_limit

future = self.client.call_async(request)
rclpy.spin_until_future_complete(self, future)
result = future.result()

if result.success:
print(f"Dangerous area added with ID: {result.area_id}")
print(f"Speed limit: {speed_limit} m/s")
else:
print(f"Failed: {result.message}")
return result.success

# Example
rclpy.init()
manager = DangerousAreaManager()
# Add a speed-restricted area near an elevator with a 0.2 m/s limit
manager.add_dangerous_area('1F', 1,
[(5.0, 5.0), (8.0, 5.0), (8.0, 8.0), (5.0, 8.0)], 0.2)

C++:

C++
#include <rclcpp/rclcpp.hpp>
#include <map_manager/srv/add_dangerous_area.hpp>
#include <map_manager/msg/dangerous_area.hpp>

class DangerousAreaManager : public rclcpp::Node {
public:
DangerousAreaManager() : Node("dangerous_area_manager") {
client_ = create_client<map_manager::srv::AddDangerousArea>(
"/add_dangerous_area");
client_->wait_for_service();
}

bool add_dangerous_area(const std::string& floor_id, int32_t area_id,
const std::vector<std::pair<double, double>>& points,
float speed_limit) {
auto request = std::make_shared<map_manager::srv::AddDangerousArea::Request>();
request->floor_id = floor_id;
request->area.id = area_id;
request->area.speed_limit = speed_limit;

// Build the polygon boundary
for (const auto& p : points) {
geometry_msgs::msg::Point32 point;
point.x = p.first;
point.y = p.second;
point.z = 0.0;
request->area.boundary.points.push_back(point);
}

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(), "Dangerous area added with ID: %d, "
"speed limit: %.2f m/s", result->area_id, speed_limit);
return true;
}
RCLCPP_ERROR(get_logger(), "Failed: %s", result->message.c_str());
}
return false;
}

private:
rclcpp::Client<map_manager::srv::AddDangerousArea>::SharedPtr client_;
};

// Example
int main(int argc, char** argv) {
rclcpp::init(argc, argv);
auto manager = std::make_shared<DangerousAreaManager>();
// Add a speed-restricted area near the elevator
manager->add_dangerous_area("1F", 1,
{{5.0, 5.0}, {8.0, 5.0}, {8.0, 8.0}, {5.0, 8.0}}, 0.2f);
rclcpp::shutdown();
return 0;
}

9.7.2 /list_dangerous_areas (Service)​

List speed-restricted areas

Service Type: map_manager/srv/ListDangerousAreas

Request:

FieldTypeDescription
floor_idstringFloor identifier; leave empty to select all floors.

Response:

FieldTypeDescription
areasmap_manager/msg/DangerousArea[]List of speed-restricted areas
floor_idsstring[]speed-restricted areafloor ID
successboolWhether the operation succeeded
messagestringresult message

Example Call:

Bash
ros2 service call ${MM}/list_dangerous_areas map_manager/srv/ListDangerousAreas \
"{floor_id: '1F'}"

Python:

Python
from map_manager.srv import ListDangerousAreas
import rclpy
from rclpy.node import Node

class DangerousAreaLister(Node):
def __init__(self):
super().__init__('dangerous_area_lister')
self.client = self.create_client(ListDangerousAreas, '/list_dangerous_areas')
self.client.wait_for_service()

def list_areas(self, floor_id: str = ''):
request = ListDangerousAreas.Request()
request.floor_id = floor_id

future = self.client.call_async(request)
rclpy.spin_until_future_complete(self, future)
result = future.result()

if result.success:
print(f"Found {len(result.areas)} dangerous areas")
for i, area in enumerate(result.areas):
floor = result.floor_ids[i] if i < len(result.floor_ids) else 'unknown'
print(f" Area {area.id} on floor {floor}:")
print(f" Speed limit: {area.speed_limit} m/s")
print(f" Vertices: {len(area.boundary.points)}")
return result

rclpy.init()
lister = DangerousAreaLister()
lister.list_areas('1F')

C++:

C++
#include <rclcpp/rclcpp.hpp>
#include <map_manager/srv/list_dangerous_areas.hpp>

class DangerousAreaLister : public rclcpp::Node {
public:
DangerousAreaLister() : Node("dangerous_area_lister") {
client_ = create_client<map_manager::srv::ListDangerousAreas>(
"/list_dangerous_areas");
client_->wait_for_service();
}

void list_areas(const std::string& floor_id = "") {
auto request = std::make_shared<map_manager::srv::ListDangerousAreas::Request>();
request->floor_id = floor_id;

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(), "Found %zu dangerous areas", result->areas.size());
for (size_t i = 0; i < result->areas.size(); ++i) {
const auto& area = result->areas[i];
const std::string& floor = (i < result->floor_ids.size()) ?
result->floor_ids[i] : "unknown";
RCLCPP_INFO(get_logger(),
" Area %d on floor %s: speed_limit=%.2f m/s, %zu vertices",
area.id, floor.c_str(), area.speed_limit,
area.boundary.points.size());
}
}
}
}

private:
rclcpp::Client<map_manager::srv::ListDangerousAreas>::SharedPtr client_;
};

9.7.3 /get_dangerous_area (Service)​

getspeed-restricted area

Service Type: map_manager/srv/GetDangerousArea

Request:

FieldTypeDescription
floor_idstringFloor identifier; leave empty to select all floors.
idint32speed-restricted area ID

Response:

FieldTypeDescription
areamap_manager/msg/DangerousAreaSpeed-restricted-area data
area_floor_idstringspeed-restricted areafloor ID
successboolWhether the operation succeeded
messagestringresult message

Example Call:

Bash
ros2 service call ${MM}/get_dangerous_area map_manager/srv/GetDangerousArea \
"{floor_id: '1F', id: 1}"

9.7.4 /update_dangerous_area (Service)​

Update a speed-restricted area

Service Type: map_manager/srv/UpdateDangerousArea

Request:

FieldTypeDescription
floor_idstringFloor identifier (required).
areamap_manager/msg/DangerousAreaUpdated speed-restricted-area data; must include id.

Response:

FieldTypeDescription
successboolWhether the operation succeeded
messagestringresult message

Example Call:

Bash
ros2 service call ${MM}/update_dangerous_area map_manager/srv/UpdateDangerousArea \
"{floor_id: '1F', area: {id: 1, boundary: {points: [{x: 0.0, y: 0.0, z: 0.0}, {x: 4.0, y: 0.0, z: 0.0}, {x: 4.0, y: 4.0, z: 0.0}, {x: 0.0, y: 4.0, z: 0.0}]}, speed_limit: 0.5}}"

9.7.5 /remove_dangerous_area (Service)​

Remove a speed-restricted area

Service Type: map_manager/srv/RemoveDangerousArea

Request:

FieldTypeDescription
floor_idstringFloor identifier; leave empty to select all floors.
area_idint32speed-restricted area ID

Response:

FieldTypeDescription
successboolWhether the operation succeeded
messagestringresult message

Example Call:

Bash
ros2 service call ${MM}/remove_dangerous_area map_manager/srv/RemoveDangerousArea \
"{floor_id: '1F', area_id: 1}"

9.7.6 /remove_all_dangerous_areas (Service)​

Remove all speed-restricted areas

Service Type: map_manager/srv/RemoveAllDangerousAreas

Request:

FieldTypeDescription
floor_idstringFloor identifier; leave empty to select all floors.

Response:

FieldTypeDescription
successboolWhether the operation succeeded
messagestringresult message
countint32removespeed-restricted areacount

Example Call:

Bash
ros2 service call ${MM}/remove_all_dangerous_areas map_manager/srv/RemoveAllDangerousAreas \
"{floor_id: '1F'}"

9.8 Room Services​

9.8.1 /add_room (Service)​

Add a room

Service Type: map_manager/srv/AddRoom

Request:

FieldTypeDescription
floor_idstringfloor ID
roommap_manager/msg/RoomRoom data; must include room.id.

Response:

FieldTypeDescription
successboolWhether the operation succeeded
messagestringresult message
room_idstringaddroom ID

Example Call:

Bash
ros2 service call ${MM}/add_room map_manager/srv/AddRoom \
"{floor_id: '1F', room: {id: 'room_101', name: 'Office 101', type: 'office', boundary: [{x: 0.0, y: 0.0, z: 0.0}, {x: 5.0, y: 0.0, z: 0.0}, {x: 5.0, y: 4.0, z: 0.0}, {x: 0.0, y: 4.0, z: 0.0}], floor_height: 0.0, ceiling_height: 2.8}}"

Python:

Python
from map_manager.srv import AddRoom
from map_manager.msg import Room
from geometry_msgs.msg import Point
import rclpy
from rclpy.node import Node

class RoomManager(Node):
def __init__(self):
super().__init__('room_manager')
self.client = self.create_client(AddRoom, '/add_room')
self.client.wait_for_service()

def add_room(self, floor_id: str, room_id: str, name: str,
room_type: str, boundary_points: list,
floor_height: float = 0.0, ceiling_height: float = 2.8):
"""
Add a room
boundary_points: Room boundary points, for example [(x1, y1), (x2, y2), ...]
"""
request = AddRoom.Request()
request.floor_id = floor_id
request.room = Room()
request.room.id = room_id
request.room.name = name
request.room.type = room_type
request.room.boundary = [
Point(x=float(p[0]), y=float(p[1]), z=0.0) for p in boundary_points
]
request.room.floor_height = floor_height
request.room.ceiling_height = ceiling_height

future = self.client.call_async(request)
rclpy.spin_until_future_complete(self, future)
result = future.result()

if result.success:
print(f"Room added: {result.room_id}")
return result.success

rclpy.init()
manager = RoomManager()
# Add a 5 m x 4 m office
manager.add_room('1F', 'room_101', 'Office 101', 'office',
[(0.0, 0.0), (5.0, 0.0), (5.0, 4.0), (0.0, 4.0)])

C++:

C++
#include <rclcpp/rclcpp.hpp>
#include <map_manager/srv/add_room.hpp>
#include <map_manager/msg/room.hpp>

class RoomManager : public rclcpp::Node {
public:
RoomManager() : Node("room_manager") {
client_ = create_client<map_manager::srv::AddRoom>("/add_room");
client_->wait_for_service();
}

bool add_room(const std::string& floor_id, const std::string& room_id,
const std::string& name, const std::string& type,
const std::vector<std::pair<double, double>>& boundary_points,
double floor_height = 0.0, double ceiling_height = 2.8) {
auto request = std::make_shared<map_manager::srv::AddRoom::Request>();
request->floor_id = floor_id;
request->room.id = room_id;
request->room.name = name;
request->room.type = type;
request->room.floor_height = floor_height;
request->room.ceiling_height = ceiling_height;

for (const auto& p : boundary_points) {
geometry_msgs::msg::Point point;
point.x = p.first;
point.y = p.second;
point.z = 0.0;
request->room.boundary.push_back(point);
}

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(), "Room added: %s", result->room_id.c_str());
return true;
}
}
return false;
}

private:
rclcpp::Client<map_manager::srv::AddRoom>::SharedPtr client_;
};

9.8.2 /list_rooms (Service)​

List rooms

Service Type: map_manager/srv/ListRooms

Request:

FieldTypeDescription
floor_idstringFloor identifier; leave empty to select all floors.

Response:

FieldTypeDescription
roomsmap_manager/msg/Room[]List of rooms
successboolWhether the operation succeeded
messagestringresult message

Example Call:

Bash
ros2 service call ${MM}/list_rooms map_manager/srv/ListRooms \
"{floor_id: '1F'}"

9.8.3 /get_room (Service)​

Get a room

Service Type: map_manager/srv/GetRoom

Request:

FieldTypeDescription
floor_idstringFloor identifier; leave empty to select all floors.
idstringroom ID

Response:

FieldTypeDescription
roommap_manager/msg/RoomRoom data
successboolWhether the operation succeeded
messagestringresult message

Example Call:

Bash
ros2 service call ${MM}/get_room map_manager/srv/GetRoom \
"{floor_id: '1F', id: 'room_101'}"

9.8.4 /update_room (Service)​

Update a room

Service Type: map_manager/srv/UpdateRoom

Request:

FieldTypeDescription
floor_idstringfloor ID
roommap_manager/msg/RoomUpdated room data; must include id.

Response:

FieldTypeDescription
successboolWhether the operation succeeded
messagestringresult message

Example Call:

Bash
ros2 service call ${MM}/update_room map_manager/srv/UpdateRoom \
"{floor_id: '1F', room: {id: 'room_101', name: 'Meeting Room 101', type: 'meeting_room', boundary: [{x: 0.0, y: 0.0, z: 0.0}, {x: 6.0, y: 0.0, z: 0.0}, {x: 6.0, y: 5.0, z: 0.0}, {x: 0.0, y: 5.0, z: 0.0}]}}"

9.8.5 /remove_room (Service)​

Remove a room

Service Type: map_manager/srv/RemoveRoom

Request:

FieldTypeDescription
floor_idstringFloor identifier; leave empty to select all floors.
room_idstringroom ID

Response:

FieldTypeDescription
successboolWhether the operation succeeded
messagestringresult message
objects_removedint32roomremovecount

Example Call:

Bash
ros2 service call ${MM}/remove_room map_manager/srv/RemoveRoom \
"{floor_id: '1F', room_id: 'room_101'}"

9.8.6 /remove_all_rooms (Service)​

Remove all rooms

Service Type: map_manager/srv/RemoveAllRooms

Request:

FieldTypeDescription
floor_idstringFloor identifier; leave empty to select all floors.

Response:

FieldTypeDescription
successboolWhether the operation succeeded
messagestringresult message
countint32removeroomcount

Example Call:

Bash
ros2 service call ${MM}/remove_all_rooms map_manager/srv/RemoveAllRooms \
"{floor_id: '1F'}"

9.9 Semantic Object Services​

9.9.1 /add_semantic_object (Service)​

Add a semantic object

Service Type: map_manager/srv/AddSemanticObject

Request:

FieldTypeDescription
floor_idstringfloor ID
objectmap_manager/msg/SemanticObjectSemantic-object data; must include object.id.

Response:

FieldTypeDescription
successboolWhether the operation succeeded
messagestringresult message
object_idstringadd ID

Example Call:

Bash
ros2 service call ${MM}/add_semantic_object map_manager/srv/AddSemanticObject \
"{floor_id: '1F', object: {id: 'desk_001', name: 'Office Desk', type: 'desk', category: 'furniture', pose: {position: {x: 2.0, y: 3.0, z: 0.0}, orientation: {w: 1.0}}, dimensions: {x: 1.4, y: 0.7, z: 0.75}, is_static: true}}"

Python:

Python
from map_manager.srv import AddSemanticObject
from map_manager.msg import SemanticObject
from geometry_msgs.msg import Pose, Vector3
import rclpy
from rclpy.node import Node

class SemanticObjectManager(Node):
def __init__(self):
super().__init__('semantic_object_manager')
self.client = self.create_client(AddSemanticObject, '/add_semantic_object')
self.client.wait_for_service()

def add_object(self, floor_id: str, obj_id: str, name: str,
obj_type: str, category: str, x: float, y: float, z: float,
length: float, width: float, height: float, is_static: bool = True):
request = AddSemanticObject.Request()
request.floor_id = floor_id
request.object = SemanticObject()
request.object.id = obj_id
request.object.name = name
request.object.type = obj_type
request.object.category = category
request.object.pose = Pose()
request.object.pose.position.x = x
request.object.pose.position.y = y
request.object.pose.position.z = z
request.object.pose.orientation.w = 1.0
request.object.dimensions = Vector3(x=length, y=width, z=height)
request.object.is_static = is_static

future = self.client.call_async(request)
rclpy.spin_until_future_complete(self, future)
result = future.result()

if result.success:
print(f"Semantic object added: {result.object_id}")
return result.success

rclpy.init()
manager = SemanticObjectManager()
# Add a 1.4 m x 0.7 m x 0.75 m desk
manager.add_object('1F', 'desk_001', 'Office Desk', 'desk', 'furniture',
2.0, 3.0, 0.0, 1.4, 0.7, 0.75, True)

C++:

C++
#include <rclcpp/rclcpp.hpp>
#include <map_manager/srv/add_semantic_object.hpp>
#include <map_manager/msg/semantic_object.hpp>

class SemanticObjectManager : public rclcpp::Node {
public:
SemanticObjectManager() : Node("semantic_object_manager") {
client_ = create_client<map_manager::srv::AddSemanticObject>(
"/add_semantic_object");
client_->wait_for_service();
}

bool add_object(const std::string& floor_id, const std::string& obj_id,
const std::string& name, const std::string& type,
const std::string& category, double x, double y, double z,
double length, double width, double height, bool is_static = true) {
auto request = std::make_shared<map_manager::srv::AddSemanticObject::Request>();
request->floor_id = floor_id;
request->object.id = obj_id;
request->object.name = name;
request->object.type = type;
request->object.category = category;
request->object.pose.position.x = x;
request->object.pose.position.y = y;
request->object.pose.position.z = z;
request->object.pose.orientation.w = 1.0;
request->object.dimensions.x = length;
request->object.dimensions.y = width;
request->object.dimensions.z = height;
request->object.is_static = is_static;

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(), "Semantic object added: %s",
result->object_id.c_str());
return true;
}
}
return false;
}

private:
rclcpp::Client<map_manager::srv::AddSemanticObject>::SharedPtr client_;
};

9.9.2 /list_semantic_objects (Service)​

List semantic objects

Service Type: map_manager/srv/ListSemanticObjects

Request:

FieldTypeDescription
floor_idstringFloor identifier; leave empty to select all floors.
object_typestringObject-type filter; leave empty to select all types.

Response:

FieldTypeDescription
objectsmap_manager/msg/SemanticObject[]List of semantic objects
successboolWhether the operation succeeded
messagestringresult message

Example Call:

Bash
ros2 service call ${MM}/list_semantic_objects map_manager/srv/ListSemanticObjects \
"{floor_id: '1F', object_type: 'desk'}"

9.9.3 /get_semantic_object (Service)​

Get a semantic object

Service Type: map_manager/srv/GetSemanticObject

Request:

FieldTypeDescription
floor_idstringFloor identifier; leave empty to select all floors.
idstringID

Response:

FieldTypeDescription
objectmap_manager/msg/SemanticObjectSemantic object data
successboolWhether the operation succeeded
messagestringresult message

Example Call:

Bash
ros2 service call ${MM}/get_semantic_object map_manager/srv/GetSemanticObject \
"{floor_id: '1F', id: 'desk_001'}"

9.9.4 /update_semantic_object (Service)​

Update a semantic object

Service Type: map_manager/srv/UpdateSemanticObject

Request:

FieldTypeDescription
floor_idstringfloor ID
objectmap_manager/msg/SemanticObjectUpdated semantic-object data; must include id.

Response:

FieldTypeDescription
successboolWhether the operation succeeded
messagestringresult message

Example Call:

Bash
ros2 service call ${MM}/update_semantic_object map_manager/srv/UpdateSemanticObject \
"{floor_id: '1F', object: {id: 'desk_001', name: 'Executive Desk', type: 'desk', category: 'furniture', pose: {position: {x: 3.0, y: 4.0, z: 0.0}, orientation: {w: 1.0}}, dimensions: {x: 1.6, y: 0.8, z: 0.75}, is_static: true}}"

9.9.5 /remove_semantic_object (Service)​

Remove a semantic object

Service Type: map_manager/srv/RemoveSemanticObject

Request:

FieldTypeDescription
floor_idstringFloor identifier; leave empty to select all floors.
object_idstringID

Response:

FieldTypeDescription
successboolWhether the operation succeeded
messagestringresult message

Example Call:

Bash
ros2 service call ${MM}/remove_semantic_object map_manager/srv/RemoveSemanticObject \
"{floor_id: '1F', object_id: 'desk_001'}"

9.9.6 /remove_all_semantic_objects (Service)​

Remove all semantic objects

Service Type: map_manager/srv/RemoveAllSemanticObjects

Request:

FieldTypeDescription
floor_idstringFloor identifier; leave empty to select all floors.
object_typestringObject-type filter; leave empty to select all types.

Response:

FieldTypeDescription
successboolWhether the operation succeeded
messagestringresult message
countint32removecount

Example Call:

Bash
ros2 service call ${MM}/remove_all_semantic_objects map_manager/srv/RemoveAllSemanticObjects \
"{floor_id: '1F', object_type: ''}"

9.10 Python Complete Example​

Python
from map_manager.srv import (
LoadCompositeMap, SaveCompositeMap, ListFloors, SwitchFloor,
AddPOI, ListPOIs, AddVirtualWall, AddForbiddenArea, AddDangerousArea
)
from map_manager.msg import POI, VirtualWall, ForbiddenArea, DangerousArea
from geometry_msgs.msg import Point, Pose2D, Polygon, Point32
import rclpy
from rclpy.node import Node

class MapManagerClient(Node):
def __init__(self, namespace: str = ''):
super().__init__('map_manager_client')

# Build the service prefix from the namespace
# namespace='' -> '' (service path: /load_composite_map)
# namespace='robot1' -> '/robot1' (service path: /robot1/load_composite_map)
prefix = f'/{namespace}' if namespace else ''

# Create service clients
self.load_client = self.create_client(LoadCompositeMap, f'{prefix}/load_composite_map')
self.save_client = self.create_client(SaveCompositeMap, f'{prefix}/save_composite_map')
self.list_floors_client = self.create_client(ListFloors, f'{prefix}/list_floors')
self.switch_floor_client = self.create_client(SwitchFloor, f'{prefix}/switch_floor')
self.add_poi_client = self.create_client(AddPOI, f'{prefix}/add_poi')
self.list_pois_client = self.create_client(ListPOIs, f'{prefix}/list_pois')
self.add_wall_client = self.create_client(AddVirtualWall, f'{prefix}/add_virtual_wall')
self.add_forbidden_client = self.create_client(AddForbiddenArea, f'{prefix}/add_forbidden_area')
self.add_dangerous_client = self.create_client(AddDangerousArea, f'{prefix}/add_dangerous_area')

def load_map(self, path: str):
"""Load a composite map."""
request = LoadCompositeMap.Request()
request.map_path = path
future = self.load_client.call_async(request)
rclpy.spin_until_future_complete(self, future)
return future.result()

def save_map(self, path: str, compress: bool = False):
"""Save a composite map."""
request = SaveCompositeMap.Request()
request.map_path = path
request.compress = compress
future = self.save_client.call_async(request)
rclpy.spin_until_future_complete(self, future)
return future.result()

def list_floors(self):
"""List all floors."""
request = ListFloors.Request()
future = self.list_floors_client.call_async(request)
rclpy.spin_until_future_complete(self, future)
return future.result()

def switch_floor(self, floor_id: str, x: float = 0.0, y: float = 0.0, theta: float = 0.0):
"""Switch floors."""
request = SwitchFloor.Request()
request.floor_id = floor_id
request.initial_pose = Pose2D(x=x, y=y, theta=theta)
request.use_transition = False
future = self.switch_floor_client.call_async(request)
rclpy.spin_until_future_complete(self, future)
return future.result()

def add_poi(self, poi_id: str, name: str, poi_type: str, floor_id: str, x: float, y: float, theta: float):
"""Add a POI."""
request = AddPOI.Request()
request.poi = POI(
id=poi_id, name=name, type=poi_type, floor_id=floor_id,
pose=Pose2D(x=x, y=y, theta=theta)
)
future = self.add_poi_client.call_async(request)
rclpy.spin_until_future_complete(self, future)
return future.result()

def add_virtual_wall(self, floor_id: str, wall_id: int, start: tuple, end: tuple):
"""Add a virtual wall."""
request = AddVirtualWall.Request()
request.floor_id = floor_id
request.wall = VirtualWall(
id=wall_id,
start=Point(x=start[0], y=start[1], z=0.0),
end=Point(x=end[0], y=end[1], z=0.0)
)
future = self.add_wall_client.call_async(request)
rclpy.spin_until_future_complete(self, future)
return future.result()

def add_forbidden_area(self, floor_id: str, area_id: int, points: list):
"""Add a forbidden area."""
request = AddForbiddenArea.Request()
request.floor_id = floor_id
polygon = Polygon()
polygon.points = [Point32(x=p[0], y=p[1], z=0.0) for p in points]
request.area = ForbiddenArea(id=area_id, boundary=polygon)
future = self.add_forbidden_client.call_async(request)
rclpy.spin_until_future_complete(self, future)
return future.result()

def add_dangerous_area(self, floor_id: str, area_id: int, points: list, speed_limit: float):
"""Add a speed-restricted area."""
request = AddDangerousArea.Request()
request.floor_id = floor_id
polygon = Polygon()
polygon.points = [Point32(x=p[0], y=p[1], z=0.0) for p in points]
request.area = DangerousArea(id=area_id, boundary=polygon, speed_limit=speed_limit)
future = self.add_dangerous_client.call_async(request)
rclpy.spin_until_future_complete(self, future)
return future.result()


# Example
def main():
rclpy.init()
# Single-robot mode (default)
client = MapManagerClient()
# Multi-robot mode with a namespace: client = MapManagerClient(namespace='robot1')

# Load the map
result = client.load_map('/opt/fftai/Navigation/Map/office')
if result.success:
print(f"Map loaded successfully: {result.map_info.name}")
print(f"Floor count: {len(result.map_info.floors)}")

# List floors
floors = client.list_floors()
for floor in floors.floors:
print(f" floor: {floor.floor_id} ({floor.name})")

# Switch floors
client.switch_floor('2F', x=1.0, y=2.0, theta=0.0)

# Add a POI
client.add_poi('charging_1', 'Charging Station', 'charging_station', '1F', 5.0, 3.0, 1.57)

# Add a virtual wall
client.add_virtual_wall('1F', 1, (0.0, 0.0), (2.0, 0.0))

# Add a forbidden area
client.add_forbidden_area('1F', 1, [(0.0, 0.0), (2.0, 0.0), (2.0, 2.0), (0.0, 2.0)])

# Add a speed-restricted area
client.add_dangerous_area('1F', 1, [(5.0, 5.0), (8.0, 5.0), (8.0, 8.0), (5.0, 8.0)], 0.3)

# Save the map
client.save_map('/opt/fftai/Navigation/Map/office_updated', compress=True)

rclpy.shutdown()

if __name__ == '__main__':
main()

10. API Summary​

Topic List​

TopicMessage TypeOptionDescription
/slam/mode_statusstd_msgs/Stringpublishcurrentstatus
/clear_mapstd_msgs/StringsubscribeClear maptrigger
/mapnav_msgs/OccupancyGridpublish2Dmap
/optimize_mapnav_msgs/OccupancyGridpublish2Dmap
/cloud_registered_gravitysensor_msgs/PointCloud2publish3Dmap
/initialposegeometry_msgs/PoseWithCovarianceStampedsubscribeinitial pose
/robot_posegeometry_msgs/PoseStampedpublishrobot3D(6DOF)
/odomnav_msgs/Odometrypublishdata
/odom_status_codestd_msgs/Int8publishstatus
/odom_status_scorestd_msgs/Float32publishposition
/plannav_msgs/Pathpublishglobal plan
/cmd_velgeometry_msgs/Twistpublish
/action_statusfourier_msgs/ActionStatuspublishAction execution status
/scansensor_msgs/LaserScanpublish2D
/segmented_groundless_pointssensor_msgs/PointCloud2publish
/imusensor_msgs/ImusubscribeIMUdata
/camera_01/color/image_rawsensor_msgs/Imagepublish
/camera_01/color/camera_infosensor_msgs/CameraInfopublishinformation
/camera_01/depth/image_rawsensor_msgs/Imagepublish
/camera_01/depth/camera_infosensor_msgs/CameraInfopublishinformation
/camera_01/depth/pointssensor_msgs/PointCloud2publish
/camera_01/fused_datafourier_msgs/UnifiedCameraDatapublishdata
/camera_01/filtered_pointcloudsensor_msgs/PointCloud2publish
/Humanoid_nav/healthfourier_msgs/HealthInfopublishsystem health status
/Humanoid_nav/eventsfourier_msgs/EventsInfopublishsystem event
/reloc_statusstd_msgs/BoolpublishRelocalizationstatus

Service List​

Mapping and Localization Services​

Service NameService TypeDescription
/slam/set_modefourier_msgs/SetModeswitch/Localization mode
/slam/load_mapfourier_msgs/LoadMapLoad map
/slam/save_mapfourier_msgs/SaveMapSave map
/slam/global_relocalizationstd_srvs/EmptyTrigger global relocalization
/slam/trigger_local_relocalizationfourier_msgs/VPRLocalRelocalizationLocal relocalization
Service NameService TypeDescription
/cancel_current_actionfourier_msgs/CancelCurrentActionCancel the current action
/get_current_actionfourier_msgs/GetCurrentActionGet the current action

Map Manager - Composite Map Services​

Service NameService TypeDescription
/load_composite_mapmap_manager/LoadCompositeMapLoad a composite map
/save_composite_mapmap_manager/SaveCompositeMapSave a composite map
/clear_composite_mapmap_manager/ClearCompositeMapClear the composite map

Map Manager - Floor Management Services​

Service NameService TypeDescription
/list_floorsmap_manager/ListFloorsList all floors
/switch_floormap_manager/SwitchFloorSwitch floors
/add_floormap_manager/AddFloorAdd a floor
/remove_floormap_manager/RemoveFloorRemove a floor
/save_floormap_manager/SaveFloorSave floor data
/load_floormap_manager/LoadFloorLoad floor data
/clear_floormap_manager/ClearFloorClear floor data

Map Manager - POI Management Services​

Service NameService TypeDescription
/add_poimap_manager/AddPOIAdd a POI
/get_poimap_manager/GetPOIGet a POI
/list_poismap_manager/ListPOIsList POIs
/update_poimap_manager/UpdatePOIUpdate a POI
/remove_poimap_manager/RemovePOIRemove a POI
/remove_all_poismap_manager/RemoveAllPOIsRemove all POIs

Map Manager - Virtual Wall Services​

Service NameService TypeDescription
/add_virtual_wallmap_manager/AddVirtualWallAdd a virtual wall
/get_virtual_wallmap_manager/GetVirtualWallGet a virtual wall
/list_virtual_wallsmap_manager/ListVirtualWallsList virtual walls
/update_virtual_wallmap_manager/UpdateVirtualWallUpdate a virtual wall
/remove_virtual_wallmap_manager/RemoveVirtualWallRemove a virtual wall
/remove_virtual_wallsmap_manager/RemoveVirtualWallsRemove virtual walls in bulk

Map Manager - Forbidden Area Services​

Service NameService TypeDescription
/add_forbidden_areamap_manager/AddForbiddenAreaAdd a forbidden area
/get_forbidden_areamap_manager/GetForbiddenAreaGet a forbidden area
/list_forbidden_areasmap_manager/ListForbiddenAreasList forbidden areas
/update_forbidden_areamap_manager/UpdateForbiddenAreaUpdate a forbidden area
/remove_forbidden_areamap_manager/RemoveForbiddenAreaRemove one forbidden area
/remove_all_forbidden_areasmap_manager/RemoveAllForbiddenAreasRemove all forbidden areas

Map Manager - Speed-Restricted Area Services​

Service NameService TypeDescription
/add_dangerous_areamap_manager/AddDangerousAreaAdd a speed-restricted area
/get_dangerous_areamap_manager/GetDangerousAreaGet a speed-restricted area
/list_dangerous_areasmap_manager/ListDangerousAreasList speed-restricted areas
/update_dangerous_areamap_manager/UpdateDangerousAreaUpdate a speed-restricted area
/remove_dangerous_areamap_manager/RemoveDangerousAreaRemove a speed-restricted area
/remove_all_dangerous_areasmap_manager/RemoveAllDangerousAreasRemove all speed-restricted areas

Map Manager - Room Services​

Service NameService TypeDescription
/add_roommap_manager/AddRoomAdd a room
/get_roommap_manager/GetRoomGet a room
/list_roomsmap_manager/ListRoomsList rooms
/update_roommap_manager/UpdateRoomUpdate a room
/remove_roommap_manager/RemoveRoomRemove a room
/remove_all_roomsmap_manager/RemoveAllRoomsRemove all rooms

Map Manager - Semantic Object Services​

Service NameService TypeDescription
/add_semantic_objectmap_manager/AddSemanticObjectAdd a semantic object
/get_semantic_objectmap_manager/GetSemanticObjectGet a semantic object
/list_semantic_objectsmap_manager/ListSemanticObjectsList semantic objects
/update_semantic_objectmap_manager/UpdateSemanticObjectUpdate a semantic object
/remove_semantic_objectmap_manager/RemoveSemanticObjectRemove a semantic object
/remove_all_semantic_objectsmap_manager/RemoveAllSemanticObjectsRemove all semantic objects

Action List​

ActionAction TypeDescription
/navigate_to_posenav2_msgs/NavigateToPoseNavigate to one target pose
/navigate_through_posesnav2_msgs/NavigateThroughPosesNavigate through multiple poses
/follow_pathnav2_msgs/FollowPathFollow a supplied path
/compute_path_to_posenav2_msgs/ComputePathToPoseCompute a path to a target pose

Message Type List​

Map Manager Message​

Message TypeDescription
map_manager/msg/FloorFloor information
map_manager/msg/CompositeMapInfoComposite map information
map_manager/msg/FloorTransitionFloor transition point
map_manager/msg/POIpoi
map_manager/msg/VirtualWallvirtual wall
map_manager/msg/ForbiddenAreaforbidden area
map_manager/msg/DangerousAreaspeed-restricted area
map_manager/msg/Roomroom
map_manager/msg/SemanticObjectsemantic object
map_manager/msg/MapLayermap