UNPKG

rclnodejs

Version:
359 lines (337 loc) 11.5 kB
// Copyright (c) 2026 The Robot Web Tools Contributors. All rights reserved. // // 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 QoS = require('./qos.js'); const { Parameter, ParameterType, ParameterDescriptor, } = require('./parameter.js'); /** * Enum of overridable QoS policy kinds. * Each value corresponds to a QoS property on the {@link QoS} class. * @enum {number} */ const QoSPolicyKind = Object.freeze({ HISTORY: 1, DEPTH: 2, RELIABILITY: 3, DURABILITY: 4, LIVELINESS: 5, AVOID_ROS_NAMESPACE_CONVENTIONS: 6, }); // Maps QoSPolicyKind -> { qosProp, paramKey, paramType, toParam, fromParam } const POLICY_MAP = { [QoSPolicyKind.HISTORY]: { qosProp: 'history', paramKey: 'history', enumObj: QoS.HistoryPolicy, paramType: ParameterType.PARAMETER_STRING, toParam: (val, enumObj) => _enumToString(val, enumObj), fromParam: (val, enumObj) => _stringToEnum(val, enumObj), }, [QoSPolicyKind.DEPTH]: { qosProp: 'depth', paramKey: 'depth', paramType: ParameterType.PARAMETER_INTEGER, toParam: (val) => BigInt(val), fromParam: (val) => Number(val), }, [QoSPolicyKind.RELIABILITY]: { qosProp: 'reliability', paramKey: 'reliability', enumObj: QoS.ReliabilityPolicy, paramType: ParameterType.PARAMETER_STRING, toParam: (val, enumObj) => _enumToString(val, enumObj), fromParam: (val, enumObj) => _stringToEnum(val, enumObj), }, [QoSPolicyKind.DURABILITY]: { qosProp: 'durability', paramKey: 'durability', enumObj: QoS.DurabilityPolicy, paramType: ParameterType.PARAMETER_STRING, toParam: (val, enumObj) => _enumToString(val, enumObj), fromParam: (val, enumObj) => _stringToEnum(val, enumObj), }, [QoSPolicyKind.LIVELINESS]: { qosProp: 'liveliness', paramKey: 'liveliness', enumObj: QoS.LivelinessPolicy, paramType: ParameterType.PARAMETER_STRING, toParam: (val, enumObj) => _enumToString(val, enumObj), fromParam: (val, enumObj) => _stringToEnum(val, enumObj), }, [QoSPolicyKind.AVOID_ROS_NAMESPACE_CONVENTIONS]: { qosProp: 'avoidRosNameSpaceConventions', paramKey: 'avoid_ros_namespace_conventions', paramType: ParameterType.PARAMETER_BOOL, toParam: (val) => val, fromParam: (val) => Boolean(val), }, }; /** * Convert a numeric enum value to a lowercase string name. * @param {number} val - enum numeric value * @param {Object} enumObj - the enum object (e.g. QoS.HistoryPolicy) * @returns {string} */ function _enumToString(val, enumObj) { for (const [key, v] of Object.entries(enumObj)) { if (v === val) { // Strip the RMW prefix: "RMW_QOS_POLICY_HISTORY_KEEP_LAST" -> "keep_last" const parts = key.split('_'); // Find the index after the policy name (HISTORY, RELIABILITY, etc.) // Pattern: RMW_QOS_POLICY_<POLICY>_<VALUE> const policyNames = [ 'HISTORY', 'RELIABILITY', 'DURABILITY', 'LIVELINESS', ]; let valueStart = 4; // default: skip RMW_QOS_POLICY_<X>_ for (let i = 3; i < parts.length; i++) { if (policyNames.includes(parts[i])) { valueStart = i + 1; break; } } return parts.slice(valueStart).join('_').toLowerCase(); } } return String(val); } /** * Convert a lowercase string back to a numeric enum value. * @param {string} str - the string value (e.g. "keep_last") * @param {Object} enumObj - the enum object * @returns {number} */ function _stringToEnum(str, enumObj) { const upper = str.toUpperCase(); for (const [key, val] of Object.entries(enumObj)) { if (key.endsWith('_' + upper)) { return val; } } throw new Error(`Unknown QoS policy value: "${str}"`); } /** * Options for overriding QoS policies via ROS parameters. * * When passed to `createPublisher()` or `createSubscription()`, the node * will declare read-only parameters for each specified policy kind. These * parameters can be set via command-line arguments, launch files, or * parameter files to override the QoS profile at startup. * * Parameter naming convention: * `qos_overrides.<topic>.<publisher|subscription>[_<entityId>].<policy>` * * @example * // Override history, depth, and reliability via parameters * const sub = node.createSubscription( * 'std_msgs/msg/String', '/chatter', * { qos: rclnodejs.QoS.profileDefault, * qosOverridingOptions: QoSOverridingOptions.withDefaultPolicies() }, * (msg) => console.log(msg.data) * ); * // Now you can override via CLI: * // --ros-args -p "qos_overrides./chatter.subscription.depth:=20" */ class QoSOverridingOptions { /** * @param {Array<QoSPolicyKind>} policyKinds - Which QoS policies to expose as parameters. * @param {Object} [opts] * @param {function} [opts.callback] - Optional validation callback. Receives the * final QoS profile after overrides are applied. Should return * `{successful: true}` or `{successful: false, reason: '...'}`. * @param {string} [opts.entityId] - Optional suffix to disambiguate multiple * publishers/subscriptions on the same topic. */ constructor(policyKinds, opts = {}) { this._policyKinds = Array.from(policyKinds); this._callback = opts.callback || null; this._entityId = opts.entityId || null; } get policyKinds() { return this._policyKinds; } get callback() { return this._callback; } get entityId() { return this._entityId; } /** * Create options that override history, depth, and reliability — * the most commonly tuned policies. * @param {Object} [opts] * @param {function} [opts.callback] - Validation callback. * @param {string} [opts.entityId] - Entity disambiguation suffix. * @returns {QoSOverridingOptions} */ static withDefaultPolicies(opts = {}) { return new QoSOverridingOptions( [QoSPolicyKind.HISTORY, QoSPolicyKind.DEPTH, QoSPolicyKind.RELIABILITY], opts ); } } /** * Resolve QoS profile string to a mutable QoS object. * If already a QoS instance, return as-is. * @param {QoS|string} qos * @returns {QoS} */ function _resolveQoS(qos) { if (qos instanceof QoS) { return qos; } // Plain object with QoS fields — construct a QoS from its properties if (typeof qos === 'object' && qos !== null && !Array.isArray(qos)) { return new QoS( qos.history, qos.depth, qos.reliability, qos.durability, qos.liveliness, qos.avoidRosNameSpaceConventions ); } // Profile string: create a QoS with the corresponding defaults // Values must match the rmw_qos_profile_* definitions in rmw/types.h switch (qos) { case QoS.profileDefault: return new QoS( QoS.HistoryPolicy.RMW_QOS_POLICY_HISTORY_KEEP_LAST, 10, QoS.ReliabilityPolicy.RMW_QOS_POLICY_RELIABILITY_RELIABLE, QoS.DurabilityPolicy.RMW_QOS_POLICY_DURABILITY_VOLATILE ); case QoS.profileSystemDefault: return new QoS( QoS.HistoryPolicy.RMW_QOS_POLICY_HISTORY_SYSTEM_DEFAULT, 0, QoS.ReliabilityPolicy.RMW_QOS_POLICY_RELIABILITY_SYSTEM_DEFAULT, QoS.DurabilityPolicy.RMW_QOS_POLICY_DURABILITY_SYSTEM_DEFAULT, QoS.LivelinessPolicy.RMW_QOS_POLICY_LIVELINESS_SYSTEM_DEFAULT ); case QoS.profileSensorData: return new QoS( QoS.HistoryPolicy.RMW_QOS_POLICY_HISTORY_KEEP_LAST, 5, QoS.ReliabilityPolicy.RMW_QOS_POLICY_RELIABILITY_BEST_EFFORT, QoS.DurabilityPolicy.RMW_QOS_POLICY_DURABILITY_VOLATILE ); case QoS.profileServicesDefault: return new QoS( QoS.HistoryPolicy.RMW_QOS_POLICY_HISTORY_KEEP_LAST, 10, QoS.ReliabilityPolicy.RMW_QOS_POLICY_RELIABILITY_RELIABLE, QoS.DurabilityPolicy.RMW_QOS_POLICY_DURABILITY_VOLATILE ); case QoS.profileParameters: return new QoS( QoS.HistoryPolicy.RMW_QOS_POLICY_HISTORY_KEEP_LAST, 1000, QoS.ReliabilityPolicy.RMW_QOS_POLICY_RELIABILITY_RELIABLE, QoS.DurabilityPolicy.RMW_QOS_POLICY_DURABILITY_VOLATILE ); case QoS.profileParameterEvents: return new QoS( QoS.HistoryPolicy.RMW_QOS_POLICY_HISTORY_KEEP_LAST, 1000, QoS.ReliabilityPolicy.RMW_QOS_POLICY_RELIABILITY_RELIABLE, QoS.DurabilityPolicy.RMW_QOS_POLICY_DURABILITY_VOLATILE ); case QoS.profileActionStatusDefault: return new QoS( QoS.HistoryPolicy.RMW_QOS_POLICY_HISTORY_KEEP_LAST, 1, QoS.ReliabilityPolicy.RMW_QOS_POLICY_RELIABILITY_RELIABLE, QoS.DurabilityPolicy.RMW_QOS_POLICY_DURABILITY_TRANSIENT_LOCAL ); default: return new QoS( QoS.HistoryPolicy.RMW_QOS_POLICY_HISTORY_KEEP_LAST, 10, QoS.ReliabilityPolicy.RMW_QOS_POLICY_RELIABILITY_RELIABLE, QoS.DurabilityPolicy.RMW_QOS_POLICY_DURABILITY_VOLATILE ); } } /** * Declare QoS override parameters on the node and apply any overrides * to the QoS profile in-place. * * @param {'publisher'|'subscription'} entityType * @param {Node} node * @param {string} topic - Fully resolved topic name. * @param {QoS} qos - Mutable QoS object (will be modified in-place). * @param {QoSOverridingOptions} options */ function declareQosParameters(entityType, node, topic, qos, options) { if (!options || options.policyKinds.length === 0) { return; } const idSuffix = options.entityId ? `_${options.entityId}` : ''; const namePrefix = `qos_overrides.${topic}.${entityType}${idSuffix}`; for (const policyKind of options.policyKinds) { const mapping = POLICY_MAP[policyKind]; if (!mapping) { continue; } const paramName = `${namePrefix}.${mapping.paramKey}`; const currentValue = qos[mapping.qosProp]; const paramValue = mapping.toParam(currentValue, mapping.enumObj); const descriptor = new ParameterDescriptor( paramName, mapping.paramType, `QoS override for ${mapping.qosProp}`, true // readOnly ); let param; try { param = node.declareParameter( new Parameter(paramName, mapping.paramType, paramValue), descriptor ); } catch (e) { // Already declared (e.g. multiple entities on same topic) — reuse if (node.hasParameter(paramName)) { param = node.getParameter(paramName); } else { throw e; } } // Apply the (possibly overridden) parameter value back to QoS if (param && param.value !== paramValue) { qos[mapping.qosProp] = mapping.fromParam(param.value, mapping.enumObj); } } // Run validation callback if provided if (options.callback) { const result = options.callback(qos); if (result && !result.successful) { throw new Error( `QoS override validation failed: ${result.reason || 'unknown reason'}` ); } } } module.exports = { QoSPolicyKind, QoSOverridingOptions, declareQosParameters, _resolveQoS, };