Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 57 additions & 13 deletions lib/node.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ const TypeDescriptionService = require('./type_description_service.js');
const Entity = require('./entity.js');
const { SubscriptionEventCallbacks } = require('../lib/event_handler.js');
const { PublisherEventCallbacks } = require('../lib/event_handler.js');
const { validateFullTopicName } = require('./validator.js');

// Parameter event publisher constants
const PARAMETER_EVENT_MSG_TYPE = 'rcl_interfaces/msg/ParameterEvent';
Expand Down Expand Up @@ -1072,27 +1073,57 @@ class Node extends rclnodejs.ShadowNode {
}

/**
* Get a list of publishers on a given topic.
* @param {string} topic - the topic name to get the publishers for.
* @param {boolean} noDemangle - if `true`, `topic_name` needs to be a valid middleware topic name,
* otherwise it should be a valid ROS topic name.
* Return a list of publishers on a given topic.
*
* The returned parameter is a list of TopicEndpointInfo objects, where each will contain
* the node name, node namespace, topic type, topic endpoint's GID, and its QoS profile.
*
* When the `no_mangle` parameter is `true`, the provided `topic` should be a valid
* topic name for the middleware (useful when combining ROS with native middleware (e.g. DDS)
* apps). When the `no_mangle` parameter is `false`, the provided `topic` should
* follow ROS topic name conventions.
*
* `topic` may be a relative, private, or fully qualified topic name.
* A relative or private topic will be expanded using this node's namespace and name.
* The queried `topic` is not remapped.
*
* @param {string} topic - The topic on which to find the publishers.
* @param {boolean} [noDemangle=false] - If `true`, `topic` needs to be a valid middleware topic
* name, otherwise it should be a valid ROS topic name. Defaults to `false`.
* @returns {Array} - list of publishers
*/
getPublishersInfoByTopic(topic, noDemangle) {
return rclnodejs.getPublishersInfoByTopic(this.handle, topic, noDemangle);
getPublishersInfoByTopic(topic, noDemangle = false) {
return rclnodejs.getPublishersInfoByTopic(
this.handle,
this._getValidatedTopic(topic, noDemangle),
noDemangle
);
}

/**
* Get a list of subscriptions on a given topic.
* @param {string} topic - the topic name to get the subscriptions for.
* @param {boolean} noDemangle - if `true`, `topic_name` needs to be a valid middleware topic name,
* otherwise it should be a valid ROS topic name.
* Return a list of subscriptions on a given topic.
*
* The returned parameter is a list of TopicEndpointInfo objects, where each will contain
* the node name, node namespace, topic type, topic endpoint's GID, and its QoS profile.
*
* When the `no_mangle` parameter is `true`, the provided `topic` should be a valid
* topic name for the middleware (useful when combining ROS with native middleware (e.g. DDS)
* apps). When the `no_mangle` parameter is `false`, the provided `topic` should
* follow ROS topic name conventions.
*
* `topic` may be a relative, private, or fully qualified topic name.
* A relative or private topic will be expanded using this node's namespace and name.
* The queried `topic` is not remapped.
*
* @param {string} topic - The topic on which to find the subscriptions.
* @param {boolean} [noDemangle=false] - If `true`, `topic` needs to be a valid middleware topic
name, otherwise it should be a valid ROS topic name. Defaults to `false`.
* @returns {Array} - list of subscriptions
*/
getSubscriptionsInfoByTopic(topic, noDemangle) {
getSubscriptionsInfoByTopic(topic, noDemangle = false) {
return rclnodejs.getSubscriptionsInfoByTopic(
this.handle,
topic,
this._getValidatedTopic(topic, noDemangle),
noDemangle
);
}
Expand Down Expand Up @@ -1428,7 +1459,7 @@ class Node extends rclnodejs.ShadowNode {
* Determine if a parameter descriptor exists.
*
* @param {string} name - The name of a descriptor to for.
* @return {boolean} - True if a descriptor has been declared; otherwise false.
* @return {boolean} - true if a descriptor has been declared; otherwise false.
*/
hasParameterDescriptor(name) {
return !!this.getParameterDescriptor(name);
Expand Down Expand Up @@ -1864,6 +1895,19 @@ class Node extends rclnodejs.ShadowNode {
this._actionServers.push(actionServer);
this.syncHandles();
}

_getValidatedTopic(topicName, noDemangle) {
if (noDemangle) {
return topicName;
}
const fqTopicName = rclnodejs.expandTopicName(
topicName,
this.name(),
this.namespace()
);
validateFullTopicName(fqTopicName);
return rclnodejs.remapTopicName(this.handle, fqTopicName);
}
}

/**
Expand Down
43 changes: 43 additions & 0 deletions src/rcl_node_bindings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#include <rcl/arguments.h>
#include <rcl/error_handling.h>
#include <rcl/rcl.h>
#include <rcl/remap.h>
#include <rcl_action/rcl_action.h>
#include <rcl_yaml_param_parser/parser.h>
#include <rcl_yaml_param_parser/types.h>
Expand Down Expand Up @@ -507,6 +508,47 @@ Napi::Value ResolveName(const Napi::CallbackInfo& info) {
return Napi::String::New(env, output_cstr);
}

Napi::Value RemapTopicName(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
RclHandle* node_handle = RclHandle::Unwrap(info[0].As<Napi::Object>());
rcl_node_t* node = reinterpret_cast<rcl_node_t*>(node_handle->ptr());
std::string topic_name = info[1].As<Napi::String>().Utf8Value();

const rcl_node_options_t* node_options = rcl_node_get_options(node);
if (nullptr == node_options) {
Napi::Error::New(env, "failed to get node options")
.ThrowAsJavaScriptException();
return env.Undefined();
}

const rcl_arguments_t* global_args = nullptr;
if (node_options->use_global_arguments) {
global_args = &(node->context->global_arguments);
}

char* output_cstr = nullptr;
rcl_ret_t ret = rcl_remap_topic_name(
&(node_options->arguments), global_args, topic_name.c_str(),
rcl_node_get_name(node), rcl_node_get_namespace(node),
node_options->allocator, &output_cstr);
if (RCL_RET_OK != ret) {
Napi::Error::New(env, "failed to remap topic name")
.ThrowAsJavaScriptException();
return env.Undefined();
}
if (nullptr == output_cstr) {
return Napi::String::New(env, topic_name);
}

auto name_deleter = [&]() {
node_options->allocator.deallocate(output_cstr,
node_options->allocator.state);
};
RCPPUTILS_SCOPE_EXIT({ name_deleter(); });

return Napi::String::New(env, output_cstr);
}

Napi::Object InitNodeBindings(Napi::Env env, Napi::Object exports) {
exports.Set("getParameterOverrides",
Napi::Function::New(env, GetParameterOverrides));
Expand All @@ -532,6 +574,7 @@ Napi::Object InitNodeBindings(Napi::Env env, Napi::Object exports) {
exports.Set("getRMWImplementationIdentifier",
Napi::Function::New(env, GetRMWImplementationIdentifier));
exports.Set("resolveName", Napi::Function::New(env, ResolveName));
exports.Set("remapTopicName", Napi::Function::New(env, RemapTopicName));
return exports;
}

Expand Down
21 changes: 17 additions & 4 deletions test/test-graph.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,21 +29,34 @@ describe('rclnodejs graph test suite', function () {
});

it('Get publishers info by topic', function () {
const node = rclnodejs.createNode('publisher_node');
const node = rclnodejs.createNode('publisher_node', '/my_ns');
assert.deepStrictEqual(
0,
node.getPublishersInfoByTopic('/my_ns/topic', false).length
);
const String = 'std_msgs/msg/String';
node.createPublisher(String, 'topic');
const publishers = node.getPublishersInfoByTopic('/topic', false);
const publishers = node.getPublishersInfoByTopic('/my_ns/topic', false);
assert.strictEqual(publishers.length, 1);
assert.strictEqual(publishers[0].node_namespace, '/my_ns');
assert.strictEqual(publishers[0].node_name, 'publisher_node');
assert.strictEqual(publishers[0].topic_type, String);
});

it('Get subscriptions info by topic', function () {
const node = rclnodejs.createNode('subscription_node');
const node = rclnodejs.createNode('subscription_node', '/my_ns');
assert.deepStrictEqual(
0,
node.getSubscriptionsInfoByTopic('/my_ns/topic', false).length
);
const String = 'std_msgs/msg/String';
node.createSubscription(String, 'topic', (msg) => {});
const subscriptions = node.getSubscriptionsInfoByTopic('/topic', false);
const subscriptions = node.getSubscriptionsInfoByTopic(
'/my_ns/topic',
false
);
assert.strictEqual(subscriptions.length, 1);
assert.strictEqual(subscriptions[0].node_namespace, '/my_ns');
assert.strictEqual(subscriptions[0].node_name, 'subscription_node');
assert.strictEqual(subscriptions[0].topic_type, String);
});
Expand Down
40 changes: 32 additions & 8 deletions types/node.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -750,21 +750,45 @@ declare module 'rclnodejs' {
getServiceNamesAndTypes(): Array<NamesAndTypesQueryResult>;

/**
* Get an array of publishers on a given topic.
* Return a list of publishers on a given topic.
*
* @param topic - The name of the topic.
* @param noDemangle - if `true`, `topic_name` needs to be a valid middleware topic name,
* otherwise it should be a valid ROS topic name.
* The returned parameter is a list of TopicEndpointInfo objects, where each will contain
* the node name, node namespace, topic type, topic endpoint's GID, and its QoS profile.
*
* When the `no_mangle` parameter is `true`, the provided `topic` should be a valid
* topic name for the middleware (useful when combining ROS with native middleware (e.g. DDS)
* apps). When the `no_mangle` parameter is `false`, the provided `topic` should
* follow ROS topic name conventions.
*
* `topic` may be a relative, private, or fully qualified topic name.
* A relative or private topic will be expanded using this node's namespace and name.
* The queried `topic` is not remapped.
*
* @param topic - The topic on which to find the publishers.
* @param [noDemangle=false] - If `true`, `topic` needs to be a valid middleware topic
* name, otherwise it should be a valid ROS topic name. Defaults to `false`.
* @returns An array of publishers.
*/
getPublishersInfoByTopic(topic: string, noDemangle: boolean): Array<object>;

/**
* Get an array of subscriptions on a given topic.
* Return a list of subscriptions on a given topic.
*
* @param topic - The name of the topic.
* @param noDemangle - if `true`, `topic_name` needs to be a valid middleware topic name,
* otherwise it should be a valid ROS topic name.
* The returned parameter is a list of TopicEndpointInfo objects, where each will contain
* the node name, node namespace, topic type, topic endpoint's GID, and its QoS profile.
*
* When the `no_mangle` parameter is `true`, the provided `topic` should be a valid
* topic name for the middleware (useful when combining ROS with native middleware (e.g. DDS)
* apps). When the `no_mangle` parameter is `false`, the provided `topic` should
* follow ROS topic name conventions.
*
* `topic` may be a relative, private, or fully qualified topic name.
* A relative or private topic will be expanded using this node's namespace and name.
* The queried `topic` is not remapped.
*
* @param topic - The topic on which to find the subscriptions..
* @param [noDemangle=false] - If `true`, `topic` needs to be a valid middleware topic
name, otherwise it should be a valid ROS topic name. Defaults to `false`.
* @returns An array of subscriptions.
*/
getSubscriptionsInfoByTopic(
Expand Down
Loading