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
96 changes: 95 additions & 1 deletion lib/event_handler.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,11 @@

const rclnodejs = require('./native_loader.js');
const DistroUtils = require('./distro.js');
const { OperationError } = require('./errors.js');
const {
OperationError,
RangeValidationError,
TypeValidationError,
} = require('./errors.js');
const Entity = require('./entity.js');

/**
Expand Down Expand Up @@ -55,6 +59,94 @@ const SubscriptionEventType = {
SUBSCRIPTION_MATCHED: 5,
};

/**
* Check if a publisher event type is supported by the active RMW implementation.
*
* Only available in ROS 2 Rolling and later, where the underlying rcl API
* (`rcl_publisher_event_type_is_supported`) is provided.
*
* @param {number} eventType - A {@link PublisherEventType} value.
* @return {boolean} True if the event type is supported by the active RMW
* implementation, false otherwise.
* @throws {OperationError} if invoked on a ROS distro older than Rolling.
* @throws {TypeValidationError} if eventType is not a number.
* @throws {RangeValidationError} if eventType is not a valid
* {@link PublisherEventType} value.
*/
function isPublisherEventTypeSupported(eventType) {
if (typeof rclnodejs.isPublisherEventTypeSupported !== 'function') {
throw new OperationError(
'isPublisherEventTypeSupported is only available in ROS 2 Rolling and later',
{
code: 'UNSUPPORTED_ROS_VERSION',
entityType: 'publisher event type',
details: {
requiredVersion: 'rolling',
currentVersion: DistroUtils.getDistroId(),
},
}
);
}
if (typeof eventType !== 'number') {
throw new TypeValidationError('eventType', eventType, 'number', {
entityType: 'publisher event type',
});
}
if (!Object.values(PublisherEventType).includes(eventType)) {
throw new RangeValidationError(
'eventType',
eventType,
'one of PublisherEventType values',
{ entityType: 'publisher event type' }
);
}
return rclnodejs.isPublisherEventTypeSupported(eventType);
Comment on lines +76 to +103
}

/**
* Check if a subscription event type is supported by the active RMW implementation.
*
* Only available in ROS 2 Rolling and later, where the underlying rcl API
* (`rcl_subscription_event_type_is_supported`) is provided.
*
* @param {number} eventType - A {@link SubscriptionEventType} value.
* @return {boolean} True if the event type is supported by the active RMW
* implementation, false otherwise.
* @throws {OperationError} if invoked on a ROS distro older than Rolling.
* @throws {TypeValidationError} if eventType is not a number.
* @throws {RangeValidationError} if eventType is not a valid
* {@link SubscriptionEventType} value.
*/
function isSubscriptionEventTypeSupported(eventType) {
if (typeof rclnodejs.isSubscriptionEventTypeSupported !== 'function') {
throw new OperationError(
'isSubscriptionEventTypeSupported is only available in ROS 2 Rolling and later',
{
code: 'UNSUPPORTED_ROS_VERSION',
entityType: 'subscription event type',
details: {
requiredVersion: 'rolling',
currentVersion: DistroUtils.getDistroId(),
},
}
);
}
if (typeof eventType !== 'number') {
throw new TypeValidationError('eventType', eventType, 'number', {
entityType: 'subscription event type',
});
}
if (!Object.values(SubscriptionEventType).includes(eventType)) {
throw new RangeValidationError(
'eventType',
eventType,
'one of SubscriptionEventType values',
{ entityType: 'subscription event type' }
);
}
return rclnodejs.isSubscriptionEventTypeSupported(eventType);
}
Comment on lines +120 to +148

