Skip to content

Commit da404d2

Browse files
committed
add : get clients, servers info
Signed-off-by: Minju, Lee <[email protected]>
1 parent eeaa522 commit da404d2

File tree

8 files changed

+300
-5
lines changed

8 files changed

+300
-5
lines changed

rclcpp/include/rclcpp/node.hpp

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1405,6 +1405,58 @@ class Node : public std::enable_shared_from_this<Node>
14051405
std::vector<rclcpp::TopicEndpointInfo>
14061406
get_subscriptions_info_by_topic(const std::string & topic_name, bool no_mangle = false) const;
14071407

1408+
/// Return the service endpoint information about clients on a given service.
1409+
/**
1410+
* The returned parameter is a list of service endpoint information, where each item will contain
1411+
* the node name, node namespace, service type, endpoint type, service endpoint's GID, and its QoS
1412+
* profile.
1413+
*
1414+
* When the `no_mangle` parameter is `true`, the provided `service_name` should be a valid service
1415+
* name for the middleware (useful when combining ROS with native middleware (e.g. DDS) apps).
1416+
* When the `no_mangle` parameter is `false`, the provided `service_name` should follow
1417+
* ROS service name conventions.
1418+
*
1419+
* 'service_name` may be a relative, private, or fully qualified service name.
1420+
* A relative or private service will be expanded using this node's namespace and name.
1421+
* The queried `service_name` is not remapped.
1422+
*
1423+
* \param[in] service_name the actual service name used; it will not be automatically remapped.
1424+
* \param[in] no_mangle if `true`, `service_name` needs to be a valid middleware service name,
1425+
* otherwise it should be a valid ROS service name. Defaults to `false`.
1426+
* \return a list of TopicEndpointInfo representing all the clients on this service.
1427+
* \throws InvalidTopicNameError if the given service_name is invalid.
1428+
* \throws std::runtime_error if internal error happens.
1429+
*/
1430+
RCLCPP_PUBLIC
1431+
std::vector<rclcpp::TopicEndpointInfo>
1432+
get_clients_info_by_service(const std::string & service_name, bool no_mangle = false) const;
1433+
1434+
/// Return the service endpoint information about servers on a given service.
1435+
/**
1436+
* The returned parameter is a list of service endpoint information, where each item will contain
1437+
* the node name, node namespace, service type, endpoint type, service endpoint's GID, and its QoS
1438+
* profile.
1439+
*
1440+
* When the `no_mangle` parameter is `true`, the provided `service_name` should be a valid service
1441+
* name for the middleware (useful when combining ROS with native middleware (e.g. DDS) apps).
1442+
* When the `no_mangle` parameter is `false`, the provided `service_name` should follow
1443+
* ROS service name conventions.
1444+
*
1445+
* 'service_name` may be a relative, private, or fully qualified service name.
1446+
* A relative or private service will be expanded using this node's namespace and name.
1447+
* The queried `service_name` is not remapped.
1448+
*
1449+
* \param[in] service_name the actual service name used; it will not be automatically remapped.
1450+
* \param[in] no_mangle if `true`, `service_name` needs to be a valid middleware service name,
1451+
* otherwise it should be a valid ROS service name. Defaults to `false`.
1452+
* \return a list of TopicEndpointInfo representing all the servers on this service.
1453+
* \throws InvalidTopicNameError if the given service_name is invalid.
1454+
* \throws std::runtime_error if internal error happens.
1455+
*/
1456+
RCLCPP_PUBLIC
1457+
std::vector<rclcpp::TopicEndpointInfo>
1458+
get_servers_info_by_service(const std::string & service_name, bool no_mangle = false) const;
1459+
14081460
/// Return a graph event, which will be set anytime a graph change occurs.
14091461
/* The graph Event object is a loan which must be returned.
14101462
* The Event object is scoped and therefore to return the loan just let it go

rclcpp/include/rclcpp/node_interfaces/node_graph.hpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,18 @@ class NodeGraph : public NodeGraphInterface
159159
const std::string & topic_name,
160160
bool no_mangle = false) const override;
161161

162+
RCLCPP_PUBLIC
163+
std::vector<rclcpp::TopicEndpointInfo>
164+
get_clients_info_by_service(
165+
const std::string & service_name,
166+
bool no_mangle = false) const override;
167+
168+
RCLCPP_PUBLIC
169+
std::vector<rclcpp::TopicEndpointInfo>
170+
get_servers_info_by_service(
171+
const std::string & service_name,
172+
bool no_mangle = false) const override;
173+
162174
private:
163175
RCLCPP_DISABLE_COPY(NodeGraph)
164176

rclcpp/include/rclcpp/node_interfaces/node_graph_interface.hpp

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -408,6 +408,30 @@ class NodeGraphInterface
408408
virtual
409409
std::vector<rclcpp::TopicEndpointInfo>
410410
get_subscriptions_info_by_topic(const std::string & topic_name, bool no_mangle = false) const = 0;
411+
412+
/// Return the service endpoint information about clients on a given service.
413+
/**
414+
* \param[in] service_name the actual service name used; it will not be automatically remapped.
415+
* \param[in] no_mangle if `true`, `service_name` needs to be a valid middleware service name,
416+
* otherwise it should be a valid ROS service name.
417+
* \sa rclcpp::Node::get_clients_info_by_service
418+
*/
419+
RCLCPP_PUBLIC
420+
virtual
421+
std::vector<rclcpp::TopicEndpointInfo>
422+
get_clients_info_by_service(const std::string & service_name, bool no_mangle = false) const = 0;
423+
424+
/// Return the service endpoint information about servers on a given service.
425+
/**
426+
* \param[in] service_name the actual service name used; it will not be automatically remapped.
427+
* \param[in] no_mangle if `true`, `service_name` needs to be a valid middleware service name,
428+
* otherwise it should be a valid ROS service name.
429+
* \sa rclcpp::Node::get_servers_info_by_service
430+
*/
431+
RCLCPP_PUBLIC
432+
virtual
433+
std::vector<rclcpp::TopicEndpointInfo>
434+
get_servers_info_by_service(const std::string & service_name, bool no_mangle = false) const = 0;
411435
};
412436

