diff --git a/lib/context.js b/lib/context.js index 050c9c93..0865a86b 100644 --- a/lib/context.js +++ b/lib/context.js @@ -219,6 +219,14 @@ class Context { } return defaultContext; } + + /** + * Get the domain ID of this context. + * @returns {Number} domain ID of this context + */ + get domainId() { + return rclnodejs.getDomainId(this.handle); + } } Context._instances = []; diff --git a/lib/node.js b/lib/node.js index 95d5ae0f..f8155179 100644 --- a/lib/node.js +++ b/lib/node.js @@ -1061,6 +1061,24 @@ class Node extends rclnodejs.ShadowNode { return rclnodejs.countSubscribers(this.handle, expandedTopic); } + /** + * Get the number of clients on a given service name. + * @param {string} serviceName - the service name + * @returns {Number} + */ + countClients(serviceName) { + return rclnodejs.countClients(this.handle, serviceName); + } + + /** + * Get the number of services on a given service name. + * @param {string} serviceName - the service name + * @returns {Number} + */ + countServices(serviceName) { + return rclnodejs.countServices(this.handle, serviceName); + } + /** * Get the list of parameter-overrides found on the commandline and * in the NodeOptions.parameter_overrides property. diff --git a/lib/publisher.js b/lib/publisher.js index 7985847c..6f90cca4 100644 --- a/lib/publisher.js +++ b/lib/publisher.js @@ -72,6 +72,14 @@ class Publisher extends Entity { ); return new Publisher(handle, typeClass, topic, options); } + + /** + * Get the number of subscriptions to this publisher. + * @returns {number} The number of subscriptions + */ + get subscriptionCount() { + return rclnodejs.getSubscriptionCount(this._handle); + } } module.exports = Publisher; diff --git a/lib/subscription.js b/lib/subscription.js index f7af18ba..f563ae01 100644 --- a/lib/subscription.js +++ b/lib/subscription.js @@ -116,6 +116,14 @@ class Subscription extends Entity { ? rclnodejs.clearContentFilter(this.handle) : true; } + + /** + * Get the number of publishers to this subscription. + * @returns {number} The number of publishers + */ + get publisherCount() { + return rclnodejs.getPublisherCount(this._handle); + } } module.exports = Subscription; diff --git a/src/rcl_context_bindings.cpp b/src/rcl_context_bindings.cpp index 1d971177..5e1c22db 100644 --- a/src/rcl_context_bindings.cpp +++ b/src/rcl_context_bindings.cpp @@ -127,11 +127,29 @@ Napi::Value IsContextValid(const Napi::CallbackInfo& info) { return Napi::Boolean::New(env, is_valid); } +Napi::Value GetDomainId(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); + + RclHandle* context_handle = RclHandle::Unwrap(info[0].As()); + rcl_context_t* context = + reinterpret_cast(context_handle->ptr()); + size_t domain_id; + rcl_ret_t ret = rcl_context_get_domain_id(context, &domain_id); + if (RCL_RET_OK != ret) { + Napi::Error::New(env, rcl_get_error_string().str) + .ThrowAsJavaScriptException(); + return env.Undefined(); + } + + return Napi::Number::New(env, domain_id); +} + Napi::Object InitContextBindings(Napi::Env env, Napi::Object exports) { exports.Set("init", Napi::Function::New(env, Init)); exports.Set("shutdown", Napi::Function::New(env, Shutdown)); exports.Set("createContext", Napi::Function::New(env, CreateContext)); exports.Set("isContextValid", Napi::Function::New(env, IsContextValid)); + exports.Set("getDomainId", Napi::Function::New(env, GetDomainId)); return exports; } diff --git a/src/rcl_graph_bindings.cpp b/src/rcl_graph_bindings.cpp index 5461d751..6bf4e54c 100644 --- a/src/rcl_graph_bindings.cpp +++ b/src/rcl_graph_bindings.cpp @@ -232,36 +232,6 @@ Napi::Value GetNodeNames(const Napi::CallbackInfo& info) { return result_list; } -Napi::Value CountPublishers(const Napi::CallbackInfo& info) { - Napi::Env env = info.Env(); - - RclHandle* node_handle = RclHandle::Unwrap(info[0].As()); - rcl_node_t* node = reinterpret_cast(node_handle->ptr()); - std::string topic_name = info[1].As().Utf8Value(); - - size_t count = 0; - THROW_ERROR_IF_NOT_EQUAL( - RCL_RET_OK, rcl_count_publishers(node, topic_name.c_str(), &count), - "Failed to count publishers."); - - return Napi::Number::New(env, static_cast(count)); -} - -Napi::Value CountSubscribers(const Napi::CallbackInfo& info) { - Napi::Env env = info.Env(); - - RclHandle* node_handle = RclHandle::Unwrap(info[0].As()); - rcl_node_t* node = reinterpret_cast(node_handle->ptr()); - std::string topic_name = info[1].As().Utf8Value(); - - size_t count = 0; - THROW_ERROR_IF_NOT_EQUAL( - RCL_RET_OK, rcl_count_subscribers(node, topic_name.c_str(), &count), - "Failed to count subscribers."); - - return Napi::Number::New(env, static_cast(count)); -} - Napi::Value ServiceServerIsAvailable(const Napi::CallbackInfo& info) { Napi::Env env = info.Env(); @@ -292,8 +262,6 @@ Napi::Object InitGraphBindings(Napi::Env env, Napi::Object exports) { exports.Set("getServiceNamesAndTypes", Napi::Function::New(env, GetServiceNamesAndTypes)); exports.Set("getNodeNames", Napi::Function::New(env, GetNodeNames)); - exports.Set("countPublishers", Napi::Function::New(env, CountPublishers)); - exports.Set("countSubscribers", Napi::Function::New(env, CountSubscribers)); exports.Set("serviceServerIsAvailable", Napi::Function::New(env, ServiceServerIsAvailable)); return exports; diff --git a/src/rcl_node_bindings.cpp b/src/rcl_node_bindings.cpp index 1b6e53cc..cf0fa517 100644 --- a/src/rcl_node_bindings.cpp +++ b/src/rcl_node_bindings.cpp @@ -318,6 +318,66 @@ Napi::Value ActionGetNamesAndTypes(const Napi::CallbackInfo& info) { return result_list; } +Napi::Value CountPublishers(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); + + RclHandle* node_handle = RclHandle::Unwrap(info[0].As()); + rcl_node_t* node = reinterpret_cast(node_handle->ptr()); + std::string topic_name = info[1].As().Utf8Value(); + + size_t count = 0; + THROW_ERROR_IF_NOT_EQUAL( + RCL_RET_OK, rcl_count_publishers(node, topic_name.c_str(), &count), + "Failed to count publishers."); + + return Napi::Number::New(env, static_cast(count)); +} + +Napi::Value CountSubscribers(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); + + RclHandle* node_handle = RclHandle::Unwrap(info[0].As()); + rcl_node_t* node = reinterpret_cast(node_handle->ptr()); + std::string topic_name = info[1].As().Utf8Value(); + + size_t count = 0; + THROW_ERROR_IF_NOT_EQUAL( + RCL_RET_OK, rcl_count_subscribers(node, topic_name.c_str(), &count), + "Failed to count subscribers."); + + return Napi::Number::New(env, static_cast(count)); +} + +Napi::Value CountClients(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); + + rcl_node_t* node = reinterpret_cast( + RclHandle::Unwrap(info[0].As())->ptr()); + std::string service_name = info[1].As().Utf8Value(); + + size_t count = 0; + THROW_ERROR_IF_NOT_EQUAL( + rcl_count_clients(node, service_name.c_str(), &count), RCL_RET_OK, + rcl_get_error_string().str); + + return Napi::Number::New(env, count); +} + +Napi::Value CountServices(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); + + rcl_node_t* node = reinterpret_cast( + RclHandle::Unwrap(info[0].As())->ptr()); + std::string service_name = info[1].As().Utf8Value(); + + size_t count = 0; + THROW_ERROR_IF_NOT_EQUAL( + rcl_count_services(node, service_name.c_str(), &count), RCL_RET_OK, + rcl_get_error_string().str); + + return Napi::Number::New(env, count); +} + Napi::Object InitNodeBindings(Napi::Env env, Napi::Object exports) { exports.Set("getParameterOverrides", Napi::Function::New(env, GetParameterOverrides)); @@ -331,6 +391,10 @@ Napi::Object InitNodeBindings(Napi::Env env, Napi::Object exports) { Napi::Function::New(env, ActionGetServerNamesAndTypesByNode)); exports.Set("actionGetNamesAndTypes", Napi::Function::New(env, ActionGetNamesAndTypes)); + exports.Set("countPublishers", Napi::Function::New(env, CountPublishers)); + exports.Set("countSubscribers", Napi::Function::New(env, CountSubscribers)); + exports.Set("countClients", Napi::Function::New(env, CountClients)); + exports.Set("countServices", Napi::Function::New(env, CountServices)); return exports; } diff --git a/src/rcl_publisher_bindings.cpp b/src/rcl_publisher_bindings.cpp index b382075e..bc02ae02 100644 --- a/src/rcl_publisher_bindings.cpp +++ b/src/rcl_publisher_bindings.cpp @@ -114,11 +114,27 @@ Napi::Value PublishRawMessage(const Napi::CallbackInfo& info) { return env.Undefined(); } +Napi::Value GetSubscriptionCount(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); + + rcl_publisher_t* publisher = reinterpret_cast( + RclHandle::Unwrap(info[0].As())->ptr()); + + size_t count = 0; + THROW_ERROR_IF_NOT_EQUAL( + rcl_publisher_get_subscription_count(publisher, &count), RCL_RET_OK, + rcl_get_error_string().str); + + return Napi::Number::New(env, count); +} + Napi::Object InitPublisherBindings(Napi::Env env, Napi::Object exports) { exports.Set("createPublisher", Napi::Function::New(env, CreatePublisher)); exports.Set("publish", Napi::Function::New(env, Publish)); exports.Set("getPublisherTopic", Napi::Function::New(env, GetPublisherTopic)); exports.Set("publishRawMessage", Napi::Function::New(env, PublishRawMessage)); + exports.Set("getSubscriptionCount", + Napi::Function::New(env, GetSubscriptionCount)); return exports; } diff --git a/src/rcl_subscription_bindings.cpp b/src/rcl_subscription_bindings.cpp index e5f2cf62..bece3ea7 100644 --- a/src/rcl_subscription_bindings.cpp +++ b/src/rcl_subscription_bindings.cpp @@ -303,6 +303,20 @@ Napi::Value ClearContentFilter(const Napi::CallbackInfo& info) { #endif } +Napi::Value GetPublisherCount(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); + + rcl_subscription_t* subscription = reinterpret_cast( + RclHandle::Unwrap(info[0].As())->ptr()); + + size_t count = 0; + THROW_ERROR_IF_NOT_EQUAL( + rcl_subscription_get_publisher_count(subscription, &count), RCL_RET_OK, + rcl_get_error_string().str); + + return Napi::Number::New(env, count); +} + Napi::Object InitSubscriptionBindings(Napi::Env env, Napi::Object exports) { exports.Set("rclTake", Napi::Function::New(env, RclTake)); exports.Set("createSubscription", @@ -314,6 +328,7 @@ Napi::Object InitSubscriptionBindings(Napi::Env env, Napi::Object exports) { exports.Set("setContentFilter", Napi::Function::New(env, SetContentFilter)); exports.Set("clearContentFilter", Napi::Function::New(env, ClearContentFilter)); + exports.Set("getPublisherCount", Napi::Function::New(env, GetPublisherCount)); return exports; } diff --git a/test/test-context.js b/test/test-context.js index 7af58cd6..e1c6bfd4 100644 --- a/test/test-context.js +++ b/test/test-context.js @@ -80,4 +80,10 @@ describe('context test suite', function () { context.shutdown(); assert.strictEqual(context.nodes.length, 0); }); + + it('context number id', async function () { + let context = new rclnodejs.Context(); + await rclnodejs.init(context); + assert.strictEqual(typeof context.domainId, 'number'); + }); }); diff --git a/test/test-node.js b/test/test-node.js index e0b85f5a..fe0c6150 100644 --- a/test/test-node.js +++ b/test/test-node.js @@ -420,6 +420,23 @@ describe('rcl node methods testing', function () { await assertUtils.createDelay(500); assert.strictEqual(node.countSubscribers('chatter'), 2); }); + + it('node.countClients', function () { + const node = rclnodejs.createNode('publisher_node'); + const AddTwoInts = 'example_interfaces/srv/AddTwoInts'; + node.createClient(AddTwoInts, 'add_two_ints'); + assert.strictEqual(node.countClients('/add_two_ints'), 1); + + node.createClient(AddTwoInts, 'add_two_ints'); + assert.strictEqual(node.countClients('/add_two_ints'), 2); + }); + + it('node.countServices', function () { + const node = rclnodejs.createNode('publisher_node'); + const AddTwoInts = 'example_interfaces/srv/AddTwoInts'; + node.createService(AddTwoInts, 'add_two_ints', (req) => {}); + assert.strictEqual(node.countServices('/add_two_ints'), 1); + }); }); describe('topic & serviceName getter/setter', function () { diff --git a/test/test-publisher.js b/test/test-publisher.js index f8109593..31e48c5c 100644 --- a/test/test-publisher.js +++ b/test/test-publisher.js @@ -14,6 +14,7 @@ 'use strict'; +const assert = require('assert'); const rclnodejs = require('../index.js'); describe('rclnodejs publisher test suite', function () { @@ -42,4 +43,12 @@ describe('rclnodejs publisher test suite', function () { publisher.publish(msg); rclnodejs.spin(node); }); + + it('Test count of subscriptions', function () { + const node = rclnodejs.createNode('publisher_node'); + const String = 'std_msgs/msg/String'; + const publisher = node.createPublisher(String, 'topic'); + node.createSubscription(String, 'topic', (msg) => {}); + assert.strictEqual(publisher.subscriptionCount, 1); + }); }); diff --git a/test/test-subscription.js b/test/test-subscription.js new file mode 100644 index 00000000..3a2afad8 --- /dev/null +++ b/test/test-subscription.js @@ -0,0 +1,38 @@ +// Copyright (c) 2025, The Robot Web Tools Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +'use strict'; + +const assert = require('assert'); +const rclnodejs = require('../index.js'); + +describe('rclnodejs subscription test suite', function () { + this.timeout(60 * 1000); + + beforeEach(function () { + return rclnodejs.init(); + }); + + afterEach(function () { + rclnodejs.shutdown(); + }); + + it('Test count of subscription', function () { + const node = rclnodejs.createNode('publisher_node'); + const String = 'std_msgs/msg/String'; + node.createPublisher(String, 'topic'); + const subscription = node.createSubscription(String, 'topic', (msg) => {}); + assert.strictEqual(subscription.publisherCount, 1); + }); +});