class EventHandler extends Entity {
constructor(handle, callback, eventType, eventTypeName) {
super(handle, null, null);
Expand Down Expand Up @@ -488,4 +580,6 @@ module.exports = {
PublisherEventType,
SubscriptionEventCallbacks,
SubscriptionEventType,
isPublisherEventTypeSupported,
isSubscriptionEventTypeSupported,
};
26 changes: 26 additions & 0 deletions src/rcl_event_handle_bindings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,26 @@ Napi::Value CreatePublisherEventHandle(const Napi::CallbackInfo& info) {
return js_obj;
}

#if ROS_VERSION >= 5000
Napi::Value IsPublisherEventTypeSupported(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
rcl_publisher_event_type_t event_type =
static_cast<rcl_publisher_event_type_t>(
info[0].As<Napi::Number>().Int32Value());
return Napi::Boolean::New(env,
rcl_publisher_event_type_is_supported(event_type));
}

Napi::Value IsSubscriptionEventTypeSupported(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
rcl_subscription_event_type_t event_type =
static_cast<rcl_subscription_event_type_t>(
info[0].As<Napi::Number>().Int32Value());
return Napi::Boolean::New(
env, rcl_subscription_event_type_is_supported(event_type));
}
#endif // ROS_VERSION >= 5000

Napi::Value TakeEvent(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
RclHandle* event_handle = RclHandle::Unwrap(info[0].As<Napi::Object>());
Expand Down Expand Up @@ -288,6 +308,12 @@ Napi::Object InitEventHandleBindings(Napi::Env env, Napi::Object exports) {
exports.Set("createPublisherEventHandle",
Napi::Function::New(env, CreatePublisherEventHandle));
exports.Set("takeEvent", Napi::Function::New(env, TakeEvent));
#if ROS_VERSION >= 5000
exports.Set("isPublisherEventTypeSupported",
Napi::Function::New(env, IsPublisherEventTypeSupported));
exports.Set("isSubscriptionEventTypeSupported",
Napi::Function::New(env, IsSubscriptionEventTypeSupported));
#endif // ROS_VERSION >= 5000
return exports;
}

Expand Down
83 changes: 83 additions & 0 deletions test/test-event-handle.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ const {
PublisherEventCallbacks,
PublisherEventType,
SubscriptionEventType,
isPublisherEventTypeSupported,
isSubscriptionEventTypeSupported,
} = require('../lib/event_handler.js');

describe('Event handle test suite prior to jazzy', function () {
Expand All @@ -46,6 +48,87 @@ describe('Event handle test suite prior to jazzy', function () {
});
});

describe('Event type is supported - native binding unavailable', function () {
before(function () {
if (
typeof rclnodejsBinding.isPublisherEventTypeSupported === 'function' &&
typeof rclnodejsBinding.isSubscriptionEventTypeSupported === 'function'
) {
this.skip();
}
});

it('isPublisherEventTypeSupported throws when native binding is missing', function () {
assert.throws(() => {
isPublisherEventTypeSupported(PublisherEventType.PUBLISHER_MATCHED);
}, /isPublisherEventTypeSupported is only available in ROS 2 Rolling and later/);
});

it('isSubscriptionEventTypeSupported throws when native binding is missing', function () {
assert.throws(() => {
isSubscriptionEventTypeSupported(
SubscriptionEventType.SUBSCRIPTION_MATCHED
);
}, /isSubscriptionEventTypeSupported is only available in ROS 2 Rolling and later/);
});
});

describe('Event type is supported - native binding available', function () {
before(function () {
if (
typeof rclnodejsBinding.isPublisherEventTypeSupported !== 'function' ||
typeof rclnodejsBinding.isSubscriptionEventTypeSupported !== 'function'
) {
this.skip();
}
});

it('isPublisherEventTypeSupported returns a boolean for every event type', function () {
for (const eventType of Object.values(PublisherEventType)) {
const result = isPublisherEventTypeSupported(eventType);
assert.strictEqual(typeof result, 'boolean');
}
});

it('isSubscriptionEventTypeSupported returns a boolean for every event type', function () {
for (const eventType of Object.values(SubscriptionEventType)) {
const result = isSubscriptionEventTypeSupported(eventType);
assert.strictEqual(typeof result, 'boolean');
}
});

it('MATCHED events are reported as supported across RMW implementations', function () {
assert.strictEqual(
isPublisherEventTypeSupported(PublisherEventType.PUBLISHER_MATCHED),
true
);
assert.strictEqual(
isSubscriptionEventTypeSupported(
SubscriptionEventType.SUBSCRIPTION_MATCHED
),
true
);
});

it('isPublisherEventTypeSupported rejects invalid event types', function () {
assert.throws(() => {
isPublisherEventTypeSupported(-1);
}, /Value '-1' for 'eventType' is out of range: one of PublisherEventType values/);
assert.throws(() => {
isPublisherEventTypeSupported('matched');
}, /Invalid type for 'eventType': expected number, got string/);
});

it('isSubscriptionEventTypeSupported rejects invalid event types', function () {
assert.throws(() => {
isSubscriptionEventTypeSupported(999);
}, /Value '999' for 'eventType' is out of range: one of SubscriptionEventType values/);
assert.throws(() => {
isSubscriptionEventTypeSupported(undefined);
}, /Invalid type for 'eventType': expected number, got undefined/);
});
});

describe('Event handle test suite', function () {
this.timeout(5 * 1000);
let node;
Expand Down
Loading