413437
} // namespace node_interfaces

rclcpp/src/rclcpp/node.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -547,6 +547,19 @@ Node::get_subscriptions_info_by_topic(const std::string & topic_name, bool no_ma
547547
return node_graph_->get_subscriptions_info_by_topic(topic_name, no_mangle);
548548
}
549549

550+
std::vector<rclcpp::TopicEndpointInfo>
551+
Node::get_clients_info_by_service(const std::string & service_name, bool no_mangle) const
552+
{
553+
return node_graph_->get_clients_info_by_service(service_name, no_mangle);
554+
}
555+
556+
std::vector<rclcpp::TopicEndpointInfo>
557+
Node::get_servers_info_by_service(const std::string & service_name, bool no_mangle) const
558+
{
559+
return node_graph_->get_servers_info_by_service(service_name, no_mangle);
560+
}
561+
562+
550563
void
551564
Node::for_each_callback_group(
552565
const node_interfaces::NodeBaseInterface::CallbackGroupFunction & func)

rclcpp/src/rclcpp/node_interfaces/node_graph.cpp

Lines changed: 42 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -664,7 +664,8 @@ get_info_by_topic(
664664
rclcpp::node_interfaces::NodeBaseInterface * node_base,
665665
const std::string & topic_name,
666666
bool no_mangle,
667-
FunctionT rcl_get_info_by_topic)
667+
bool is_service,
668+
FunctionT rcl_get_info_by_topic_or_service)
668669
{
669670
std::string fqdn;
670671
auto rcl_node_handle = node_base->get_rcl_node_handle();
@@ -676,7 +677,7 @@ get_info_by_topic(
676677
topic_name,
677678
rcl_node_get_name(rcl_node_handle),
678679
rcl_node_get_namespace(rcl_node_handle),
679-
false); // false = not a service
680+
is_service);
680681

