-
Notifications
You must be signed in to change notification settings - Fork 81
Add getServersInfoByService and getClientsInfoByService for node #1335
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
fe3e866
b1f1050
74456bd
2b5cebb
6cc3728
da2995b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1349,6 +1349,74 @@ class Node extends rclnodejs.ShadowNode { | |
| ); | ||
| } | ||
|
|
||
| /** | ||
| * Return a list of clients on a given service. | ||
| * | ||
| * The returned parameter is a list of ServiceEndpointInfo objects, where each will contain | ||
| * the node name, node namespace, service type, service endpoint's GID, and its QoS profile. | ||
| * | ||
| * When the `no_mangle` parameter is `true`, the provided `service` should be a valid | ||
| * service name for the middleware (useful when combining ROS with native middleware (e.g. DDS) | ||
| * apps). When the `no_mangle` parameter is `false`, the provided `service` should | ||
| * follow ROS service name conventions. | ||
| * | ||
| * `service` may be a relative, private, or fully qualified service name. | ||
| * A relative or private service will be expanded using this node's namespace and name. | ||
| * The queried `service` is not remapped. | ||
| * | ||
| * @param {string} service - The service on which to find the clients. | ||
| * @param {boolean} [noDemangle=false] - If `true`, `service` needs to be a valid middleware service | ||
| * name, otherwise it should be a valid ROS service name. Defaults to `false`. | ||
| * @returns {Array} - list of clients | ||
| */ | ||
| getClientsInfoByService(service, noDemangle = false) { | ||
| if (DistroUtils.getDistroId() < DistroUtils.DistroId.ROLLING) { | ||
| console.warn( | ||
| 'getClientsInfoByService is not supported by this version of ROS 2' | ||
| ); | ||
| return null; | ||
| } | ||
| return rclnodejs.getClientsInfoByService( | ||
| this.handle, | ||
| this._getValidatedServiceName(service, noDemangle), | ||
| noDemangle | ||
| ); | ||
| } | ||
|
|
||
| /** | ||
| * Return a list of servers on a given service. | ||
| * | ||
| * The returned parameter is a list of ServiceEndpointInfo objects, where each will contain | ||
| * the node name, node namespace, service type, service endpoint's GID, and its QoS profile. | ||
| * | ||
| * When the `no_mangle` parameter is `true`, the provided `service` should be a valid | ||
| * service name for the middleware (useful when combining ROS with native middleware (e.g. DDS) | ||
| * apps). When the `no_mangle` parameter is `false`, the provided `service` should | ||
| * follow ROS service name conventions. | ||
| * | ||
| * `service` may be a relative, private, or fully qualified service name. | ||
| * A relative or private service will be expanded using this node's namespace and name. | ||
| * The queried `service` is not remapped. | ||
| * | ||
| * @param {string} service - The service on which to find the servers. | ||
| * @param {boolean} [noDemangle=false] - If `true`, `service` needs to be a valid middleware service | ||
| * name, otherwise it should be a valid ROS service name. Defaults to `false`. | ||
| * @returns {Array} - list of servers | ||
| */ | ||
| getServersInfoByService(service, noDemangle = false) { | ||
| if (DistroUtils.getDistroId() < DistroUtils.DistroId.ROLLING) { | ||
|
||
| console.warn( | ||
| 'getServersInfoByService is not supported by this version of ROS 2' | ||
| ); | ||
| return null; | ||
| } | ||
| return rclnodejs.getServersInfoByService( | ||
| this.handle, | ||
| this._getValidatedServiceName(service, noDemangle), | ||
| noDemangle | ||
| ); | ||
| } | ||
|
|
||
| /** | ||
| * Get the list of nodes discovered by the provided node. | ||
| * @return {Array<string>} - An array of the names. | ||
|
|
@@ -2142,6 +2210,22 @@ class Node extends rclnodejs.ShadowNode { | |
| validateFullTopicName(fqTopicName); | ||
| return rclnodejs.remapTopicName(this.handle, fqTopicName); | ||
| } | ||
|
|
||
| _getValidatedServiceName(serviceName, noDemangle) { | ||
| if (typeof serviceName !== 'string') { | ||
| throw new TypeValidationError('serviceName', serviceName, 'string', { | ||
| nodeName: this.name(), | ||
| }); | ||
| } | ||
|
|
||
| if (noDemangle) { | ||
| return serviceName; | ||
| } | ||
|
|
||
| const resolvedServiceName = this.resolveServiceName(serviceName); | ||
| rclnodejs.validateTopicName(resolvedServiceName); | ||
| return resolvedServiceName; | ||
| } | ||
| } | ||
|
|
||
| /** | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -33,6 +33,13 @@ typedef rcl_ret_t (*rcl_get_info_by_topic_func_t)( | |
| const char* topic_name, bool no_mangle, | ||
| rcl_topic_endpoint_info_array_t* info_array); | ||
|
|
||
| #if ROS_VERSION > 2505 | ||
| typedef rcl_ret_t (*rcl_get_info_by_service_func_t)( | ||
| const rcl_node_t* node, rcutils_allocator_t* allocator, | ||
| const char* service_name, bool no_mangle, | ||
| rcl_service_endpoint_info_array_t* info_array); | ||
| #endif // ROS_VERSION > 2505 | ||
|
|
||
| Napi::Value GetPublisherNamesAndTypesByNode(const Napi::CallbackInfo& info) { | ||
| Napi::Env env = info.Env(); | ||
|
|
||
|
|
@@ -257,6 +264,66 @@ Napi::Value GetSubscriptionsInfoByTopic(const Napi::CallbackInfo& info) { | |
| "subscriptions", rcl_get_subscriptions_info_by_topic); | ||
| } | ||
|
|
||
| #if ROS_VERSION > 2505 | ||
| Napi::Value GetInfoByService( | ||
| Napi::Env env, rcl_node_t* node, const char* service_name, bool no_mangle, | ||
| const char* type, rcl_get_info_by_service_func_t rcl_get_info_by_service) { | ||
| rcutils_allocator_t allocator = rcutils_get_default_allocator(); | ||
| rcl_service_endpoint_info_array_t info_array = | ||
| rcl_get_zero_initialized_service_endpoint_info_array(); | ||
|
|
||
| RCPPUTILS_SCOPE_EXIT({ | ||
| rcl_ret_t fini_ret = | ||
| rcl_service_endpoint_info_array_fini(&info_array, &allocator); | ||
| if (RCL_RET_OK != fini_ret) { | ||
| Napi::Error::New(env, rcl_get_error_string().str) | ||
| .ThrowAsJavaScriptException(); | ||
| rcl_reset_error(); | ||
| } | ||
| }); | ||
|
|
||
| rcl_ret_t ret = rcl_get_info_by_service(node, &allocator, service_name, | ||
| no_mangle, &info_array); | ||
| if (RCL_RET_OK != ret) { | ||
| if (RCL_RET_UNSUPPORTED == ret) { | ||
| Napi::Error::New( | ||
| env, std::string("Failed to get information by service for ") + type + | ||
| ": function not supported by RMW_IMPLEMENTATION") | ||
| .ThrowAsJavaScriptException(); | ||
| return env.Undefined(); | ||
| } | ||
| Napi::Error::New( | ||
| env, std::string("Failed to get information by service for ") + type) | ||
| .ThrowAsJavaScriptException(); | ||
| return env.Undefined(); | ||
| } | ||
|
|
||
| return ConvertToJSServiceEndpointInfoList(env, &info_array); | ||
| } | ||
|
Comment on lines
+268
to
+302
|
||
| #endif // ROS_VERSION > 2505 | ||
|
|
||
| #if ROS_VERSION > 2505 | ||
| Napi::Value GetClientsInfoByService(const Napi::CallbackInfo& info) { | ||
| RclHandle* node_handle = RclHandle::Unwrap(info[0].As<Napi::Object>()); | ||
| rcl_node_t* node = reinterpret_cast<rcl_node_t*>(node_handle->ptr()); | ||
| std::string service_name = info[1].As<Napi::String>().Utf8Value(); | ||
| bool no_mangle = info[2].As<Napi::Boolean>(); | ||
|
|
||
| return GetInfoByService(info.Env(), node, service_name.c_str(), no_mangle, | ||
| "clients", rcl_get_clients_info_by_service); | ||
| } | ||
|
|
||
| Napi::Value GetServersInfoByService(const Napi::CallbackInfo& info) { | ||
| RclHandle* node_handle = RclHandle::Unwrap(info[0].As<Napi::Object>()); | ||
| rcl_node_t* node = reinterpret_cast<rcl_node_t*>(node_handle->ptr()); | ||
| std::string service_name = info[1].As<Napi::String>().Utf8Value(); | ||
| bool no_mangle = info[2].As<Napi::Boolean>(); | ||
|
|
||
| return GetInfoByService(info.Env(), node, service_name.c_str(), no_mangle, | ||
| "servers", rcl_get_servers_info_by_service); | ||
| } | ||
| #endif // ROS_VERSION > 2505 | ||
|
|
||
| Napi::Object InitGraphBindings(Napi::Env env, Napi::Object exports) { | ||
| exports.Set("getPublisherNamesAndTypesByNode", | ||
| Napi::Function::New(env, GetPublisherNamesAndTypesByNode)); | ||
|
|
@@ -274,6 +341,12 @@ Napi::Object InitGraphBindings(Napi::Env env, Napi::Object exports) { | |
| Napi::Function::New(env, GetPublishersInfoByTopic)); | ||
| exports.Set("getSubscriptionsInfoByTopic", | ||
| Napi::Function::New(env, GetSubscriptionsInfoByTopic)); | ||
| #if ROS_VERSION > 2505 | ||
| exports.Set("getClientsInfoByService", | ||
| Napi::Function::New(env, GetClientsInfoByService)); | ||
| exports.Set("getServersInfoByService", | ||
| Napi::Function::New(env, GetServersInfoByService)); | ||
| #endif // ROS_VERSION > 2505 | ||
| return exports; | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -60,4 +60,48 @@ describe('rclnodejs graph test suite', function () { | |||||||||
| assert.strictEqual(subscriptions[0].node_name, 'subscription_node'); | ||||||||||
| assert.strictEqual(subscriptions[0].topic_type, String); | ||||||||||
| }); | ||||||||||
|
|
||||||||||
| it('Get clients info by service', function () { | ||||||||||
| if ( | ||||||||||
| rclnodejs.DistroUtils.getDistroId() < | ||||||||||
| rclnodejs.DistroUtils.DistroId.ROLLING | ||||||||||
|
Comment on lines
+66
to
+67
|
||||||||||
| rclnodejs.DistroUtils.getDistroId() < | |
| rclnodejs.DistroUtils.DistroId.ROLLING | |
| rclnodejs.DistroUtils.getDistroId() <= | |
| rclnodejs.DistroUtils.DistroId.JAZZY |
Copilot
AI
Dec 1, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The version check is inconsistent with the C++ code. The C++ code uses #if ROS_VERSION > 2405 (greater than JAZZY), which includes KILTED (2505) and ROLLING (5000). However, this test skip condition < DistroUtils.DistroId.ROLLING only includes ROLLING and excludes KILTED.
For KILTED (2505):
- C++ code will compile the functions (2505 > 2405 = true)
- Test will be skipped (2505 < 5000 = true)
The check should be <= DistroUtils.DistroId.JAZZY to match the C++ version check.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The version check is inconsistent with the C++ code. The C++ code uses
#if ROS_VERSION > 2405(greater than JAZZY), which includes KILTED (2505) and ROLLING (5000). However, this JavaScript check< DistroUtils.DistroId.ROLLINGonly includes ROLLING and excludes KILTED, causing a mismatch.For KILTED (2505):
The check should be
<= DistroUtils.DistroId.JAZZYto match the C++ version check.