// 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.
import rclnodejs from './native_loader.js';
import DistroUtils from './distro.js';
import {
OperationError,
RangeValidationError,
TypeValidationError,
} from './errors.js';
import Entity from './entity.js';
/**
* Enumeration for PublisherEventCallbacks event types.
* @enum {number}
*/
const PublisherEventType = {
/** @member {number} */
PUBLISHER_OFFERED_DEADLINE_MISSED: 0,
/** @member {number} */
PUBLISHER_LIVELINESS_LOST: 1,
/** @member {number} */
PUBLISHER_OFFERED_INCOMPATIBLE_QOS: 2,
/** @member {number} */
PUBLISHER_INCOMPATIBLE_TYPE: 3,
/** @member {number} */
PUBLISHER_MATCHED: 4,
};
/**
* Enumeration for SubscriptionEventCallbacks event types.
* @enum {number}
*/
const SubscriptionEventType = {
/** @member {number} */
SUBSCRIPTION_REQUESTED_DEADLINE_MISSED: 0,
/** @member {number} */
SUBSCRIPTION_LIVELINESS_CHANGED: 1,
/** @member {number} */
SUBSCRIPTION_REQUESTED_INCOMPATIBLE_QOS: 2,
/** @member {number} */
SUBSCRIPTION_MESSAGE_LOST: 3,
/** @member {number} */
SUBSCRIPTION_INCOMPATIBLE_TYPE: 4,
/** @member {number} */
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);
}
/**
* 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);
}
class EventHandler extends Entity {
constructor(handle, callback, eventType, eventTypeName) {
super(handle, null, null);
this._callback = callback;
this._eventType = eventType;
this._eventTypeName = eventTypeName;
}
takeData() {
const data = rclnodejs.takeEvent(this._handle, {
[this._eventTypeName]: this._eventType,
});
if (this._callback) {
this._callback(data);
}
}
}
/**
* @class - Class representing a ROS 2 PublisherEventCallbacks
* @hideconstructor
*/
class PublisherEventCallbacks {
constructor() {
if (DistroUtils.getDistroId() < DistroUtils.getDistroId('jazzy')) {
throw new OperationError(
'PublisherEventCallbacks is only available in ROS 2 Jazzy and later',
{
code: 'UNSUPPORTED_ROS_VERSION',
entityType: 'publisher event callbacks',
details: {
requiredVersion: 'jazzy',
currentVersion: DistroUtils.getDistroId(),
},
}
);
}
this._deadline = null;
this._incompatible_qos = null;
this._liveliness = null;
this._incompatible_type = null;
this._matched = null;
this._eventHandlers = [];
}
/**
* Set deadline missed callback.
* @param {function} callback - The callback function to be called.
*/
set deadline(callback) {
this._deadline = callback;
}
/**
* Get deadline missed callback.
* @return {function} - The callback function.
*/
get deadline() {
return this._deadline;
}
/**
* Set incompatible QoS callback.
* @param {function} callback - The callback function to be called.
*/
set incompatibleQos(callback) {
this._incompatible_qos = callback;
}
/**
* Get incompatible QoS callback.
* @return {function} - The callback function.
*/
get incompatibleQos() {
return this._incompatible_qos;
}
/**
* Set liveliness lost callback.
* @param {function} callback - The callback function to be called.
*/
set liveliness(callback) {
this._liveliness = callback;
}
/**
* Get liveliness lost callback.
* @return {function} - The callback function.
*/
get liveliness() {
return this._liveliness;
}
/**
* Set incompatible type callback.
* @param {function} callback - The callback function to be called.
*/
set incompatibleType(callback) {
this._incompatible_type = callback;
}
/**
* Get incompatible type callback.
* @return {function} - The callback function.
*/
get incompatibleType() {
return this._incompatible_type;
}
/**
* Set matched callback.
* @param {function} callback - The callback function to be called.
*/
set matched(callback) {
this._matched = callback;
}
/**
* Get matched callback.
* @return {function} - The callback function.
*/
get matched() {
return this._matched;
}
createEventHandlers(publisherHandle) {
if (this._deadline) {
const deadlineHandle = rclnodejs.createPublisherEventHandle(
publisherHandle,
PublisherEventType.PUBLISHER_OFFERED_DEADLINE_MISSED
);
this._eventHandlers.push(
new EventHandler(
deadlineHandle,
this._deadline,
PublisherEventType.PUBLISHER_OFFERED_DEADLINE_MISSED,
'publisher_event_type'
)
);
}
if (this._incompatible_qos) {
const incompatibleQosHandle = rclnodejs.createPublisherEventHandle(
publisherHandle,
PublisherEventType.PUBLISHER_OFFERED_INCOMPATIBLE_QOS
);
this._eventHandlers.push(
new EventHandler(
incompatibleQosHandle,
this._incompatible_qos,
PublisherEventType.PUBLISHER_OFFERED_INCOMPATIBLE_QOS,
'publisher_event_type'
)
);
}
if (this._liveliness) {
const livelinessHandle = rclnodejs.createPublisherEventHandle(
publisherHandle,
PublisherEventType.PUBLISHER_LIVELINESS_LOST
);
this._eventHandlers.push(
new EventHandler(
livelinessHandle,
this._liveliness,
PublisherEventType.PUBLISHER_LIVELINESS_LOST,
'publisher_event_type'
)
);
}
if (this._incompatible_type) {
const incompatibleTypeHandle = rclnodejs.createPublisherEventHandle(
publisherHandle,
PublisherEventType.PUBLISHER_INCOMPATIBLE_TYPE
);
this._eventHandlers.push(
new EventHandler(
incompatibleTypeHandle,
this._incompatible_type,
PublisherEventType.PUBLISHER_INCOMPATIBLE_TYPE,
'publisher_event_type'
)
);
}
if (this._matched) {
const matchedHandle = rclnodejs.createPublisherEventHandle(
publisherHandle,
PublisherEventType.PUBLISHER_MATCHED
);
this._eventHandlers.push(
new EventHandler(
matchedHandle,
this._matched,
PublisherEventType.PUBLISHER_MATCHED,
'publisher_event_type'
)
);
}
return this._eventHandlers;
}
get eventHandlers() {
return this._eventHandlers;
}
}
/**
* @class - Class representing a ROS 2 SubscriptionEventCallbacks
* @hideconstructor
*/
class SubscriptionEventCallbacks {
constructor() {
if (DistroUtils.getDistroId() < DistroUtils.getDistroId('jazzy')) {
throw new OperationError(
'SubscriptionEventCallbacks is only available in ROS 2 Jazzy and later',
{
code: 'UNSUPPORTED_ROS_VERSION',
entityType: 'subscription event callbacks',
details: {
requiredVersion: 'jazzy',
currentVersion: DistroUtils.getDistroId(),
},
}
);
}
this._deadline = null;
this._incompatible_qos = null;
this._liveliness = null;
this._message_lost = null;
this._incompatible_type = null;
this._matched = null;
this._eventHandlers = [];
}
/**
* Set the callback for deadline missed event.
* @param {function} callback - The callback function to be called.
*/
set deadline(callback) {
this._deadline = callback;
}
/**
* Get the callback for deadline missed event.
* @return {function} - The callback function.
*/
get deadline() {
return this._deadline;
}
/**
* Set the callback for incompatible QoS event.
* @param {function} callback - The callback function to be called.
*/
set incompatibleQos(callback) {
this._incompatible_qos = callback;
}
/**
* Get the callback for incompatible QoS event.
* @return {function} - The callback function.
*/
get incompatibleQos() {
return this._incompatible_qos;
}
/**
* Set the callback for liveliness changed event.
* @param {function} callback - The callback function to be called.
*/
set liveliness(callback) {
this._liveliness = callback;
}
/**
* Get the callback for liveliness changed event.
* @return {function} - The callback function.
*/
get liveliness() {
return this._liveliness;
}
/**
* Set the callback for message lost event.
* @param {function} callback - The callback function to be called.
*/
set messageLost(callback) {
this._message_lost = callback;
}
/**
* Get the callback for message lost event.
* @return {function} - The callback function.
*/
get messageLost() {
return this._message_lost;
}
/**
* Set the callback for incompatible type event.
* @param {function} callback - The callback function to be called.
*/
set incompatibleType(callback) {
this._incompatible_type = callback;
}
/**
* Get the callback for incompatible type event.
* @return {function} - The callback function.
*/
get incompatibleType() {
return this._incompatible_type;
}
/**
* Set the callback for matched event.
* @param {function} callback - The callback function to be called.
*/
set matched(callback) {
this._matched = callback;
}
/**
* Get the callback for matched event.
* @return {function} - The callback function.
*/
get matched() {
return this._matched;
}
createEventHandlers(subscriptionHandle) {
if (this._deadline) {
const deadlineHandle = rclnodejs.createSubscriptionEventHandle(
subscriptionHandle,
SubscriptionEventType.SUBSCRIPTION_REQUESTED_DEADLINE_MISSED
);
this._eventHandlers.push(
new EventHandler(
deadlineHandle,
this._deadline,
SubscriptionEventType.SUBSCRIPTION_REQUESTED_DEADLINE_MISSED,
'subscription_event_type'
)
);
}
if (this._incompatible_qos) {
const incompatibleQosHandle = rclnodejs.createSubscriptionEventHandle(
subscriptionHandle,
SubscriptionEventType.SUBSCRIPTION_REQUESTED_INCOMPATIBLE_QOS
);
this._eventHandlers.push(
new EventHandler(
incompatibleQosHandle,
this._incompatible_qos,
SubscriptionEventType.SUBSCRIPTION_REQUESTED_INCOMPATIBLE_QOS,
'subscription_event_type'
)
);
}
if (this._liveliness) {
const livelinessHandle = rclnodejs.createSubscriptionEventHandle(
subscriptionHandle,
SubscriptionEventType.SUBSCRIPTION_LIVELINESS_CHANGED
);
this._eventHandlers.push(
new EventHandler(
livelinessHandle,
this._liveliness,
SubscriptionEventType.SUBSCRIPTION_LIVELINESS_CHANGED,
'subscription_event_type'
)
);
}
if (this._message_lost) {
const messageLostHandle = rclnodejs.createSubscriptionEventHandle(
subscriptionHandle,
SubscriptionEventType.SUBSCRIPTION_MESSAGE_LOST
);
this._eventHandlers.push(
new EventHandler(
messageLostHandle,
this._message_lost,
SubscriptionEventType.SUBSCRIPTION_MESSAGE_LOST,
'subscription_event_type'
)
);
}
if (this._incompatible_type) {
const incompatibleTypeHandle = rclnodejs.createSubscriptionEventHandle(
subscriptionHandle,
SubscriptionEventType.SUBSCRIPTION_INCOMPATIBLE_TYPE
);
this._eventHandlers.push(
new EventHandler(
incompatibleTypeHandle,
this._incompatible_type,
SubscriptionEventType.SUBSCRIPTION_INCOMPATIBLE_TYPE,
'subscription_event_type'
)
);
}
if (this._matched) {
const matchedHandle = rclnodejs.createSubscriptionEventHandle(
subscriptionHandle,
SubscriptionEventType.SUBSCRIPTION_MATCHED
);
this._eventHandlers.push(
new EventHandler(
matchedHandle,
this._matched,
SubscriptionEventType.SUBSCRIPTION_MATCHED,
'subscription_event_type'
)
);
}
return this._eventHandlers;
}
}
export {
PublisherEventCallbacks,
PublisherEventType,
SubscriptionEventCallbacks,
SubscriptionEventType,
isPublisherEventTypeSupported,
isSubscriptionEventTypeSupported,
};