681682
// Get the node options
682683
const rcl_node_options_t * node_options = rcl_node_get_options(rcl_node_handle);
@@ -687,9 +688,9 @@ get_info_by_topic(
687688
if (node_options->use_global_arguments) {
688689
global_args = &(rcl_node_handle->context->global_arguments);
689690
}
690-
691+
auto rcl_remap_name = is_service ? rcl_remap_service_name : rcl_remap_topic_name;
691692
char * remapped_topic_name = nullptr;
692-
rcl_ret_t ret = rcl_remap_topic_name(
693+
rcl_ret_t ret = rcl_remap_name(
693694
&(node_options->arguments),
694695
global_args,
695696
fqdn.c_str(),
@@ -708,7 +709,13 @@ get_info_by_topic(
708709
rcutils_allocator_t allocator = rcutils_get_default_allocator();
709710
rcl_topic_endpoint_info_array_t info_array = rcl_get_zero_initialized_topic_endpoint_info_array();
710711
rcl_ret_t ret =
711-
rcl_get_info_by_topic(rcl_node_handle, &allocator, fqdn.c_str(), no_mangle, &info_array);
712+
rcl_get_info_by_topic_or_service(
713+
rcl_node_handle,
714+
&allocator,
715+
fqdn.c_str(),
716+
no_mangle,
717+
&info_array
718+
);
712719
if (RCL_RET_OK != ret) {
713720
auto error_msg =
714721
std::string("Failed to get information by topic for ") + EndpointType + std::string(":");
@@ -745,6 +752,7 @@ NodeGraph::get_publishers_info_by_topic(
745752
node_base_,
746753
topic_name,
747754
no_mangle,
755+
false,
748756
rcl_get_publishers_info_by_topic);
749757
}
750758

@@ -758,9 +766,38 @@ NodeGraph::get_subscriptions_info_by_topic(
758766
node_base_,
759767
topic_name,
760768
no_mangle,
769+
false,
761770
rcl_get_subscriptions_info_by_topic);
762771
}
763772

773+
static constexpr char kClientsEndpointTypeName[] = "clients";
774+
std::vector<rclcpp::TopicEndpointInfo>
775+
NodeGraph::get_clients_info_by_service(
776+
const std::string & service_name,
777+
bool no_mangle) const
778+
{
779+
return get_info_by_topic<kClientsEndpointTypeName>(
780+
node_base_,
781+
service_name,
782+
no_mangle,
783+
true,
784+
rcl_get_clients_info_by_service);
785+
}
786+
787+
static constexpr char kServersEndpointTypeName[] = "servers";
788+
std::vector<rclcpp::TopicEndpointInfo>
789+
NodeGraph::get_servers_info_by_service(
790+
const std::string & service_name,
791+
bool no_mangle) const
792+
{
793+
return get_info_by_topic<kServersEndpointTypeName>(
794+
node_base_,
795+
service_name,
796+
no_mangle,
797+
true,
798+
rcl_get_servers_info_by_service);
799+
}
800+
764801
std::string &
765802
rclcpp::TopicEndpointInfo::node_name()
766803
{

rclcpp/test/rclcpp/test_node.cpp

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3263,6 +3263,135 @@ TEST_F(TestNode, get_publishers_subscriptions_info_by_topic) {
32633263
}, rclcpp::exceptions::InvalidTopicNameError);
32643264
}
32653265

3266+
// test that calling get_clients_info_by_service and get_servers_info_by_service
3267+
TEST_F(TestNode, get_clients_servers_info_by_service) {
3268+
auto node = std::make_shared<rclcpp::Node>("my_node", "/ns");
3269+
std::string service_name = "test_service_info";
3270+
std::string fq_service_name = rclcpp::expand_topic_or_service_name(
3271+
service_name, node->get_name(), node->get_namespace(), true);
3272+
3273+
// Lists should be empty
3274+
EXPECT_TRUE(node->get_clients_info_by_service(fq_service_name).empty());
3275+
EXPECT_TRUE(node->get_servers_info_by_service(fq_service_name).empty());
3276+
3277+
// Add a publisher
3278+
rclcpp::QoSInitialization qos_initialization =
3279+
{
3280+
RMW_QOS_POLICY_HISTORY_KEEP_ALL,
3281+
10
3282+
};
3283+
rmw_qos_profile_t rmw_qos_profile_default =
3284+
{
3285+
RMW_QOS_POLICY_HISTORY_KEEP_ALL,
3286+
10,
3287+
RMW_QOS_POLICY_RELIABILITY_BEST_EFFORT,
3288+
RMW_QOS_POLICY_DURABILITY_TRANSIENT_LOCAL,
3289+
{1, 12345},
3290+
{20, 9887665},
3291+
RMW_QOS_POLICY_LIVELINESS_MANUAL_BY_TOPIC,
3292+
{5, 23456},
3293+
false
3294+
};
3295+
rclcpp::QoS qos = rclcpp::QoS(qos_initialization, rmw_qos_profile_default);
3296+
auto client = node->create_client<test_msgs::srv::Empty>(service_name, qos);
3297+
// Wait for the underlying RMW implementation to catch up with graph changes
3298+
auto client_is_generated =
3299+
[&]() {return node->get_clients_info_by_service(fq_service_name).size() > 0u;};
3300+
ASSERT_TRUE(wait_for_event(node, client_is_generated));
3301+
// List should have at least one item
3302+
auto client_list = node->get_clients_info_by_service(fq_service_name);
3303+
ASSERT_GE(client_list.size(), (size_t)1);
3304+
// Server list should be empty
3305+
EXPECT_TRUE(node->get_servers_info_by_service(fq_service_name).empty());
3306+
// Verify client list has the right data.
3307+
for(auto client_info : client_list) {
3308+
EXPECT_EQ(node->get_name(), client_info.node_name());
3309+
EXPECT_EQ(node->get_namespace(), client_info.node_namespace());
3310+
ASSERT_NE(client_info.topic_type().find("test_msgs/srv/Empty"), std::string::npos);
3311+
if(client_info.topic_type() == "test_msgs/srv/Empty_Request") {
3312+
EXPECT_EQ(rclcpp::EndpointType::Publisher, client_info.endpoint_type());
3313+
auto actual_qos_profile = client_info.qos_profile().get_rmw_qos_profile();
3314+
{
3315+
SCOPED_TRACE("Publisher QOS 1");
3316+
expect_qos_profile_eq(qos.get_rmw_qos_profile(), actual_qos_profile, true);
3317+
}
3318+
} else if(client_info.topic_type() == "test_msgs/srv/Empty_Response") {
3319+
EXPECT_EQ(rclcpp::EndpointType::Subscription, client_info.endpoint_type());
3320+
auto actual_qos_profile = client_info.qos_profile().get_rmw_qos_profile();
3321+
{
3322+
SCOPED_TRACE("Publisher QOS 1");
3323+
expect_qos_profile_eq(qos.get_rmw_qos_profile(), actual_qos_profile, false);
3324+
}
3325+
}
3326+
}
3327+
3328+
// Add a subscription
3329+
rclcpp::QoSInitialization qos_initialization2 =
3330+
{
3331+
RMW_QOS_POLICY_HISTORY_KEEP_LAST,
3332+
0
3333+
};
3334+
rmw_qos_profile_t rmw_qos_profile_default2 =
3335+
{
3336+
RMW_QOS_POLICY_HISTORY_KEEP_LAST,
3337+
0,
3338+
RMW_QOS_POLICY_RELIABILITY_RELIABLE,
3339+
RMW_QOS_POLICY_DURABILITY_VOLATILE,
3340+
{15, 1678},
3341+
{29, 2345},
3342+
RMW_QOS_POLICY_LIVELINESS_AUTOMATIC,
3343+
{5, 23456},
3344+
false
3345+
};
3346+
rclcpp::QoS qos2 = rclcpp::QoS(qos_initialization2, rmw_qos_profile_default2);
3347+
auto callback = [](test_msgs::srv::Empty_Request::ConstSharedPtr req,
3348+
test_msgs::srv::Empty_Response::ConstSharedPtr resp) {
3349+
(void)req;
3350+
(void)resp;
3351+
};
3352+
auto server = node->create_service<test_msgs::srv::Empty>(service_name, callback, qos2);
3353+
// Wait for the underlying RMW implementation to catch up with graph changes
3354+
auto server_is_generated =
3355+
[&]() {return node->get_servers_info_by_service(fq_service_name).size() > 0u;};
3356+
ASSERT_TRUE(wait_for_event(node, server_is_generated));
3357+
// Both lists should have at least one item
3358+
client_list = node->get_clients_info_by_service(fq_service_name);
3359+
auto server_list = node->get_servers_info_by_service(fq_service_name);
3360+
ASSERT_GE(client_list.size(), (size_t)1);
3361+
ASSERT_GE(server_list.size(), (size_t)1);
3362+
// Verify server list has the right data.
3363+
for(auto server_info : server_list) {
3364+
EXPECT_EQ(node->get_name(), server_info.node_name());
3365+
EXPECT_EQ(node->get_namespace(), server_info.node_namespace());
3366+
ASSERT_NE(server_info.topic_type().find("test_msgs/srv/Empty"), std::string::npos);
3367+
if(server_info.topic_type() == "test_msgs/srv/Empty_Request") {
3368+
EXPECT_EQ(rclcpp::EndpointType::Subscription, server_info.endpoint_type());
3369+
auto actual_qos_profile = server_info.qos_profile().get_rmw_qos_profile();
3370+
{
3371+
SCOPED_TRACE("Publisher QOS 1");
3372+
expect_qos_profile_eq(qos2.get_rmw_qos_profile(), actual_qos_profile, false);
3373+
}
3374+
} else if(server_info.topic_type() == "test_msgs/srv/Empty_Response") {
3375+
EXPECT_EQ(rclcpp::EndpointType::Publisher, server_info.endpoint_type());
3376+
auto actual_qos_profile = server_info.qos_profile().get_rmw_qos_profile();
3377+
{
3378+
SCOPED_TRACE("Publisher QOS 1");
3379+
expect_qos_profile_eq(qos2.get_rmw_qos_profile(), actual_qos_profile, true);
3380+
}
3381+
}
3382+
}
3383+
3384+
// Error cases
3385+
EXPECT_THROW(
3386+
{
3387+
client_list = node->get_clients_info_by_service("13");
3388+
}, rclcpp::exceptions::InvalidServiceNameError);
3389+
EXPECT_THROW(
3390+
{
3391+
server_list = node->get_servers_info_by_service("13");
3392+
}, rclcpp::exceptions::InvalidServiceNameError);
3393+
}
3394+
32663395
TEST_F(TestNode, callback_groups) {
32673396
auto node = std::make_shared<rclcpp::Node>("node", "ns");
32683397
size_t num_callback_groups_in_basic_node = 0;

rclcpp_lifecycle/include/rclcpp_lifecycle/lifecycle_node.hpp

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -722,6 +722,22 @@ class LifecycleNode : public node_interfaces::LifecycleNodeInterface,
722722
std::vector<rclcpp::TopicEndpointInfo>
723723
get_subscriptions_info_by_topic(const std::string & topic_name, bool no_mangle = false) const;
724724

725+
/// Return the service endpoint information about clients on a given service.
726+
/**
727+
* \sa rclcpp::Node::get_clients_info_by_service
728+
*/
729+
RCLCPP_LIFECYCLE_PUBLIC
730+
std::vector<rclcpp::TopicEndpointInfo>
731+
get_clients_info_by_service(const std::string & service_name, bool no_mangle = false) const;
732+
733+
/// Return the service endpoint information about server on a given service.
734+
/**
735+
* \sa rclcpp::Node::get_servers_info_by_service
736+
*/
737+
RCLCPP_LIFECYCLE_PUBLIC
738+
std::vector<rclcpp::TopicEndpointInfo>
739+
get_servers_info_by_service(const std::string & service_name, bool no_mangle = false) const;
740+
725741
/// Return a graph event, which will be set anytime a graph change occurs.
726742
/* The graph Event object is a loan which must be returned.
727743
* The Event object is scoped and therefore to return the load just let it go

rclcpp_lifecycle/src/lifecycle_node.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -410,6 +410,18 @@ LifecycleNode::get_subscriptions_info_by_topic(const std::string & topic_name, b
410410
return node_graph_->get_subscriptions_info_by_topic(topic_name, no_mangle);
411411
}
412412

413+
std::vector<rclcpp::TopicEndpointInfo>
414+
LifecycleNode::get_clients_info_by_service(const std::string & service_name, bool no_mangle) const
415+
{
416+
return node_graph_->get_clients_info_by_service(service_name, no_mangle);
417+
}
418+
419+
std::vector<rclcpp::TopicEndpointInfo>
420+
LifecycleNode::get_servers_info_by_service(const std::string & service_name, bool no_mangle) const
421+
{
422+
return node_graph_->get_servers_info_by_service(service_name, no_mangle);
423+
}
424+
413425
void
414426
LifecycleNode::for_each_callback_group(
415427
const rclcpp::node_interfaces::NodeBaseInterface::CallbackGroupFunction & func)

0 commit comments

Comments
 (0)