• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

RobotWebTools / rclnodejs / 23940401301

03 Apr 2026 08:47AM UTC coverage: 85.411% (-0.4%) from 85.83%
23940401301

push

github

minggangw
Add ParameterEventHandler node filtering support (#1474)

- add `ParameterEventHandler.configureNodesFilter(nodeNames?)` to apply or clear `/parameter_events` subscription content filters for selected nodes
- resolve relative node names against the handler node namespace
- reuse `Node.getFullyQualifiedName()` for handler node fully qualified name resolution instead of duplicating that logic
- add TypeScript declarations for `configureNodesFilter()`
- add focused `ParameterEventHandler` tests for:
  - absolute node filters
  - relative node name resolution
  - clearing filters when `nodeNames` is omitted
  - clearing filters when `nodeNames` is empty
  - invalid `nodeNames` validation

Testing:
- `npx mocha test/test-parameter-event-handler.js`
- `npx tsd`

Fix: #1473

1565 of 1992 branches covered (78.56%)

Branch coverage included in aggregate %.

31 of 32 new or added lines in 1 file covered. (96.88%)

59 existing lines in 1 file now uncovered.

3177 of 3560 relevant lines covered (89.24%)

445.38 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

87.0
/lib/node.js
1
// Copyright (c) 2017 Intel Corporation. All rights reserved.
2
//
3
// Licensed under the Apache License, Version 2.0 (the "License");
4
// you may not use this file except in compliance with the License.
5
// You may obtain a copy of the License at
6
//
7
//     http://www.apache.org/licenses/LICENSE-2.0
8
//
9
// Unless required by applicable law or agreed to in writing, software
10
// distributed under the License is distributed on an "AS IS" BASIS,
11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
// See the License for the specific language governing permissions and
13
// limitations under the License.
14

15
'use strict';
16

17
const rclnodejs = require('./native_loader.js');
26✔
18

19
const ActionInterfaces = require('./action/interfaces.js');
26✔
20
const Client = require('./client.js');
26✔
21
const Clock = require('./clock.js');
26✔
22
const Context = require('./context.js');
26✔
23
const debug = require('debug')('rclnodejs:node');
26✔
24
const DistroUtils = require('./distro.js');
26✔
25
const GuardCondition = require('./guard_condition.js');
26✔
26
const loader = require('./interface_loader.js');
26✔
27
const Logging = require('./logging.js');
26✔
28
const NodeOptions = require('./node_options.js');
26✔
29
const {
30
  ParameterType,
31
  Parameter,
32
  ParameterDescriptor,
33
} = require('./parameter.js');
26✔
34
const { isValidSerializationMode } = require('./message_serialization.js');
26✔
35
const {
36
  TypeValidationError,
37
  RangeValidationError,
38
  ValidationError,
39
} = require('./errors.js');
26✔
40
const ParameterService = require('./parameter_service.js');
26✔
41
const ParameterClient = require('./parameter_client.js');
26✔
42
const ParameterWatcher = require('./parameter_watcher.js');
26✔
43
const ParameterEventHandler = require('./parameter_event_handler.js');
26✔
44
const Publisher = require('./publisher.js');
26✔
45
const QoS = require('./qos.js');
26✔
46
const Rates = require('./rate.js');
26✔
47
const Service = require('./service.js');
26✔
48
const Subscription = require('./subscription.js');
26✔
49
const ObservableSubscription = require('./observable_subscription.js');
26✔
50
const MessageInfo = require('./message_info.js');
26✔
51
const {
52
  declareQosParameters,
53
  _resolveQoS,
54
} = require('./qos_overriding_options.js');
26✔
55
const TimeSource = require('./time_source.js');
26✔
56
const Timer = require('./timer.js');
26✔
57
const TypeDescriptionService = require('./type_description_service.js');
26✔
58
const Entity = require('./entity.js');
26✔
59
const { SubscriptionEventCallbacks } = require('../lib/event_handler.js');
26✔
60
const { PublisherEventCallbacks } = require('../lib/event_handler.js');
26✔
61
const { validateFullTopicName } = require('./validator.js');
26✔
62

63
// Parameter event publisher constants
64
const PARAMETER_EVENT_MSG_TYPE = 'rcl_interfaces/msg/ParameterEvent';
26✔
65
const PARAMETER_EVENT_TOPIC = 'parameter_events';
26✔
66

67
/**
68
 * @class - Class representing a Node in ROS
69
 */
70

71
class Node extends rclnodejs.ShadowNode {
72
  /**
73
   * Create a ROS2Node.
74
   * model using the {@link https://github.com/ros2/rcl/tree/master/rcl_lifecycle|ros2 client library (rcl) lifecyle api}.
75
   * @param {string} nodeName - The name used to register in ROS.
76
   * @param {string} [namespace=''] - The namespace used in ROS.
77
   * @param {Context} [context=Context.defaultContext()] - The context to create the node in.
78
   * @param {NodeOptions} [options=NodeOptions.defaultOptions] - The options to configure the new node behavior.
79
   * @throws {Error} If the given context is not registered.
80
   */
81
  constructor(
82
    nodeName,
83
    namespace = '',
45✔
84
    context = Context.defaultContext(),
82✔
85
    options = NodeOptions.defaultOptions,
82✔
86
    args = [],
104✔
87
    useGlobalArguments = true
104✔
88
  ) {
89
    super();
953✔
90

91
    if (typeof nodeName !== 'string') {
953✔
92
      throw new TypeValidationError('nodeName', nodeName, 'string');
12✔
93
    }
94
    if (typeof namespace !== 'string') {
941✔
95
      throw new TypeValidationError('namespace', namespace, 'string');
10✔
96
    }
97

98
    this._init(nodeName, namespace, options, context, args, useGlobalArguments);
931✔
99
    debug(
920✔
100
      'Finish initializing node, name = %s and namespace = %s.',
101
      nodeName,
102
      namespace
103
    );
104
  }
105

106
  static _normalizeOptions(options) {
107
    if (options instanceof NodeOptions) {
931✔
108
      return options;
928✔
109
    }
110
    const defaults = NodeOptions.defaultOptions;
3✔
111
    return {
3✔
112
      startParameterServices:
113
        options.startParameterServices ?? defaults.startParameterServices,
5✔
114
      parameterOverrides:
115
        options.parameterOverrides ?? defaults.parameterOverrides,
5✔
116
      automaticallyDeclareParametersFromOverrides:
117
        options.automaticallyDeclareParametersFromOverrides ??
6✔
118
        defaults.automaticallyDeclareParametersFromOverrides,
119
      startTypeDescriptionService:
120
        options.startTypeDescriptionService ??
6✔
121
        defaults.startTypeDescriptionService,
122
      enableRosout: options.enableRosout ?? defaults.enableRosout,
5✔
123
      rosoutQos: options.rosoutQos ?? defaults.rosoutQos,
6✔
124
    };
125
  }
126

127
  _init(name, namespace, options, context, args, useGlobalArguments) {
128
    options = Node._normalizeOptions(options);
931✔
129

130
    this.handle = rclnodejs.createNode(
931✔
131
      name,
132
      namespace,
133
      context.handle,
134
      args,
135
      useGlobalArguments,
136
      options.rosoutQos
137
    );
138
    Object.defineProperty(this, 'handle', {
921✔
139
      configurable: false,
140
      writable: false,
141
    }); // make read-only
142

143
    this._context = context;
921✔
144
    this.context.onNodeCreated(this);
921✔
145

146
    this._publishers = [];
921✔
147
    this._subscriptions = [];
921✔
148
    this._clients = [];
921✔
149
    this._services = [];
921✔
150
    this._timers = [];
921✔
151
    this._guards = [];
921✔
152
    this._events = [];
921✔
153
    this._actionClients = [];
921✔
154
    this._actionServers = [];
921✔
155
    this._parameterClients = [];
921✔
156
    this._parameterWatchers = [];
921✔
157
    this._parameterEventHandlers = [];
921✔
158
    this._rateTimerServer = null;
921✔
159
    this._parameterDescriptors = new Map();
921✔
160
    this._parameters = new Map();
921✔
161
    this._parameterService = null;
921✔
162
    this._typeDescriptionService = null;
921✔
163
    this._parameterEventPublisher = null;
921✔
164
    this._preSetParametersCallbacks = [];
921✔
165
    this._setParametersCallbacks = [];
921✔
166
    this._postSetParametersCallbacks = [];
921✔
167
    this._logger = new Logging(rclnodejs.getNodeLoggerName(this.handle));
921✔
168
    this._spinning = false;
921✔
169
    this._enableRosout = options.enableRosout;
921✔
170

171
    if (this._enableRosout) {
921✔
172
      rclnodejs.initRosoutPublisherForNode(this.handle);
919✔
173
    }
174

175
    this._parameterEventPublisher = this.createPublisher(
921✔
176
      PARAMETER_EVENT_MSG_TYPE,
177
      PARAMETER_EVENT_TOPIC
178
    );
179

180
    // initialize _parameterOverrides from parameters defined on the commandline
181
    this._parameterOverrides = this._getNativeParameterOverrides();
921✔
182

183
    // override cli parameterOverrides with those specified in options
184
    if (options.parameterOverrides.length > 0) {
921✔
185
      for (const parameter of options.parameterOverrides) {
12✔
186
        if (!(parameter instanceof Parameter)) {
17✔
187
          throw new TypeValidationError(
1✔
188
            'parameterOverride',
189
            parameter,
190
            'Parameter instance',
191
            {
192
              nodeName: name,
193
            }
194
          );
195
        }
196
        this._parameterOverrides.set(parameter.name, parameter);
16✔
197
      }
198
    }
199

200
    // initialize _parameters from parameterOverrides
201
    if (options.automaticallyDeclareParametersFromOverrides) {
920✔
202
      for (const parameter of this._parameterOverrides.values()) {
5✔
203
        parameter.validate();
10✔
204
        const descriptor = ParameterDescriptor.fromParameter(parameter);
10✔
205
        this._parameters.set(parameter.name, parameter);
10✔
206
        this._parameterDescriptors.set(parameter.name, descriptor);
10✔
207
      }
208
    }
209

210
    // Clock that has support for ROS time.
211
    // Note: parameter overrides and parameter event publisher need to be ready at this point
212
    // to be able to declare 'use_sim_time' if it was not declared yet.
213
    this._clock = new Clock.ROSClock();
920✔
214
    this._timeSource = new TimeSource(this);
920✔
215
    this._timeSource.attachClock(this._clock);
920✔
216

217
    if (options.startParameterServices) {
920✔
218
      this._parameterService = new ParameterService(this);
913✔
219
      this._parameterService.start();
913✔
220
    }
221

222
    if (
920!
223
      DistroUtils.getDistroId() >= DistroUtils.getDistroId('jazzy') &&
1,840✔
224
      options.startTypeDescriptionService
225
    ) {
226
      this._typeDescriptionService = new TypeDescriptionService(this);
920✔
227
      this._typeDescriptionService.start();
920✔
228
    }
229
  }
230

231
  execute(handles) {
232
    let timersReady = this._timers.filter((timer) =>
4,630✔
233
      handles.includes(timer.handle)
3,839✔
234
    );
235
    let guardsReady = this._guards.filter((guard) =>
4,630✔
236
      handles.includes(guard.handle)
3✔
237
    );
238
    let subscriptionsReady = this._subscriptions.filter((subscription) =>
4,630✔
239
      handles.includes(subscription.handle)
418✔
240
    );
241
    let clientsReady = this._clients.filter((client) =>
4,630✔
242
      handles.includes(client.handle)
171✔
243
    );
244
    let servicesReady = this._services.filter((service) =>
4,630✔
245
      handles.includes(service.handle)
14,510✔
246
    );
247
    let actionClientsReady = this._actionClients.filter((actionClient) =>
4,630✔
248
      handles.includes(actionClient.handle)
205✔
249
    );
250
    let actionServersReady = this._actionServers.filter((actionServer) =>
4,630✔
251
      handles.includes(actionServer.handle)
203✔
252
    );
253
    let eventsReady = this._events.filter((event) =>
4,630✔
254
      handles.includes(event.handle)
4✔
255
    );
256

257
    timersReady.forEach((timer) => {
4,630✔
258
      if (timer.isReady()) {
3,830!
259
        let timerInfo;
260
        if (typeof rclnodejs.callTimerWithInfo === 'function') {
3,830✔
261
          timerInfo = rclnodejs.callTimerWithInfo(timer.handle);
3,829✔
262
          timer.callback(timerInfo);
3,829✔
263
        } else {
264
          rclnodejs.callTimer(timer.handle);
1✔
265
          timer.callback();
1✔
266
        }
267
      }
268
    });
269

270
    eventsReady.forEach((event) => {
4,630✔
271
      event.takeData();
4✔
272
    });
273

274
    for (const subscription of subscriptionsReady) {
4,630✔
275
      if (subscription.isDestroyed()) continue;
399✔
276
      if (subscription.isRaw) {
390✔
277
        let rawMessage = rclnodejs.rclTakeRaw(subscription.handle);
1✔
278
        if (rawMessage) {
1!
279
          subscription.processResponse(rawMessage);
1✔
280
        }
281
        continue;
1✔
282
      }
283

284
      this._runWithMessageType(
389✔
285
        subscription.typeClass,
286
        (message, deserialize) => {
287
          if (subscription.wantsMessageInfo) {
389✔
288
            let rawInfo = rclnodejs.rclTakeWithInfo(
4✔
289
              subscription.handle,
290
              message
291
            );
292
            if (rawInfo) {
4!
293
              subscription.processResponse(
4✔
294
                deserialize(),
295
                new MessageInfo(rawInfo)
296
              );
297
            }
298
          } else {
299
            let success = rclnodejs.rclTake(subscription.handle, message);
385✔
300
            if (success) {
385✔
301
              subscription.processResponse(deserialize());
382✔
302
            }
303
          }
304
        }
305
      );
306
    }
307

308
    for (const guard of guardsReady) {
4,630✔
309
      if (guard.isDestroyed()) continue;
3!
310

311
      guard.callback();
3✔
312
    }
313

314
    for (const client of clientsReady) {
4,630✔
315
      if (client.isDestroyed()) continue;
87!
316
      this._runWithMessageType(
87✔
317
        client.typeClass.Response,
318
        (message, deserialize) => {
319
          let sequenceNumber = rclnodejs.rclTakeResponse(
87✔
320
            client.handle,
321
            message
322
          );
323
          if (sequenceNumber !== undefined) {
87✔
324
            client.processResponse(sequenceNumber, deserialize());
86✔
325
          }
326
        }
327
      );
328
    }
329

330
    for (const service of servicesReady) {
4,630✔
331
      if (service.isDestroyed()) continue;
124!
332
      this._runWithMessageType(
124✔
333
        service.typeClass.Request,
334
        (message, deserialize) => {
335
          let header = rclnodejs.rclTakeRequest(
124✔
336
            service.handle,
337
            this.handle,
338
            message
339
          );
340
          if (header) {
124✔
341
            service.processRequest(header, deserialize());
91✔
342
          }
343
        }
344
      );
345
    }
346

347
    for (const actionClient of actionClientsReady) {
4,630✔
348
      if (actionClient.isDestroyed()) continue;
126!
349

350
      const properties = actionClient.handle.properties;
126✔
351

352
      if (properties.isGoalResponseReady) {
126✔
353
        this._runWithMessageType(
56✔
354
          actionClient.typeClass.impl.SendGoalService.Response,
355
          (message, deserialize) => {
356
            let sequence = rclnodejs.actionTakeGoalResponse(
56✔
357
              actionClient.handle,
358
              message
359
            );
360
            if (sequence != undefined) {
56✔
361
              actionClient.processGoalResponse(sequence, deserialize());
49✔
362
            }
363
          }
364
        );
365
      }
366

367
      if (properties.isCancelResponseReady) {
126✔
368
        this._runWithMessageType(
5✔
369
          actionClient.typeClass.impl.CancelGoal.Response,
370
          (message, deserialize) => {
371
            let sequence = rclnodejs.actionTakeCancelResponse(
5✔
372
              actionClient.handle,
373
              message
374
            );
375
            if (sequence != undefined) {
5!
376
              actionClient.processCancelResponse(sequence, deserialize());
5✔
377
            }
378
          }
379
        );
380
      }
381

382
      if (properties.isResultResponseReady) {
126✔
383
        this._runWithMessageType(
32✔
384
          actionClient.typeClass.impl.GetResultService.Response,
385
          (message, deserialize) => {
386
            let sequence = rclnodejs.actionTakeResultResponse(
32✔
387
              actionClient.handle,
388
              message
389
            );
390
            if (sequence != undefined) {
32!
391
              actionClient.processResultResponse(sequence, deserialize());
32✔
392
            }
393
          }
394
        );
395
      }
396

397
      if (properties.isFeedbackReady) {
126✔
398
        this._runWithMessageType(
9✔
399
          actionClient.typeClass.impl.FeedbackMessage,
400
          (message, deserialize) => {
401
            let success = rclnodejs.actionTakeFeedback(
9✔
402
              actionClient.handle,
403
              message
404
            );
405
            if (success) {
9!
406
              actionClient.processFeedbackMessage(deserialize());
9✔
407
            }
408
          }
409
        );
410
      }
411

412
      if (properties.isStatusReady) {
126✔
413
        this._runWithMessageType(
73✔
414
          actionClient.typeClass.impl.GoalStatusArray,
415
          (message, deserialize) => {
416
            let success = rclnodejs.actionTakeStatus(
73✔
417
              actionClient.handle,
418
              message
419
            );
420
            if (success) {
73!
421
              actionClient.processStatusMessage(deserialize());
73✔
422
            }
423
          }
424
        );
425
      }
426
    }
427

428
    for (const actionServer of actionServersReady) {
4,630✔
429
      if (actionServer.isDestroyed()) continue;
95!
430

431
      const properties = actionServer.handle.properties;
95✔
432

433
      if (properties.isGoalRequestReady) {
95✔
434
        this._runWithMessageType(
54✔
435
          actionServer.typeClass.impl.SendGoalService.Request,
436
          (message, deserialize) => {
437
            const result = rclnodejs.actionTakeGoalRequest(
54✔
438
              actionServer.handle,
439
              message
440
            );
441
            if (result) {
54✔
442
              actionServer.processGoalRequest(result, deserialize());
49✔
443
            }
444
          }
445
        );
446
      }
447

448
      if (properties.isCancelRequestReady) {
95✔
449
        this._runWithMessageType(
5✔
450
          actionServer.typeClass.impl.CancelGoal.Request,
451
          (message, deserialize) => {
452
            const result = rclnodejs.actionTakeCancelRequest(
5✔
453
              actionServer.handle,
454
              message
455
            );
456
            if (result) {
5!
457
              actionServer.processCancelRequest(result, deserialize());
5✔
458
            }
459
          }
460
        );
461
      }
462

463
      if (properties.isResultRequestReady) {
95✔
464
        this._runWithMessageType(
32✔
465
          actionServer.typeClass.impl.GetResultService.Request,
466
          (message, deserialize) => {
467
            const result = rclnodejs.actionTakeResultRequest(
32✔
468
              actionServer.handle,
469
              message
470
            );
471
            if (result) {
32!
472
              actionServer.processResultRequest(result, deserialize());
32✔
473
            }
474
          }
475
        );
476
      }
477

478
      if (properties.isGoalExpired) {
95✔
479
        let numGoals = actionServer._goalHandles.size;
4✔
480
        if (numGoals > 0) {
4!
481
          let GoalInfoArray = ActionInterfaces.GoalInfo.ArrayType;
4✔
482
          let message = new GoalInfoArray(numGoals);
4✔
483
          let count = rclnodejs.actionExpireGoals(
4✔
484
            actionServer.handle,
485
            numGoals,
486
            message._refArray.buffer
487
          );
488
          if (count > 0) {
4!
489
            actionServer.processGoalExpired(message, count);
4✔
490
          }
491
          GoalInfoArray.freeArray(message);
4✔
492
        }
493
      }
494
    }
495

496
    // At this point it is safe to clear the cache of any
497
    // destroyed entity references
498
    Entity._gcHandles();
4,630✔
499
  }
500

501
  /**
502
   * Determine if this node is spinning.
503
   * @returns {boolean} - true when spinning; otherwise returns false.
504
   */
505
  get spinning() {
506
    return this._spinning;
4,662✔
507
  }
508

509
  /**
510
   * Trigger the event loop to continuously check for and route.
511
   * incoming events.
512
   * @param {Node} node - The node to be spun up.
513
   * @param {number} [timeout=10] - Timeout to wait in milliseconds. Block forever if negative. Don't wait if 0.
514
   * @throws {Error} If the node is already spinning.
515
   * @return {undefined}
516
   */
517
  spin(timeout = 10) {
26✔
518
    if (this.spinning) {
707!
UNCOV
519
      throw new Error('The node is already spinning.');
×
520
    }
521
    this.start(this.context.handle, timeout);
707✔
522
    this._spinning = true;
707✔
523
  }
524

525
  /**
526
   * Use spin().
527
   * @deprecated, since 0.18.0
528
   */
529
  startSpinning(timeout) {
UNCOV
530
    this.spin(timeout);
×
531
  }
532

533
  /**
534
   * Terminate spinning - no further events will be received.
535
   * @returns {undefined}
536
   */
537
  stop() {
538
    super.stop();
707✔
539
    this._spinning = false;
707✔
540
  }
541

542
  /**
543
   * Terminate spinning - no further events will be received.
544
   * @returns {undefined}
545
   * @deprecated since 0.18.0, Use stop().
546
   */
547
  stopSpinning() {
UNCOV
548
    super.stop();
×
UNCOV
549
    this._spinning = false;
×
550
  }
551

552
  /**
553
   * Spin the node and trigger the event loop to check for one incoming event. Thereafter the node
554
   * will not received additional events until running additional calls to spin() or spinOnce().
555
   * @param {Node} node - The node to be spun.
556
   * @param {number} [timeout=10] - Timeout to wait in milliseconds. Block forever if negative. Don't wait if 0.
557
   * @throws {Error} If the node is already spinning.
558
   * @return {undefined}
559
   */
560
  spinOnce(timeout = 10) {
2✔
561
    if (this.spinning) {
3,009✔
562
      throw new Error('The node is already spinning.');
2✔
563
    }
564
    super.spinOnce(this.context.handle, timeout);
3,007✔
565
  }
566

567
  _removeEntityFromArray(entity, array) {
568
    let index = array.indexOf(entity);
398✔
569
    if (index > -1) {
398✔
570
      array.splice(index, 1);
396✔
571
    }
572
  }
573

574
  _destroyEntity(entity, array, syncHandles = true) {
338✔
575
    if (entity['isDestroyed'] && entity.isDestroyed()) return;
350✔
576

577
    this._removeEntityFromArray(entity, array);
342✔
578
    if (syncHandles) {
342✔
579
      this.syncHandles();
332✔
580
    }
581

582
    if (entity['_destroy']) {
342✔
583
      entity._destroy();
336✔
584
    } else {
585
      // guards and timers
586
      entity.handle.release();
6✔
587
    }
588
  }
589

590
  _validateOptions(options) {
591
    if (
8,485✔
592
      options !== undefined &&
8,589✔
593
      (options === null || typeof options !== 'object')
594
    ) {
595
      throw new TypeValidationError('options', options, 'object', {
20✔
596
        nodeName: this.name(),
597
      });
598
    }
599

600
    if (options === undefined) {
8,465✔
601
      return Node.getDefaultOptions();
8,423✔
602
    }
603

604
    if (options.enableTypedArray === undefined) {
42✔
605
      options = Object.assign(options, { enableTypedArray: true });
28✔
606
    }
607

608
    if (options.qos === undefined) {
42✔
609
      options = Object.assign(options, { qos: QoS.profileDefault });
20✔
610
    }
611

612
    if (options.isRaw === undefined) {
42✔
613
      options = Object.assign(options, { isRaw: false });
31✔
614
    }
615

616
    if (options.serializationMode === undefined) {
42✔
617
      options = Object.assign(options, { serializationMode: 'default' });
25✔
618
    } else if (!isValidSerializationMode(options.serializationMode)) {
17✔
619
      throw new ValidationError(
1✔
620
        `Invalid serializationMode: ${options.serializationMode}. Valid modes are: 'default', 'plain', 'json'`,
621
        {
622
          code: 'INVALID_SERIALIZATION_MODE',
623
          argumentName: 'serializationMode',
624
          providedValue: options.serializationMode,
625
          expectedType: "'default' | 'plain' | 'json'",
626
          nodeName: this.name(),
627
        }
628
      );
629
    }
630

631
    return options;
41✔
632
  }
633

634
  /**
635
   * Create a Timer.
636
   * @param {bigint} period - The number representing period in nanoseconds.
637
   * @param {function} callback - The callback to be called when the timer fires.
638
   *   On distros with native support, the callback receives a `TimerInfo` object
639
   *   describing the expected and actual call time.
640
   * @param {object|Clock} [optionsOrClock] - Timer options or the clock which the timer gets time from.
641
   *   Supported options: `{ autostart?: boolean }`.
642
   * @param {Clock} [clock] - The clock which the timer gets time from when options are provided.
643
   * @return {Timer} - An instance of Timer.
644
   */
645
  createTimer(period, callback, optionsOrClock = null, clock = null) {
144✔
646
    let options = {};
73✔
647

648
    if (optionsOrClock instanceof Clock.Clock) {
73!
UNCOV
649
      clock = optionsOrClock;
×
650
    } else if (optionsOrClock === null || optionsOrClock === undefined) {
73✔
651
      // Keep the 4th argument as the clock when the 3rd argument is omitted or explicitly null.
652
    } else {
653
      if (typeof optionsOrClock !== 'object' || Array.isArray(optionsOrClock)) {
2!
UNCOV
654
        throw new TypeValidationError(
×
655
          'options',
656
          optionsOrClock,
657
          'object or Clock',
658
          {
659
            nodeName: this.name(),
660
          }
661
        );
662
      }
663
      options = optionsOrClock;
2✔
664
    }
665

666
    if (
73!
667
      arguments.length === 4 &&
73!
668
      clock !== null &&
669
      !(clock instanceof Clock.Clock)
670
    ) {
UNCOV
671
      throw new TypeValidationError('clock', clock, 'Clock', {
×
672
        nodeName: this.name(),
673
      });
674
    }
675

676
    if (typeof period !== 'bigint') {
73✔
677
      throw new TypeValidationError('period', period, 'bigint', {
1✔
678
        nodeName: this.name(),
679
      });
680
    }
681
    if (typeof callback !== 'function') {
72✔
682
      throw new TypeValidationError('callback', callback, 'function', {
1✔
683
        nodeName: this.name(),
684
      });
685
    }
686
    if (
71✔
687
      options.autostart !== undefined &&
73✔
688
      typeof options.autostart !== 'boolean'
689
    ) {
690
      throw new TypeValidationError(
1✔
691
        'options.autostart',
692
        options.autostart,
693
        'boolean',
694
        {
695
          nodeName: this.name(),
696
        }
697
      );
698
    }
699

700
    const timerClock = clock || this._clock;
70✔
701
    const autostart = options.autostart ?? true;
70✔
702
    let timerHandle = rclnodejs.createTimer(
70✔
703
      timerClock.handle,
704
      this.context.handle,
705
      period,
706
      autostart
707
    );
708
    let timer = new Timer(timerHandle, period, callback);
70✔
709
    debug('Finish creating timer, period = %d.', period);
70✔
710
    this._timers.push(timer);
70✔
711
    this.syncHandles();
70✔
712

713
    return timer;
70✔
714
  }
715

716
  /**
717
   * Create a Rate.
718
   *
719
   * @param {number} hz - The frequency of the rate timer; default is 1 hz.
720
   * @returns {Promise<Rate>} - Promise resolving to new instance of Rate.
721
   */
722
  async createRate(hz = 1) {
4✔
723
    if (typeof hz !== 'number') {
9!
UNCOV
724
      throw new TypeValidationError('hz', hz, 'number', {
×
725
        nodeName: this.name(),
726
      });
727
    }
728

729
    const MAX_RATE_HZ_IN_MILLISECOND = 1000.0;
9✔
730
    if (hz <= 0.0 || hz > MAX_RATE_HZ_IN_MILLISECOND) {
9✔
731
      throw new RangeValidationError(
2✔
732
        'hz',
733
        hz,
734
        `0.0 < hz <= ${MAX_RATE_HZ_IN_MILLISECOND}`,
735
        {
736
          nodeName: this.name(),
737
        }
738
      );
739
    }
740

741
    // lazy initialize rateTimerServer
742
    if (!this._rateTimerServer) {
7✔
743
      this._rateTimerServer = new Rates.RateTimerServer(this);
5✔
744
      await this._rateTimerServer.init();
5✔
745
    }
746

747
    const period = Math.round(1000 / hz);
7✔
748
    const timer = this._rateTimerServer.createTimer(BigInt(period) * 1000000n);
7✔
749
    const rate = new Rates.Rate(hz, timer);
7✔
750

751
    return rate;
7✔
752
  }
753

754
  /**
755
   * Create a Publisher.
756
   * @param {function|string|object} typeClass - The ROS message class,
757
        OR a string representing the message class, e.g. 'std_msgs/msg/String',
758
        OR an object representing the message class, e.g. {package: 'std_msgs', type: 'msg', name: 'String'}
759
   * @param {string} topic - The name of the topic.
760
   * @param {object} options - The options argument used to parameterize the publisher.
761
   * @param {boolean} options.enableTypedArray - The topic will use TypedArray if necessary, default: true.
762
   * @param {QoS} options.qos - ROS Middleware "quality of service" settings for the publisher, default: QoS.profileDefault.
763
   * @param {QoSOverridingOptions} [options.qosOverridingOptions] - If provided, declares read-only ROS parameters
764
   *  for the specified QoS policies (e.g. `qos_overrides./topic.publisher.depth`). These can be overridden at
765
   *  startup via `--ros-args -p` or `--params-file`. If qos is a profile string, it will be resolved to a
766
   *  mutable QoS object before overrides are applied.
767
   * @param {PublisherEventCallbacks} eventCallbacks - The event callbacks for the publisher.
768
   * @return {Publisher} - An instance of Publisher.
769
   */
770
  createPublisher(typeClass, topic, options, eventCallbacks) {
771
    return this._createPublisher(
1,300✔
772
      typeClass,
773
      topic,
774
      options,
775
      Publisher,
776
      eventCallbacks
777
    );
778
  }
779

780
  _createPublisher(typeClass, topic, options, publisherClass, eventCallbacks) {
781
    if (typeof typeClass === 'string' || typeof typeClass === 'object') {
1,303✔
782
      typeClass = loader.loadInterface(typeClass);
1,279✔
783
    }
784
    options = this._validateOptions(options);
1,296✔
785

786
    if (typeof typeClass !== 'function') {
1,296✔
787
      throw new TypeValidationError('typeClass', typeClass, 'function', {
8✔
788
        nodeName: this.name(),
789
        entityType: 'publisher',
790
      });
791
    }
792
    if (typeof topic !== 'string') {
1,288✔
793
      throw new TypeValidationError('topic', topic, 'string', {
12✔
794
        nodeName: this.name(),
795
        entityType: 'publisher',
796
      });
797
    }
798
    if (
1,276!
799
      eventCallbacks &&
1,278✔
800
      !(eventCallbacks instanceof PublisherEventCallbacks)
801
    ) {
UNCOV
802
      throw new TypeValidationError(
×
803
        'eventCallbacks',
804
        eventCallbacks,
805
        'PublisherEventCallbacks',
806
        {
807
          nodeName: this.name(),
808
          entityType: 'publisher',
809
          entityName: topic,
810
        }
811
      );
812
    }
813

814
    // Apply QoS overriding options if provided
815
    if (options.qosOverridingOptions) {
1,276✔
816
      const resolvedTopic = this.resolveTopicName(topic);
5✔
817
      if (typeof options.qos === 'string' || !(options.qos instanceof QoS)) {
5✔
818
        options.qos = _resolveQoS(options.qos);
2✔
819
      }
820
      declareQosParameters(
5✔
821
        'publisher',
822
        this,
823
        resolvedTopic,
824
        options.qos,
825
        options.qosOverridingOptions
826
      );
827
    }
828

829
    let publisher = publisherClass.createPublisher(
1,275✔
830
      this,
831
      typeClass,
832
      topic,
833
      options,
834
      eventCallbacks
835
    );
836
    debug('Finish creating publisher, topic = %s.', topic);
1,265✔
837
    this._publishers.push(publisher);
1,265✔
838
    return publisher;
1,265✔
839
  }
840

841
  /**
842
   * This callback is called when a message is published
843
   * @callback SubscriptionCallback
844
   * @param {Object} message - The message published
845
   * @see [Node.createSubscription]{@link Node#createSubscription}
846
   * @see [Node.createPublisher]{@link Node#createPublisher}
847
   * @see {@link Publisher}
848
   * @see {@link Subscription}
849
   */
850

851
  /**
852
   * Create a Subscription with optional content-filtering.
853
   * @param {function|string|object} typeClass - The ROS message class,
854
        OR a string representing the message class, e.g. 'std_msgs/msg/String',
855
        OR an object representing the message class, e.g. {package: 'std_msgs', type: 'msg', name: 'String'}
856
   * @param {string} topic - The name of the topic.
857
   * @param {object} options - The options argument used to parameterize the subscription.
858
   * @param {boolean} options.enableTypedArray - The topic will use TypedArray if necessary, default: true.
859
   * @param {QoS} options.qos - ROS Middleware "quality of service" settings for the subscription, default: QoS.profileDefault.
860
   * @param {boolean} options.isRaw - The topic is serialized when true, default: false.
861
   * @param {string} [options.serializationMode='default'] - Controls message serialization format:
862
   *  'default': Use native rclnodejs behavior (respects enableTypedArray setting),
863
   *  'plain': Convert TypedArrays to regular arrays,
864
   *  'json': Fully JSON-safe (handles TypedArrays, BigInt, etc.).
865
   * @param {object} [options.contentFilter=undefined] - The content-filter, default: undefined.
866
   *  Confirm that your RMW supports content-filtered topics before use. 
867
   * @param {string} options.contentFilter.expression - Specifies the criteria to select the data samples of
868
   *  interest. It is similar to the WHERE part of an SQL clause.
869
   * @param {string[]} [options.contentFilter.parameters=undefined] - Array of strings that give values to
870
   *  the ‘parameters’ (i.e., "%n" tokens) in the filter_expression. The number of supplied parameters must
871
   *  fit with the requested values in the filter_expression (i.e., the number of %n tokens). default: undefined.
872
   * @param {QoSOverridingOptions} [options.qosOverridingOptions] - If provided, declares read-only ROS parameters
873
   *  for the specified QoS policies (e.g. `qos_overrides./topic.subscription.depth`). These can be overridden at
874
   *  startup via `--ros-args -p` or `--params-file`. If qos is a profile string, it will be resolved to a
875
   *  mutable QoS object before overrides are applied.
876
   * @param {SubscriptionCallback} callback - The callback to be call when receiving the topic subscribed. The topic will be an instance of null-terminated Buffer when options.isRaw is true.
877
   * @param {SubscriptionEventCallbacks} eventCallbacks - The event callbacks for the subscription.
878
   * @return {Subscription} - An instance of Subscription.
879
   * @throws {ERROR} - May throw an RMW error if content-filter is malformed. 
880
   * @see {@link SubscriptionCallback}
881
   * @see {@link https://www.omg.org/spec/DDS/1.4/PDF|Content-filter details at DDS 1.4 specification, Annex B}
882
   */
883
  createSubscription(typeClass, topic, options, callback, eventCallbacks) {
884
    if (typeof typeClass === 'string' || typeof typeClass === 'object') {
467✔
885
      typeClass = loader.loadInterface(typeClass);
458✔
886
    }
887

888
    if (typeof options === 'function') {
460✔
889
      callback = options;
343✔
890
      options = undefined;
343✔
891
    }
892
    options = this._validateOptions(options);
460✔
893

894
    if (typeof typeClass !== 'function') {
450✔
895
      throw new TypeValidationError('typeClass', typeClass, 'function', {
4✔
896
        nodeName: this.name(),
897
        entityType: 'subscription',
898
      });
899
    }
900
    if (typeof topic !== 'string') {
446✔
901
      throw new TypeValidationError('topic', topic, 'string', {
6✔
902
        nodeName: this.name(),
903
        entityType: 'subscription',
904
      });
905
    }
906
    if (typeof callback !== 'function') {
440!
UNCOV
907
      throw new TypeValidationError('callback', callback, 'function', {
×
908
        nodeName: this.name(),
909
        entityType: 'subscription',
910
        entityName: topic,
911
      });
912
    }
913
    if (
440!
914
      eventCallbacks &&
444✔
915
      !(eventCallbacks instanceof SubscriptionEventCallbacks)
916
    ) {
UNCOV
917
      throw new TypeValidationError(
×
918
        'eventCallbacks',
919
        eventCallbacks,
920
        'SubscriptionEventCallbacks',
921
        {
922
          nodeName: this.name(),
923
          entityType: 'subscription',
924
          entityName: topic,
925
        }
926
      );
927
    }
928

929
    // Apply QoS overriding options if provided
930
    if (options.qosOverridingOptions) {
440✔
931
      const resolvedTopic = this.resolveTopicName(topic);
2✔
932
      if (typeof options.qos === 'string' || !(options.qos instanceof QoS)) {
2!
UNCOV
933
        options.qos = _resolveQoS(options.qos);
×
934
      }
935
      declareQosParameters(
2✔
936
        'subscription',
937
        this,
938
        resolvedTopic,
939
        options.qos,
940
        options.qosOverridingOptions
941
      );
942
    }
943

944
    let subscription = Subscription.createSubscription(
440✔
945
      this,
946
      typeClass,
947
      topic,
948
      options,
949
      callback,
950
      eventCallbacks
951
    );
952
    debug('Finish creating subscription, topic = %s.', topic);
429✔
953
    this._subscriptions.push(subscription);
429✔
954
    this.syncHandles();
429✔
955

956
    return subscription;
429✔
957
  }
958

959
  /**
960
   * Create a Subscription that returns an RxJS Observable.
961
   * This allows using reactive programming patterns with ROS 2 messages.
962
   *
963
   * @param {function|string|object} typeClass - The ROS message class,
964
   *      OR a string representing the message class, e.g. 'std_msgs/msg/String',
965
   *      OR an object representing the message class, e.g. {package: 'std_msgs', type: 'msg', name: 'String'}
966
   * @param {string} topic - The name of the topic.
967
   * @param {object} [options] - The options argument used to parameterize the subscription.
968
   * @param {boolean} [options.enableTypedArray=true] - The topic will use TypedArray if necessary.
969
   * @param {QoS} [options.qos=QoS.profileDefault] - ROS Middleware "quality of service" settings.
970
   * @param {boolean} [options.isRaw=false] - The topic is serialized when true.
971
   * @param {string} [options.serializationMode='default'] - Controls message serialization format.
972
   * @param {object} [options.contentFilter] - The content-filter (if supported by RMW).
973
   * @param {SubscriptionEventCallbacks} [eventCallbacks] - The event callbacks for the subscription.
974
   * @return {ObservableSubscription} - An ObservableSubscription with an RxJS Observable.
975
   */
976
  createObservableSubscription(typeClass, topic, options, eventCallbacks) {
977
    let observableSubscription = null;
7✔
978

979
    const subscription = this.createSubscription(
7✔
980
      typeClass,
981
      topic,
982
      options,
983
      (message) => {
984
        if (observableSubscription) {
8!
985
          observableSubscription._emit(message);
8✔
986
        }
987
      },
988
      eventCallbacks
989
    );
990

991
    observableSubscription = new ObservableSubscription(subscription);
7✔
992
    return observableSubscription;
7✔
993
  }
994

995
  /**
996
   * Create a Client.
997
   * @param {function|string|object} typeClass - The ROS message class,
998
        OR a string representing the message class, e.g. 'std_msgs/msg/String',
999
        OR an object representing the message class, e.g. {package: 'std_msgs', type: 'msg', name: 'String'}
1000
   * @param {string} serviceName - The service name to request.
1001
   * @param {object} options - The options argument used to parameterize the client.
1002
   * @param {boolean} options.enableTypedArray - The response will use TypedArray if necessary, default: true.
1003
   * @param {QoS} options.qos - ROS Middleware "quality of service" settings for the client, default: QoS.profileDefault.
1004
   * @return {Client} - An instance of Client.
1005
   */
1006
  createClient(typeClass, serviceName, options) {
1007
    if (typeof typeClass === 'string' || typeof typeClass === 'object') {
196✔
1008
      typeClass = loader.loadInterface(typeClass);
186✔
1009
    }
1010
    options = this._validateOptions(options);
189✔
1011

1012
    if (typeof typeClass !== 'function') {
189✔
1013
      throw new TypeValidationError('typeClass', typeClass, 'function', {
8✔
1014
        nodeName: this.name(),
1015
        entityType: 'client',
1016
      });
1017
    }
1018
    if (typeof serviceName !== 'string') {
181✔
1019
      throw new TypeValidationError('serviceName', serviceName, 'string', {
12✔
1020
        nodeName: this.name(),
1021
        entityType: 'client',
1022
      });
1023
    }
1024

1025
    let client = Client.createClient(
169✔
1026
      this.handle,
1027
      serviceName,
1028
      typeClass,
1029
      options
1030
    );
1031
    debug('Finish creating client, service = %s.', serviceName);
159✔
1032
    this._clients.push(client);
159✔
1033
    this.syncHandles();
159✔
1034

1035
    return client;
159✔
1036
  }
1037

1038
  /**
1039
   * This callback is called when a request is sent to service
1040
   * @callback RequestCallback
1041
   * @param {Object} request - The request sent to the service
1042
   * @param {Response} response - The response to client.
1043
        Use [response.send()]{@link Response#send} to send response object to client
1044
   * @return {undefined}
1045
   * @see [Node.createService]{@link Node#createService}
1046
   * @see [Client.sendRequest]{@link Client#sendRequest}
1047
   * @see {@link Client}
1048
   * @see {@link Service}
1049
   * @see {@link Response#send}
1050
   */
1051

1052
  /**
1053
   * Create a Service.
1054
   * @param {function|string|object} typeClass - The ROS message class,
1055
        OR a string representing the message class, e.g. 'std_msgs/msg/String',
1056
        OR an object representing the message class, e.g. {package: 'std_msgs', type: 'msg', name: 'String'}
1057
   * @param {string} serviceName - The service name to offer.
1058
   * @param {object} options - The options argument used to parameterize the service.
1059
   * @param {boolean} options.enableTypedArray - The request will use TypedArray if necessary, default: true.
1060
   * @param {QoS} options.qos - ROS Middleware "quality of service" settings for the service, default: QoS.profileDefault.
1061
   * @param {RequestCallback} callback - The callback to be called when receiving request.
1062
   * @return {Service} - An instance of Service.
1063
   * @see {@link RequestCallback}
1064
   */
1065
  createService(typeClass, serviceName, options, callback) {
1066
    if (typeof typeClass === 'string' || typeof typeClass === 'object') {
5,551✔
1067
      typeClass = loader.loadInterface(typeClass);
5,541✔
1068
    }
1069

1070
    if (typeof options === 'function') {
5,544✔
1071
      callback = options;
5,521✔
1072
      options = undefined;
5,521✔
1073
    }
1074
    options = this._validateOptions(options);
5,544✔
1075

1076
    if (typeof typeClass !== 'function') {
5,534✔
1077
      throw new TypeValidationError('typeClass', typeClass, 'function', {
4✔
1078
        nodeName: this.name(),
1079
        entityType: 'service',
1080
      });
1081
    }
1082
    if (typeof serviceName !== 'string') {
5,530✔
1083
      throw new TypeValidationError('serviceName', serviceName, 'string', {
6✔
1084
        nodeName: this.name(),
1085
        entityType: 'service',
1086
      });
1087
    }
1088
    if (typeof callback !== 'function') {
5,524!
UNCOV
1089
      throw new TypeValidationError('callback', callback, 'function', {
×
1090
        nodeName: this.name(),
1091
        entityType: 'service',
1092
        entityName: serviceName,
1093
      });
1094
    }
1095

1096
    let service = Service.createService(
5,524✔
1097
      this.handle,
1098
      serviceName,
1099
      typeClass,
1100
      options,
1101
      callback
1102
    );
1103
    debug('Finish creating service, service = %s.', serviceName);
5,514✔
1104
    this._services.push(service);
5,514✔
1105
    this.syncHandles();
5,514✔
1106

1107
    return service;
5,514✔
1108
  }
1109

1110
  /**
1111
   * Create a ParameterClient for accessing parameters on a remote node.
1112
   * @param {string} remoteNodeName - The name of the remote node whose parameters to access.
1113
   * @param {object} [options] - Options for parameter client.
1114
   * @param {number} [options.timeout=5000] - Default timeout in milliseconds for service calls.
1115
   * @return {ParameterClient} - An instance of ParameterClient.
1116
   */
1117
  createParameterClient(remoteNodeName, options = {}) {
50✔
1118
    if (typeof remoteNodeName !== 'string' || remoteNodeName.trim() === '') {
103!
UNCOV
1119
      throw new TypeError('Remote node name must be a non-empty string');
×
1120
    }
1121

1122
    const parameterClient = new ParameterClient(this, remoteNodeName, options);
103✔
1123
    debug(
103✔
1124
      'Finish creating parameter client for remote node = %s.',
1125
      remoteNodeName
1126
    );
1127
    this._parameterClients.push(parameterClient);
103✔
1128

1129
    return parameterClient;
103✔
1130
  }
1131

1132
  /**
1133
   * Create a ParameterWatcher for watching parameter changes on a remote node.
1134
   * @param {string} remoteNodeName - The name of the remote node whose parameters to watch.
1135
   * @param {string[]} parameterNames - Array of parameter names to watch.
1136
   * @param {object} [options] - Options for parameter watcher.
1137
   * @param {number} [options.timeout=5000] - Default timeout in milliseconds for service calls.
1138
   * @return {ParameterWatcher} - An instance of ParameterWatcher.
1139
   */
1140
  createParameterWatcher(remoteNodeName, parameterNames, options = {}) {
56✔
1141
    const watcher = new ParameterWatcher(
57✔
1142
      this,
1143
      remoteNodeName,
1144
      parameterNames,
1145
      options
1146
    );
1147
    debug(
53✔
1148
      'Finish creating parameter watcher for remote node = %s.',
1149
      remoteNodeName
1150
    );
1151
    this._parameterWatchers.push(watcher);
53✔
1152

1153
    return watcher;
53✔
1154
  }
1155

1156
  /**
1157
   * Create a guard condition.
1158
   * @param {Function} callback - The callback to be called when the guard condition is triggered.
1159
   * @return {GuardCondition} - An instance of GuardCondition.
1160
   */
1161
  createGuardCondition(callback) {
1162
    if (typeof callback !== 'function') {
3!
UNCOV
1163
      throw new TypeValidationError('callback', callback, 'function', {
×
1164
        nodeName: this.name(),
1165
        entityType: 'guard_condition',
1166
      });
1167
    }
1168

1169
    let guard = GuardCondition.createGuardCondition(callback, this.context);
3✔
1170
    debug('Finish creating guard condition');
3✔
1171
    this._guards.push(guard);
3✔
1172
    this.syncHandles();
3✔
1173

1174
    return guard;
3✔
1175
  }
1176

1177
  /**
1178
   * Destroy all resource allocated by this node, including
1179
   * <code>Timer</code>s/<code>Publisher</code>s/<code>Subscription</code>s
1180
   * /<code>Client</code>s/<code>Service</code>s
1181
   * @return {undefined}
1182
   */
1183
  destroy() {
1184
    if (this.spinning) {
944✔
1185
      this.stop();
615✔
1186
    }
1187

1188
    // Action servers/clients require manual destruction due to circular reference with goal handles.
1189
    this._actionClients.forEach((actionClient) => actionClient.destroy());
944✔
1190
    this._actionServers.forEach((actionServer) => actionServer.destroy());
944✔
1191

1192
    this._parameterClients.forEach((paramClient) => paramClient.destroy());
944✔
1193
    this._parameterWatchers.forEach((watcher) => watcher.destroy());
944✔
1194
    this._parameterEventHandlers.forEach((handler) => handler.destroy());
944✔
1195

1196
    this.context.onNodeDestroyed(this);
944✔
1197

1198
    if (this._enableRosout) {
944✔
1199
      rclnodejs.finiRosoutPublisherForNode(this.handle);
919✔
1200
      this._enableRosout = false;
919✔
1201
    }
1202

1203
    this.handle.release();
944✔
1204
    this._clock = null;
944✔
1205
    this._timers = [];
944✔
1206
    this._publishers = [];
944✔
1207
    this._subscriptions = [];
944✔
1208
    this._clients = [];
944✔
1209
    this._services = [];
944✔
1210
    this._guards = [];
944✔
1211
    this._actionClients = [];
944✔
1212
    this._actionServers = [];
944✔
1213
    this._parameterClients = [];
944✔
1214
    this._parameterWatchers = [];
944✔
1215
    this._parameterEventHandlers = [];
944✔
1216

1217
    if (this._rateTimerServer) {
944✔
1218
      this._rateTimerServer.shutdown();
5✔
1219
      this._rateTimerServer = null;
5✔
1220
    }
1221
  }
1222

1223
  /**
1224
   * Destroy a Publisher.
1225
   * @param {Publisher} publisher - The Publisher to be destroyed.
1226
   * @return {undefined}
1227
   */
1228
  destroyPublisher(publisher) {
1229
    if (!(publisher instanceof Publisher)) {
14✔
1230
      throw new TypeValidationError(
2✔
1231
        'publisher',
1232
        publisher,
1233
        'Publisher instance',
1234
        {
1235
          nodeName: this.name(),
1236
        }
1237
      );
1238
    }
1239
    if (publisher.events) {
12!
UNCOV
1240
      publisher.events.forEach((event) => {
×
UNCOV
1241
        this._destroyEntity(event, this._events);
×
1242
      });
UNCOV
1243
      publisher.events = [];
×
1244
    }
1245
    this._destroyEntity(publisher, this._publishers, false);
12✔
1246
  }
1247

1248
  /**
1249
   * Destroy a Subscription.
1250
   * @param {Subscription} subscription - The Subscription to be destroyed.
1251
   * @return {undefined}
1252
   */
1253
  destroySubscription(subscription) {
1254
    if (!(subscription instanceof Subscription)) {
111✔
1255
      throw new TypeValidationError(
2✔
1256
        'subscription',
1257
        subscription,
1258
        'Subscription instance',
1259
        {
1260
          nodeName: this.name(),
1261
        }
1262
      );
1263
    }
1264
    if (subscription.events) {
109✔
1265
      subscription.events.forEach((event) => {
1✔
1266
        this._destroyEntity(event, this._events);
1✔
1267
      });
1268
      subscription.events = [];
1✔
1269
    }
1270

1271
    this._destroyEntity(subscription, this._subscriptions);
109✔
1272
  }
1273

1274
  /**
1275
   * Destroy a Client.
1276
   * @param {Client} client - The Client to be destroyed.
1277
   * @return {undefined}
1278
   */
1279
  destroyClient(client) {
1280
    if (!(client instanceof Client)) {
117✔
1281
      throw new TypeValidationError('client', client, 'Client instance', {
2✔
1282
        nodeName: this.name(),
1283
      });
1284
    }
1285
    this._destroyEntity(client, this._clients);
115✔
1286
  }
1287

1288
  /**
1289
   * Destroy a Service.
1290
   * @param {Service} service - The Service to be destroyed.
1291
   * @return {undefined}
1292
   */
1293
  destroyService(service) {
1294
    if (!(service instanceof Service)) {
8✔
1295
      throw new TypeValidationError('service', service, 'Service instance', {
2✔
1296
        nodeName: this.name(),
1297
      });
1298
    }
1299
    this._destroyEntity(service, this._services);
6✔
1300
  }
1301

1302
  /**
1303
   * Destroy a ParameterClient.
1304
   * @param {ParameterClient} parameterClient - The ParameterClient to be destroyed.
1305
   * @return {undefined}
1306
   */
1307
  destroyParameterClient(parameterClient) {
1308
    if (!(parameterClient instanceof ParameterClient)) {
54!
UNCOV
1309
      throw new TypeError('Invalid argument');
×
1310
    }
1311
    this._removeEntityFromArray(parameterClient, this._parameterClients);
54✔
1312
    parameterClient.destroy();
54✔
1313
  }
1314

1315
  /**
1316
   * Destroy a ParameterWatcher.
1317
   * @param {ParameterWatcher} watcher - The ParameterWatcher to be destroyed.
1318
   * @return {undefined}
1319
   */
1320
  destroyParameterWatcher(watcher) {
1321
    if (!(watcher instanceof ParameterWatcher)) {
1!
UNCOV
1322
      throw new TypeError('Invalid argument');
×
1323
    }
1324
    this._removeEntityFromArray(watcher, this._parameterWatchers);
1✔
1325
    watcher.destroy();
1✔
1326
  }
1327

1328
  /**
1329
   * Create a ParameterEventHandler that monitors parameter changes on any node.
1330
   *
1331
   * Unlike {@link ParameterWatcher} which watches specific parameters on a single
1332
   * remote node, ParameterEventHandler can register callbacks for parameters on
1333
   * any node in the ROS 2 graph by subscribing to /parameter_events.
1334
   *
1335
   * @param {object} [options] - Options for the handler
1336
   * @param {object} [options.qos] - QoS profile for the parameter_events subscription
1337
   * @return {ParameterEventHandler} - An instance of ParameterEventHandler
1338
   * @see {@link ParameterEventHandler}
1339
   */
1340
  createParameterEventHandler(options = {}) {
18✔
1341
    const handler = new ParameterEventHandler(this, options);
18✔
1342
    debug('Created ParameterEventHandler on node=%s', this.name());
18✔
1343
    this._parameterEventHandlers.push(handler);
18✔
1344
    return handler;
18✔
1345
  }
1346

1347
  /**
1348
   * Destroy a ParameterEventHandler.
1349
   * @param {ParameterEventHandler} handler - The handler to be destroyed.
1350
   * @return {undefined}
1351
   */
1352
  destroyParameterEventHandler(handler) {
1353
    if (!(handler instanceof ParameterEventHandler)) {
1!
UNCOV
1354
      throw new TypeError('Invalid argument');
×
1355
    }
1356
    this._removeEntityFromArray(handler, this._parameterEventHandlers);
1✔
1357
    handler.destroy();
1✔
1358
  }
1359

1360
  /**
1361
   * Destroy a Timer.
1362
   * @param {Timer} timer - The Timer to be destroyed.
1363
   * @return {undefined}
1364
   */
1365
  destroyTimer(timer) {
1366
    if (!(timer instanceof Timer)) {
8✔
1367
      throw new TypeValidationError('timer', timer, 'Timer instance', {
2✔
1368
        nodeName: this.name(),
1369
      });
1370
    }
1371
    this._destroyEntity(timer, this._timers);
6✔
1372
  }
1373

1374
  /**
1375
   * Destroy a guard condition.
1376
   * @param {GuardCondition} guard - The guard condition to be destroyed.
1377
   * @return {undefined}
1378
   */
1379
  destroyGuardCondition(guard) {
1380
    if (!(guard instanceof GuardCondition)) {
3!
UNCOV
1381
      throw new TypeValidationError('guard', guard, 'GuardCondition instance', {
×
1382
        nodeName: this.name(),
1383
      });
1384
    }
1385
    this._destroyEntity(guard, this._guards);
3✔
1386
  }
1387

1388
  /**
1389
   * Get the name of the node.
1390
   * @return {string}
1391
   */
1392
  name() {
1393
    return rclnodejs.getNodeName(this.handle);
5,506✔
1394
  }
1395

1396
  /**
1397
   * Get the namespace of the node.
1398
   * @return {string}
1399
   */
1400
  namespace() {
1401
    return rclnodejs.getNamespace(this.handle);
5,122✔
1402
  }
1403

1404
  /**
1405
   * Get the context in which this node was created.
1406
   * @return {Context}
1407
   */
1408
  get context() {
1409
    return this._context;
6,574✔
1410
  }
1411

1412
  /**
1413
   * Get the nodes logger.
1414
   * @returns {Logger} - The logger for the node.
1415
   */
1416
  getLogger() {
1417
    return this._logger;
243✔
1418
  }
1419

1420
  /**
1421
   * Get the clock used by the node.
1422
   * @returns {Clock} - The nodes clock.
1423
   */
1424
  getClock() {
1425
    return this._clock;
117✔
1426
  }
1427

1428
  /**
1429
   * Get the current time using the node's clock.
1430
   * @returns {Timer} - The current time.
1431
   */
1432
  now() {
1433
    return this.getClock().now();
2✔
1434
  }
1435

1436
  /**
1437
   * Get the list of published topics discovered by the provided node for the remote node name.
1438
   * @param {string} nodeName - The name of the node.
1439
   * @param {string} namespace - The name of the namespace.
1440
   * @param {boolean} noDemangle - If true topic names and types returned will not be demangled, default: false.
1441
   * @return {Array<{name: string, types: Array<string>}>} - An array of the names and types.
1442
   */
1443
  getPublisherNamesAndTypesByNode(nodeName, namespace, noDemangle = false) {
2✔
1444
    return rclnodejs.getPublisherNamesAndTypesByNode(
2✔
1445
      this.handle,
1446
      nodeName,
1447
      namespace,
1448
      noDemangle
1449
    );
1450
  }
1451

1452
  /**
1453
   * Get the list of published topics discovered by the provided node for the remote node name.
1454
   * @param {string} nodeName - The name of the node.
1455
   * @param {string} namespace - The name of the namespace.
1456
   * @param {boolean} noDemangle - If true topic names and types returned will not be demangled, default: false.
1457
   * @return {Array<{name: string, types: Array<string>}>} - An array of the names and types.
1458
   */
1459
  getSubscriptionNamesAndTypesByNode(nodeName, namespace, noDemangle = false) {
×
UNCOV
1460
    return rclnodejs.getSubscriptionNamesAndTypesByNode(
×
1461
      this.handle,
1462
      nodeName,
1463
      namespace,
1464
      noDemangle
1465
    );
1466
  }
1467

1468
  /**
1469
   * Get service names and types for which a remote node has servers.
1470
   * @param {string} nodeName - The name of the node.
1471
   * @param {string} namespace - The name of the namespace.
1472
   * @return {Array<{name: string, types: Array<string>}>} - An array of the names and types.
1473
   */
1474
  getServiceNamesAndTypesByNode(nodeName, namespace) {
UNCOV
1475
    return rclnodejs.getServiceNamesAndTypesByNode(
×
1476
      this.handle,
1477
      nodeName,
1478
      namespace
1479
    );
1480
  }
1481

1482
  /**
1483
   * Get service names and types for which a remote node has clients.
1484
   * @param {string} nodeName - The name of the node.
1485
   * @param {string} namespace - The name of the namespace.
1486
   * @return {Array<{name: string, types: Array<string>}>} - An array of the names and types.
1487
   */
1488
  getClientNamesAndTypesByNode(nodeName, namespace) {
UNCOV
1489
    return rclnodejs.getClientNamesAndTypesByNode(
×
1490
      this.handle,
1491
      nodeName,
1492
      namespace
1493
    );
1494
  }
1495

1496
  /**
1497
   * Get the list of topics discovered by the provided node.
1498
   * @param {boolean} noDemangle - If true topic names and types returned will not be demangled, default: false.
1499
   * @return {Array<{name: string, types: Array<string>}>} - An array of the names and types.
1500
   */
1501
  getTopicNamesAndTypes(noDemangle = false) {
×
UNCOV
1502
    return rclnodejs.getTopicNamesAndTypes(this.handle, noDemangle);
×
1503
  }
1504

1505
  /**
1506
   * Get the list of services discovered by the provided node.
1507
   * @return {Array<{name: string, types: Array<string>}>} - An array of the names and types.
1508
   */
1509
  getServiceNamesAndTypes() {
1510
    return rclnodejs.getServiceNamesAndTypes(this.handle);
2✔
1511
  }
1512

1513
  /**
1514
   * Return a list of publishers on a given topic.
1515
   *
1516
   * The returned parameter is a list of TopicEndpointInfo objects, where each will contain
1517
   * the node name, node namespace, topic type, topic endpoint's GID, and its QoS profile.
1518
   *
1519
   * When the `no_mangle` parameter is `true`, the provided `topic` should be a valid
1520
   * topic name for the middleware (useful when combining ROS with native middleware (e.g. DDS)
1521
   * apps).  When the `no_mangle` parameter is `false`, the provided `topic` should
1522
   * follow ROS topic name conventions.
1523
   *
1524
   * `topic` may be a relative, private, or fully qualified topic name.
1525
   *  A relative or private topic will be expanded using this node's namespace and name.
1526
   *  The queried `topic` is not remapped.
1527
   *
1528
   * @param {string} topic - The topic on which to find the publishers.
1529
   * @param {boolean} [noDemangle=false] - If `true`, `topic` needs to be a valid middleware topic
1530
   *                               name, otherwise it should be a valid ROS topic name. Defaults to `false`.
1531
   * @returns {Array} - list of publishers
1532
   */
1533
  getPublishersInfoByTopic(topic, noDemangle = false) {
1✔
1534
    return rclnodejs.getPublishersInfoByTopic(
4✔
1535
      this.handle,
1536
      this._getValidatedTopic(topic, noDemangle),
1537
      noDemangle
1538
    );
1539
  }
1540

1541
  /**
1542
   * Return a list of subscriptions on a given topic.
1543
   *
1544
   * The returned parameter is a list of TopicEndpointInfo objects, where each will contain
1545
   * the node name, node namespace, topic type, topic endpoint's GID, and its QoS profile.
1546
   *
1547
   * When the `no_mangle` parameter is `true`, the provided `topic` should be a valid
1548
   * topic name for the middleware (useful when combining ROS with native middleware (e.g. DDS)
1549
   * apps).  When the `no_mangle` parameter is `false`, the provided `topic` should
1550
   * follow ROS topic name conventions.
1551
   *
1552
   * `topic` may be a relative, private, or fully qualified topic name.
1553
   *  A relative or private topic will be expanded using this node's namespace and name.
1554
   *  The queried `topic` is not remapped.
1555
   *
1556
   * @param {string} topic - The topic on which to find the subscriptions.
1557
   * @param {boolean} [noDemangle=false] -  If `true`, `topic` needs to be a valid middleware topic
1558
                                    name, otherwise it should be a valid ROS topic name. Defaults to `false`.
1559
   * @returns {Array} - list of subscriptions
1560
   */
1561
  getSubscriptionsInfoByTopic(topic, noDemangle = false) {
×
1562
    return rclnodejs.getSubscriptionsInfoByTopic(
2✔
1563
      this.handle,
1564
      this._getValidatedTopic(topic, noDemangle),
1565
      noDemangle
1566
    );
1567
  }
1568

1569
  /**
1570
   * Return a list of clients on a given service.
1571
   *
1572
   * The returned parameter is a list of ServiceEndpointInfo objects, where each will contain
1573
   * the node name, node namespace, service type, service endpoint's GID, and its QoS profile.
1574
   *
1575
   * When the `no_mangle` parameter is `true`, the provided `service` should be a valid
1576
   * service name for the middleware (useful when combining ROS with native middleware (e.g. DDS)
1577
   * apps).  When the `no_mangle` parameter is `false`, the provided `service` should
1578
   * follow ROS service name conventions.
1579
   *
1580
   * `service` may be a relative, private, or fully qualified service name.
1581
   *  A relative or private service will be expanded using this node's namespace and name.
1582
   *  The queried `service` is not remapped.
1583
   *
1584
   * @param {string} service - The service on which to find the clients.
1585
   * @param {boolean} [noDemangle=false] - If `true`, `service` needs to be a valid middleware service
1586
   *                               name, otherwise it should be a valid ROS service name. Defaults to `false`.
1587
   * @returns {Array} - list of clients
1588
   */
1589
  getClientsInfoByService(service, noDemangle = false) {
×
1590
    if (DistroUtils.getDistroId() < DistroUtils.DistroId.ROLLING) {
2!
UNCOV
1591
      console.warn(
×
1592
        'getClientsInfoByService is not supported by this version of ROS 2'
1593
      );
UNCOV
1594
      return null;
×
1595
    }
1596
    return rclnodejs.getClientsInfoByService(
2✔
1597
      this.handle,
1598
      this._getValidatedServiceName(service, noDemangle),
1599
      noDemangle
1600
    );
1601
  }
1602

1603
  /**
1604
   * Return a list of servers on a given service.
1605
   *
1606
   * The returned parameter is a list of ServiceEndpointInfo objects, where each will contain
1607
   * the node name, node namespace, service type, service endpoint's GID, and its QoS profile.
1608
   *
1609
   * When the `no_mangle` parameter is `true`, the provided `service` should be a valid
1610
   * service name for the middleware (useful when combining ROS with native middleware (e.g. DDS)
1611
   * apps).  When the `no_mangle` parameter is `false`, the provided `service` should
1612
   * follow ROS service name conventions.
1613
   *
1614
   * `service` may be a relative, private, or fully qualified service name.
1615
   *  A relative or private service will be expanded using this node's namespace and name.
1616
   *  The queried `service` is not remapped.
1617
   *
1618
   * @param {string} service - The service on which to find the servers.
1619
   * @param {boolean} [noDemangle=false] - If `true`, `service` needs to be a valid middleware service
1620
   *                               name, otherwise it should be a valid ROS service name. Defaults to `false`.
1621
   * @returns {Array} - list of servers
1622
   */
1623
  getServersInfoByService(service, noDemangle = false) {
×
1624
    if (DistroUtils.getDistroId() < DistroUtils.DistroId.ROLLING) {
2!
UNCOV
1625
      console.warn(
×
1626
        'getServersInfoByService is not supported by this version of ROS 2'
1627
      );
UNCOV
1628
      return null;
×
1629
    }
1630
    return rclnodejs.getServersInfoByService(
2✔
1631
      this.handle,
1632
      this._getValidatedServiceName(service, noDemangle),
1633
      noDemangle
1634
    );
1635
  }
1636

1637
  /**
1638
   * Get the list of nodes discovered by the provided node.
1639
   * @return {Array<string>} - An array of the names.
1640
   */
1641
  getNodeNames() {
1642
    return this.getNodeNamesAndNamespaces().map((item) => item.name);
41✔
1643
  }
1644

1645
  /**
1646
   * Get the list of nodes and their namespaces discovered by the provided node.
1647
   * @return {Array<{name: string, namespace: string}>} An array of the names and namespaces.
1648
   */
1649
  getNodeNamesAndNamespaces() {
1650
    return rclnodejs.getNodeNames(this.handle, /*getEnclaves=*/ false);
17✔
1651
  }
1652

1653
  /**
1654
   * Get the list of nodes and their namespaces with enclaves discovered by the provided node.
1655
   * @return {Array<{name: string, namespace: string, enclave: string}>} An array of the names, namespaces and enclaves.
1656
   */
1657
  getNodeNamesAndNamespacesWithEnclaves() {
1658
    return rclnodejs.getNodeNames(this.handle, /*getEnclaves=*/ true);
1✔
1659
  }
1660

1661
  /**
1662
   * Return the number of publishers on a given topic.
1663
   * @param {string} topic - The name of the topic.
1664
   * @returns {number} - Number of publishers on the given topic.
1665
   */
1666
  countPublishers(topic) {
1667
    let expandedTopic = rclnodejs.expandTopicName(
6✔
1668
      topic,
1669
      this.name(),
1670
      this.namespace()
1671
    );
1672
    rclnodejs.validateTopicName(expandedTopic);
6✔
1673

1674
    return rclnodejs.countPublishers(this.handle, expandedTopic);
6✔
1675
  }
1676

1677
  /**
1678
   * Return the number of subscribers on a given topic.
1679
   * @param {string} topic - The name of the topic.
1680
   * @returns {number} - Number of subscribers on the given topic.
1681
   */
1682
  countSubscribers(topic) {
1683
    let expandedTopic = rclnodejs.expandTopicName(
6✔
1684
      topic,
1685
      this.name(),
1686
      this.namespace()
1687
    );
1688
    rclnodejs.validateTopicName(expandedTopic);
6✔
1689

1690
    return rclnodejs.countSubscribers(this.handle, expandedTopic);
6✔
1691
  }
1692

1693
  /**
1694
   * Get the number of clients on a given service name.
1695
   * @param {string} serviceName - the service name
1696
   * @returns {Number}
1697
   */
1698
  countClients(serviceName) {
1699
    if (DistroUtils.getDistroId() <= DistroUtils.getDistroId('humble')) {
2!
UNCOV
1700
      console.warn('countClients is not supported by this version of ROS 2');
×
UNCOV
1701
      return null;
×
1702
    }
1703
    return rclnodejs.countClients(this.handle, serviceName);
2✔
1704
  }
1705

1706
  /**
1707
   * Get the number of services on a given service name.
1708
   * @param {string} serviceName - the service name
1709
   * @returns {Number}
1710
   */
1711
  countServices(serviceName) {
1712
    if (DistroUtils.getDistroId() <= DistroUtils.getDistroId('humble')) {
1!
UNCOV
1713
      console.warn('countServices is not supported by this version of ROS 2');
×
UNCOV
1714
      return null;
×
1715
    }
1716
    return rclnodejs.countServices(this.handle, serviceName);
1✔
1717
  }
1718

1719
  /**
1720
   * Get the list of parameter-overrides found on the commandline and
1721
   * in the NodeOptions.parameter_overrides property.
1722
   *
1723
   * @return {Array<Parameter>} - An array of Parameters.
1724
   */
1725
  getParameterOverrides() {
1726
    return Array.from(this._parameterOverrides.values());
8✔
1727
  }
1728

1729
  /**
1730
   * Declare a parameter.
1731
   *
1732
   * Internally, register a parameter and it's descriptor.
1733
   * If a parameter-override exists, it's value will replace that of the parameter
1734
   * unless ignoreOverride is true.
1735
   * If the descriptor is undefined, then a ParameterDescriptor will be inferred
1736
   * from the parameter's state.
1737
   *
1738
   * If a parameter by the same name has already been declared then an Error is thrown.
1739
   * A parameter must be undeclared before attempting to redeclare it.
1740
   *
1741
   * @param {Parameter} parameter - Parameter to declare.
1742
   * @param {ParameterDescriptor} [descriptor] - Optional descriptor for parameter.
1743
   * @param {boolean} [ignoreOverride] - When true disregard any parameter-override that may be present.
1744
   * @return {Parameter} - The newly declared parameter.
1745
   */
1746
  declareParameter(parameter, descriptor, ignoreOverride = false) {
2,506✔
1747
    const parameters = this.declareParameters(
2,507✔
1748
      [parameter],
1749
      descriptor ? [descriptor] : [],
2,507✔
1750
      ignoreOverride
1751
    );
1752
    return parameters.length == 1 ? parameters[0] : null;
2,507!
1753
  }
1754

1755
  /**
1756
   * Declare a list of parameters.
1757
   *
1758
   * Internally register parameters with their corresponding descriptor one by one
1759
   * in the order they are provided. This is an atomic operation. If an error
1760
   * occurs the process halts and no further parameters are declared.
1761
   * Parameters that have already been processed are undeclared.
1762
   *
1763
   * While descriptors is an optional parameter, when provided there must be
1764
   * a descriptor for each parameter; otherwise an Error is thrown.
1765
   * If descriptors is not provided then a descriptor will be inferred
1766
   * from each parameter's state.
1767
   *
1768
   * When a parameter-override is available, the parameter's value
1769
   * will be replaced with that of the parameter-override unless ignoreOverrides
1770
   * is true.
1771
   *
1772
   * If a parameter by the same name has already been declared then an Error is thrown.
1773
   * A parameter must be undeclared before attempting to redeclare it.
1774
   *
1775
   * Prior to declaring the parameters each SetParameterEventCallback registered
1776
   * using setOnParameterEventCallback() is called in succession with the parameters
1777
   * list. Any SetParameterEventCallback that retuns does not return a successful
1778
   * result will cause the entire operation to terminate with no changes to the
1779
   * parameters. When all SetParameterEventCallbacks return successful then the
1780
   * list of parameters is updated.
1781
   *
1782
   * @param {Parameter[]} parameters - The parameters to declare.
1783
   * @param {ParameterDescriptor[]} [descriptors] - Optional descriptors,
1784
   *    a 1-1 correspondence with parameters.
1785
   * @param {boolean} ignoreOverrides - When true, parameter-overrides are
1786
   *    not considered, i.e.,ignored.
1787
   * @return {Parameter[]} - The declared parameters.
1788
   */
1789
  declareParameters(parameters, descriptors = [], ignoreOverrides = false) {
×
1790
    if (!Array.isArray(parameters)) {
2,507!
UNCOV
1791
      throw new TypeValidationError('parameters', parameters, 'Array', {
×
1792
        nodeName: this.name(),
1793
      });
1794
    }
1795
    if (!Array.isArray(descriptors)) {
2,507!
UNCOV
1796
      throw new TypeValidationError('descriptors', descriptors, 'Array', {
×
1797
        nodeName: this.name(),
1798
      });
1799
    }
1800
    if (descriptors.length > 0 && parameters.length !== descriptors.length) {
2,507!
UNCOV
1801
      throw new ValidationError(
×
1802
        'Each parameter must have a corresponding ParameterDescriptor',
1803
        {
1804
          code: 'PARAMETER_DESCRIPTOR_MISMATCH',
1805
          argumentName: 'descriptors',
1806
          providedValue: descriptors.length,
1807
          expectedType: `Array with length ${parameters.length}`,
1808
          nodeName: this.name(),
1809
        }
1810
      );
1811
    }
1812

1813
    const declaredDescriptors = [];
2,507✔
1814
    const declaredParameters = [];
2,507✔
1815
    const declaredParameterCollisions = [];
2,507✔
1816
    for (let i = 0; i < parameters.length; i++) {
2,507✔
1817
      let parameter =
1818
        !ignoreOverrides && this._parameterOverrides.has(parameters[i].name)
2,507✔
1819
          ? this._parameterOverrides.get(parameters[i].name)
1820
          : parameters[i];
1821

1822
      // stop processing parameters that have already been declared
1823
      if (this._parameters.has(parameter.name)) {
2,507!
UNCOV
1824
        declaredParameterCollisions.push(parameter);
×
UNCOV
1825
        continue;
×
1826
      }
1827

1828
      // create descriptor for parameter if not provided
1829
      let descriptor =
1830
        descriptors.length > 0
2,507✔
1831
          ? descriptors[i]
1832
          : ParameterDescriptor.fromParameter(parameter);
1833

1834
      descriptor.validate();
2,507✔
1835

1836
      declaredDescriptors.push(descriptor);
2,507✔
1837
      declaredParameters.push(parameter);
2,507✔
1838
    }
1839

1840
    if (declaredParameterCollisions.length > 0) {
2,507!
1841
      const errorMsg =
UNCOV
1842
        declaredParameterCollisions.length == 1
×
1843
          ? `Parameter(${declaredParameterCollisions[0]}) already declared.`
1844
          : `Multiple parameters already declared, e.g., Parameter(${declaredParameterCollisions[0]}).`;
UNCOV
1845
      throw new Error(errorMsg);
×
1846
    }
1847

1848
    // register descriptor
1849
    for (const descriptor of declaredDescriptors) {
2,507✔
1850
      this._parameterDescriptors.set(descriptor.name, descriptor);
2,507✔
1851
    }
1852

1853
    const result = this._setParametersAtomically(declaredParameters, true);
2,507✔
1854
    if (!result.successful) {
2,507!
1855
      // unregister descriptors
UNCOV
1856
      for (const descriptor of declaredDescriptors) {
×
UNCOV
1857
        this._parameterDescriptors.delete(descriptor.name);
×
1858
      }
1859

UNCOV
1860
      throw new Error(result.reason);
×
1861
    }
1862

1863
    return this.getParameters(declaredParameters.map((param) => param.name));
2,507✔
1864
  }
1865

1866
  /**
1867
   * Undeclare a parameter.
1868
   *
1869
   * Readonly parameters can not be undeclared or updated.
1870
   * @param {string} name - Name of parameter to undeclare.
1871
   * @return {undefined} -
1872
   */
1873
  undeclareParameter(name) {
1874
    if (!this.hasParameter(name)) return;
1!
1875

1876
    const descriptor = this.getParameterDescriptor(name);
1✔
1877
    if (descriptor.readOnly) {
1!
UNCOV
1878
      throw new Error(
×
1879
        `${name} parameter is read-only and can not be undeclared`
1880
      );
1881
    }
1882

1883
    this._parameters.delete(name);
1✔
1884
    this._parameterDescriptors.delete(name);
1✔
1885
  }
1886

1887
  /**
1888
   * Determine if a parameter has been declared.
1889
   * @param {string} name - name of parameter
1890
   * @returns {boolean} - Return true if parameter is declared; false otherwise.
1891
   */
1892
  hasParameter(name) {
1893
    return this._parameters.has(name);
6,278✔
1894
  }
1895

1896
  /**
1897
   * Get a declared parameter by name.
1898
   *
1899
   * If unable to locate a declared parameter then a
1900
   * parameter with type == PARAMETER_NOT_SET is returned.
1901
   *
1902
   * @param {string} name - The name of the parameter.
1903
   * @return {Parameter} - The parameter.
1904
   */
1905
  getParameter(name) {
1906
    return this.getParameters([name])[0];
1,872✔
1907
  }
1908

1909
  /**
1910
   * Get a list of parameters.
1911
   *
1912
   * Find and return the declared parameters.
1913
   * If no names are provided return all declared parameters.
1914
   *
1915
   * If unable to locate a declared parameter then a
1916
   * parameter with type == PARAMETER_NOT_SET is returned in
1917
   * it's place.
1918
   *
1919
   * @param {string[]} [names] - The names of the declared parameters
1920
   *    to find or null indicating to return all declared parameters.
1921
   * @return {Parameter[]} - The parameters.
1922
   */
1923
  getParameters(names = []) {
12✔
1924
    let params = [];
4,417✔
1925

1926
    if (names.length == 0) {
4,417✔
1927
      // get all parameters
1928
      params = [...this._parameters.values()];
12✔
1929
      return params;
12✔
1930
    }
1931

1932
    for (const name of names) {
4,405✔
1933
      const param = this.hasParameter(name)
4,412✔
1934
        ? this._parameters.get(name)
1935
        : new Parameter(name, ParameterType.PARAMETER_NOT_SET);
1936

1937
      params.push(param);
4,412✔
1938
    }
1939

1940
    return params;
4,405✔
1941
  }
1942

1943
  /**
1944
   * Get the types of given parameters.
1945
   *
1946
   * Return the types of given parameters.
1947
   *
1948
   * @param {string[]} [names] - The names of the declared parameters.
1949
   * @return {Uint8Array} - The types.
1950
   */
1951
  getParameterTypes(names = []) {
×
1952
    let types = [];
3✔
1953

1954
    for (const name of names) {
3✔
1955
      const descriptor = this._parameterDescriptors.get(name);
7✔
1956
      if (descriptor) {
7!
1957
        types.push(descriptor.type);
7✔
1958
      }
1959
    }
1960
    return types;
3✔
1961
  }
1962

1963
  /**
1964
   * Get the names of all declared parameters.
1965
   *
1966
   * @return {Array<string>} - The declared parameter names or empty array if
1967
   *    no parameters have been declared.
1968
   */
1969
  getParameterNames() {
1970
    return this.getParameters().map((param) => param.name);
53✔
1971
  }
1972

1973
  /**
1974
   * Determine if a parameter descriptor exists.
1975
   *
1976
   * @param {string} name - The name of a descriptor to for.
1977
   * @return {boolean} - true if a descriptor has been declared; otherwise false.
1978
   */
1979
  hasParameterDescriptor(name) {
1980
    return !!this.getParameterDescriptor(name);
2,546✔
1981
  }
1982

1983
  /**
1984
   * Get a declared parameter descriptor by name.
1985
   *
1986
   * If unable to locate a declared parameter descriptor then a
1987
   * descriptor with type == PARAMETER_NOT_SET is returned.
1988
   *
1989
   * @param {string} name - The name of the parameter descriptor to find.
1990
   * @return {ParameterDescriptor} - The parameter descriptor.
1991
   */
1992
  getParameterDescriptor(name) {
1993
    return this.getParameterDescriptors([name])[0];
5,093✔
1994
  }
1995

1996
  /**
1997
   * Find a list of declared ParameterDescriptors.
1998
   *
1999
   * If no names are provided return all declared descriptors.
2000
   *
2001
   * If unable to locate a declared descriptor then a
2002
   * descriptor with type == PARAMETER_NOT_SET is returned in
2003
   * it's place.
2004
   *
2005
   * @param {string[]} [names] - The names of the declared parameter
2006
   *    descriptors to find or null indicating to return all declared descriptors.
2007
   * @return {ParameterDescriptor[]} - The parameter descriptors.
2008
   */
2009
  getParameterDescriptors(names = []) {
×
2010
    let descriptors = [];
5,096✔
2011

2012
    if (names.length == 0) {
5,096!
2013
      // get all parameters
UNCOV
2014
      descriptors = [...this._parameterDescriptors.values()];
×
UNCOV
2015
      return descriptors;
×
2016
    }
2017

2018
    for (const name of names) {
5,096✔
2019
      let descriptor = this._parameterDescriptors.get(name);
5,098✔
2020
      if (!descriptor) {
5,098!
UNCOV
2021
        descriptor = new ParameterDescriptor(
×
2022
          name,
2023
          ParameterType.PARAMETER_NOT_SET
2024
        );
2025
      }
2026
      descriptors.push(descriptor);
5,098✔
2027
    }
2028

2029
    return descriptors;
5,096✔
2030
  }
2031

2032
  /**
2033
   * Replace a declared parameter.
2034
   *
2035
   * The parameter being replaced must be a declared parameter who's descriptor
2036
   * is not readOnly; otherwise an Error is thrown.
2037
   *
2038
   * @param {Parameter} parameter - The new parameter.
2039
   * @return {rclnodejs.rcl_interfaces.msg.SetParameterResult} - The result of the operation.
2040
   */
2041
  setParameter(parameter) {
2042
    const results = this.setParameters([parameter]);
26✔
2043
    return results[0];
26✔
2044
  }
2045

2046
  /**
2047
   * Replace a list of declared parameters.
2048
   *
2049
   * Declared parameters are replaced in the order they are provided and
2050
   * a ParameterEvent is published for each individual parameter change.
2051
   *
2052
   * Prior to setting the parameters each SetParameterEventCallback registered
2053
   * using setOnParameterEventCallback() is called in succession with the parameters
2054
   * list. Any SetParameterEventCallback that retuns does not return a successful
2055
   * result will cause the entire operation to terminate with no changes to the
2056
   * parameters. When all SetParameterEventCallbacks return successful then the
2057
   * list of parameters is updated.
2058
   *
2059
   * If an error occurs, the process is stopped and returned. Parameters
2060
   * set before an error remain unchanged.
2061
   *
2062
   * @param {Parameter[]} parameters - The parameters to set.
2063
   * @return {rclnodejs.rcl_interfaces.msg.SetParameterResult[]} - A list of SetParameterResult, one for each parameter that was set.
2064
   */
2065
  setParameters(parameters = []) {
×
2066
    return parameters.map((parameter) =>
37✔
2067
      this.setParametersAtomically([parameter])
40✔
2068
    );
2069
  }
2070

2071
  /**
2072
   * Repalce a list of declared parameters atomically.
2073
   *
2074
   * Declared parameters are replaced in the order they are provided.
2075
   * A single ParameterEvent is published collectively for all changed
2076
   * parameters.
2077
   *
2078
   * Prior to setting the parameters each SetParameterEventCallback registered
2079
   * using setOnParameterEventCallback() is called in succession with the parameters
2080
   * list. Any SetParameterEventCallback that retuns does not return a successful
2081
   * result will cause the entire operation to terminate with no changes to the
2082
   * parameters. When all SetParameterEventCallbacks return successful then the
2083
   * list of parameters is updated.d
2084
   *
2085
   * If an error occurs, the process stops immediately. All parameters updated to
2086
   * the point of the error are reverted to their previous state.
2087
   *
2088
   * @param {Parameter[]} parameters - The parameters to set.
2089
   * @return {rclnodejs.rcl_interfaces.msg.SetParameterResult} - describes the result of setting 1 or more parameters.
2090
   */
2091
  setParametersAtomically(parameters = []) {
×
2092
    return this._setParametersAtomically(parameters);
41✔
2093
  }
2094

2095
  /**
2096
   * Internal method for updating parameters atomically.
2097
   *
2098
   * Prior to setting the parameters each SetParameterEventCallback registered
2099
   * using setOnParameterEventCallback() is called in succession with the parameters
2100
   * list. Any SetParameterEventCallback that retuns does not return a successful
2101
   * result will cause the entire operation to terminate with no changes to the
2102
   * parameters. When all SetParameterEventCallbacks return successful then the
2103
   * list of parameters is updated.
2104
   *
2105
   * @param {Paramerter[]} parameters - The parameters to update.
2106
   * @param {boolean} declareParameterMode - When true parameters are being declared;
2107
   *    otherwise they are being changed.
2108
   * @return {SetParameterResult} - A single collective result.
2109
   */
2110
  _setParametersAtomically(parameters = [], declareParameterMode = false) {
41!
2111
    // 1) PRE callbacks — pipeline: each callback receives the output of the previous
2112
    if (this._preSetParametersCallbacks.length > 0) {
2,548✔
2113
      for (const callback of this._preSetParametersCallbacks) {
6✔
2114
        const result = callback(parameters);
7✔
2115
        if (!Array.isArray(result)) {
7!
UNCOV
2116
          return {
×
2117
            successful: false,
2118
            reason:
2119
              'pre-set parameters callback must return an array of Parameters',
2120
          };
2121
        }
2122
        parameters = result;
7✔
2123
        if (parameters.length === 0) {
7✔
2124
          return {
2✔
2125
            successful: false,
2126
            reason:
2127
              'parameter list is empty after pre-set callback; set rejected',
2128
          };
2129
        }
2130
      }
2131
    }
2132

2133
    // 2) Validate
2134
    let result = this._validateParameters(parameters, declareParameterMode);
2,546✔
2135
    if (!result.successful) {
2,546!
UNCOV
2136
      return result;
×
2137
    }
2138

2139
    // give all SetParametersCallbacks a chance to veto this change
2140
    for (const callback of this._setParametersCallbacks) {
2,546✔
2141
      result = callback(parameters);
1,633✔
2142
      if (!result.successful) {
1,633✔
2143
        // a callback has vetoed a parameter change
2144
        return result;
3✔
2145
      }
2146
    }
2147

2148
    // collectively track updates to parameters for use
2149
    // when publishing a ParameterEvent
2150
    const newParameters = [];
2,543✔
2151
    const changedParameters = [];
2,543✔
2152
    const deletedParameters = [];
2,543✔
2153

2154
    for (const parameter of parameters) {
2,543✔
2155
      if (parameter.type == ParameterType.PARAMETER_NOT_SET) {
2,543✔
2156
        this.undeclareParameter(parameter.name);
1✔
2157
        deletedParameters.push(parameter);
1✔
2158
      } else {
2159
        this._parameters.set(parameter.name, parameter);
2,542✔
2160
        if (declareParameterMode) {
2,542✔
2161
          newParameters.push(parameter);
2,507✔
2162
        } else {
2163
          changedParameters.push(parameter);
35✔
2164
        }
2165
      }
2166
    }
2167

2168
    // create ParameterEvent
2169
    const parameterEvent = new (loader.loadInterface(
2,543✔
2170
      PARAMETER_EVENT_MSG_TYPE
2171
    ))();
2172

2173
    const { seconds, nanoseconds } = this._clock.now().secondsAndNanoseconds;
2,543✔
2174
    parameterEvent.stamp = {
2,543✔
2175
      sec: Number(seconds),
2176
      nanosec: Number(nanoseconds),
2177
    };
2178

2179
    parameterEvent.node =
2,543✔
2180
      this.namespace() === '/'
2,543✔
2181
        ? this.namespace() + this.name()
2182
        : this.namespace() + '/' + this.name();
2183

2184
    if (newParameters.length > 0) {
2,543✔
2185
      parameterEvent['new_parameters'] = newParameters.map((parameter) =>
2,507✔
2186
        parameter.toParameterMessage()
2,507✔
2187
      );
2188
    }
2189
    if (changedParameters.length > 0) {
2,543✔
2190
      parameterEvent['changed_parameters'] = changedParameters.map(
35✔
2191
        (parameter) => parameter.toParameterMessage()
35✔
2192
      );
2193
    }
2194
    if (deletedParameters.length > 0) {
2,543✔
2195
      parameterEvent['deleted_parameters'] = deletedParameters.map(
1✔
2196
        (parameter) => parameter.toParameterMessage()
1✔
2197
      );
2198
    }
2199

2200
    // Publish ParameterEvent.
2201
    this._parameterEventPublisher.publish(parameterEvent);
2,543✔
2202

2203
    // POST callbacks — for side effects after successful set
2204
    for (const callback of this._postSetParametersCallbacks) {
2,543✔
2205
      callback(parameters);
4✔
2206
    }
2207

2208
    return {
2,543✔
2209
      successful: true,
2210
      reason: '',
2211
    };
2212
  }
2213

2214
  /**
2215
   * This callback is called when declaring a parameter or setting a parameter.
2216
   * The callback is provided a list of parameters and returns a SetParameterResult
2217
   * to indicate approval or veto of the operation.
2218
   *
2219
   * @callback SetParametersCallback
2220
   * @param {Parameter[]} parameters - The message published
2221
   * @returns {rcl_interfaces.msg.SetParameterResult} -
2222
   *
2223
   * @see [Node.addOnSetParametersCallback]{@link Node#addOnSetParametersCallback}
2224
   * @see [Node.removeOnSetParametersCallback]{@link Node#removeOnSetParametersCallback}
2225
   */
2226

2227
  /**
2228
   * Add a callback to the front of the list of callbacks invoked for parameter declaration
2229
   * and setting. No checks are made for duplicate callbacks.
2230
   *
2231
   * @param {SetParametersCallback} callback - The callback to add.
2232
   * @returns {undefined}
2233
   */
2234
  addOnSetParametersCallback(callback) {
2235
    this._setParametersCallbacks.unshift(callback);
942✔
2236
  }
2237

2238
  /**
2239
   * Remove a callback from the list of SetParametersCallbacks.
2240
   * If the callback is not found the process is a nop.
2241
   *
2242
   * @param {SetParametersCallback} callback - The callback to be removed
2243
   * @returns {undefined}
2244
   */
2245
  removeOnSetParametersCallback(callback) {
2246
    const idx = this._setParametersCallbacks.indexOf(callback);
2✔
2247
    if (idx > -1) {
2!
2248
      this._setParametersCallbacks.splice(idx, 1);
2✔
2249
    }
2250
  }
2251

2252
  /**
2253
   * A callback invoked before parameter validation and setting.
2254
   * It receives the parameter list and must return a (possibly modified) parameter list.
2255
   *
2256
   * @callback PreSetParametersCallback
2257
   * @param {Parameter[]} parameters - The parameters about to be set.
2258
   * @returns {Parameter[]} - The modified parameter list to proceed with.
2259
   *
2260
   * @see [Node.addPreSetParametersCallback]{@link Node#addPreSetParametersCallback}
2261
   * @see [Node.removePreSetParametersCallback]{@link Node#removePreSetParametersCallback}
2262
   */
2263

2264
  /**
2265
   * Add a callback invoked before parameter validation.
2266
   * The callback receives the parameter list and must return a (possibly modified)
2267
   * parameter list. This can be used to coerce, add, or remove parameters before
2268
   * they are validated and applied. If any pre-set callback returns an empty list,
2269
   * the set is rejected.
2270
   *
2271
   * @param {PreSetParametersCallback} callback - The callback to add.
2272
   * @returns {undefined}
2273
   */
2274
  addPreSetParametersCallback(callback) {
2275
    this._preSetParametersCallbacks.unshift(callback);
8✔
2276
  }
2277

2278
  /**
2279
   * Remove a pre-set parameters callback.
2280
   *
2281
   * @param {PreSetParametersCallback} callback - The callback to remove.
2282
   * @returns {undefined}
2283
   */
2284
  removePreSetParametersCallback(callback) {
2285
    const idx = this._preSetParametersCallbacks.indexOf(callback);
1✔
2286
    if (idx > -1) {
1!
2287
      this._preSetParametersCallbacks.splice(idx, 1);
1✔
2288
    }
2289
  }
2290

2291
  /**
2292
   * A callback invoked after parameters have been successfully set.
2293
   * It receives the final parameter list. For side effects only (return value is ignored).
2294
   *
2295
   * @callback PostSetParametersCallback
2296
   * @param {Parameter[]} parameters - The parameters that were set.
2297
   * @returns {undefined}
2298
   *
2299
   * @see [Node.addPostSetParametersCallback]{@link Node#addPostSetParametersCallback}
2300
   * @see [Node.removePostSetParametersCallback]{@link Node#removePostSetParametersCallback}
2301
   */
2302

2303
  /**
2304
   * Add a callback invoked after parameters are successfully set.
2305
   * The callback receives the final parameter list. Useful for triggering
2306
   * side effects (e.g., reconfiguring a component when a parameter changes).
2307
   *
2308
   * @param {PostSetParametersCallback} callback - The callback to add.
2309
   * @returns {undefined}
2310
   */
2311
  addPostSetParametersCallback(callback) {
2312
    this._postSetParametersCallbacks.unshift(callback);
8✔
2313
  }
2314

2315
  /**
2316
   * Remove a post-set parameters callback.
2317
   *
2318
   * @param {PostSetParametersCallback} callback - The callback to remove.
2319
   * @returns {undefined}
2320
   */
2321
  removePostSetParametersCallback(callback) {
2322
    const idx = this._postSetParametersCallbacks.indexOf(callback);
1✔
2323
    if (idx > -1) {
1!
2324
      this._postSetParametersCallbacks.splice(idx, 1);
1✔
2325
    }
2326
  }
2327

2328
  /**
2329
   * Get the fully qualified name of the node.
2330
   *
2331
   * @returns {string} - String containing the fully qualified name of the node.
2332
   */
2333
  getFullyQualifiedName() {
2334
    return rclnodejs.getFullyQualifiedName(this.handle);
1✔
2335
  }
2336

2337
  /**
2338
   * Get the RMW implementation identifier
2339
   * @returns {string} - The RMW implementation identifier.
2340
   */
2341
  getRMWImplementationIdentifier() {
2342
    return rclnodejs.getRMWImplementationIdentifier();
1✔
2343
  }
2344

2345
  /**
2346
   * Return a topic name expanded and remapped.
2347
   * @param {string} topicName - Topic name to be expanded and remapped.
2348
   * @param {boolean} [onlyExpand=false] - If `true`, remapping rules won't be applied.
2349
   * @returns {string} - A fully qualified topic name.
2350
   */
2351
  resolveTopicName(topicName, onlyExpand = false) {
14✔
2352
    if (typeof topicName !== 'string') {
15!
UNCOV
2353
      throw new TypeValidationError('topicName', topicName, 'string', {
×
2354
        nodeName: this.name(),
2355
      });
2356
    }
2357
    return rclnodejs.resolveName(
15✔
2358
      this.handle,
2359
      topicName,
2360
      onlyExpand,
2361
      /*isService=*/ false
2362
    );
2363
  }
2364

2365
  /**
2366
   * Return a service name expanded and remapped.
2367
   * @param {string} service - Service name to be expanded and remapped.
2368
   * @param {boolean} [onlyExpand=false] - If `true`, remapping rules won't be applied.
2369
   * @returns {string} - A fully qualified service name.
2370
   */
2371
  resolveServiceName(service, onlyExpand = false) {
6✔
2372
    if (typeof service !== 'string') {
7!
UNCOV
2373
      throw new TypeValidationError('service', service, 'string', {
×
2374
        nodeName: this.name(),
2375
      });
2376
    }
2377
    return rclnodejs.resolveName(
7✔
2378
      this.handle,
2379
      service,
2380
      onlyExpand,
2381
      /*isService=*/ true
2382
    );
2383
  }
2384

2385
  // returns on 1st error or result {successful, reason}
2386
  _validateParameters(parameters = [], declareParameterMode = false) {
×
2387
    for (const parameter of parameters) {
2,546✔
2388
      // detect invalid parameter
2389
      try {
2,546✔
2390
        parameter.validate();
2,546✔
2391
      } catch {
UNCOV
2392
        return {
×
2393
          successful: false,
2394
          reason: `Invalid ${parameter.name}`,
2395
        };
2396
      }
2397

2398
      // detect undeclared parameter
2399
      if (!this.hasParameterDescriptor(parameter.name)) {
2,546!
UNCOV
2400
        return {
×
2401
          successful: false,
2402
          reason: `Parameter ${parameter.name} has not been declared`,
2403
        };
2404
      }
2405

2406
      // detect readonly parameter that can not be updated
2407
      const descriptor = this.getParameterDescriptor(parameter.name);
2,546✔
2408
      if (!declareParameterMode && descriptor.readOnly) {
2,546!
UNCOV
2409
        return {
×
2410
          successful: false,
2411
          reason: `Parameter ${parameter.name} is readonly`,
2412
        };
2413
      }
2414

2415
      // validate parameter against descriptor if not an undeclare action
2416
      if (parameter.type != ParameterType.PARAMETER_NOT_SET) {
2,546✔
2417
        try {
2,545✔
2418
          descriptor.validateParameter(parameter);
2,545✔
2419
        } catch {
UNCOV
2420
          return {
×
2421
            successful: false,
2422
            reason: `Parameter ${parameter.name} does not  readonly`,
2423
          };
2424
        }
2425
      }
2426
    }
2427

2428
    return {
2,546✔
2429
      successful: true,
2430
      reason: null,
2431
    };
2432
  }
2433

2434
  // Get a Map(nodeName->Parameter[]) of CLI parameter args that
2435
  // apply to 'this' node, .e.g., -p mynode:foo:=bar -p hello:=world
2436
  _getNativeParameterOverrides() {
2437
    const overrides = new Map();
921✔
2438

2439
    // Get native parameters from rcl context->global_arguments.
2440
    // rclnodejs returns an array of objects, 1 for each node e.g., -p my_node:foo:=bar,
2441
    // and a node named '/**' for global parameter rules,
2442
    // i.e., does not include a node identifier, e.g., -p color:=red
2443
    // {
2444
    //   name: string // node name
2445
    //   parameters[] = {
2446
    //     name: string
2447
    //     type: uint
2448
    //     value: object
2449
    // }
2450
    const cliParamOverrideData = rclnodejs.getParameterOverrides(
921✔
2451
      this.context.handle
2452
    );
2453

2454
    // convert native CLI parameterOverrides to Map<nodeName,Array<ParameterOverride>>
2455
    const cliParamOverrides = new Map();
921✔
2456
    if (cliParamOverrideData) {
921✔
2457
      for (let nodeParamData of cliParamOverrideData) {
8✔
2458
        const nodeName = nodeParamData.name;
12✔
2459
        const nodeParamOverrides = [];
12✔
2460
        for (let paramData of nodeParamData.parameters) {
12✔
2461
          const paramOverride = new Parameter(
17✔
2462
            paramData.name,
2463
            paramData.type,
2464
            paramData.value
2465
          );
2466
          nodeParamOverrides.push(paramOverride);
17✔
2467
        }
2468
        cliParamOverrides.set(nodeName, nodeParamOverrides);
12✔
2469
      }
2470
    }
2471

2472
    // collect global CLI global parameters, name == /**
2473
    let paramOverrides = cliParamOverrides.get('/**'); // array of ParameterOverrides
921✔
2474
    if (paramOverrides) {
921✔
2475
      for (const parameter of paramOverrides) {
5✔
2476
        overrides.set(parameter.name, parameter);
6✔
2477
      }
2478
    }
2479

2480
    // merge CLI node parameterOverrides with global parameterOverrides, replace existing
2481
    paramOverrides = cliParamOverrides.get(this.name()); // array of ParameterOverrides
921✔
2482
    if (paramOverrides) {
921✔
2483
      for (const parameter of paramOverrides) {
5✔
2484
        overrides.set(parameter.name, parameter);
7✔
2485
      }
2486
    }
2487

2488
    return overrides;
921✔
2489
  }
2490

2491
  /**
2492
   * Invokes the callback with a raw message of the given type. After the callback completes
2493
   * the message will be destroyed.
2494
   * @param {function} Type - Message type to create.
2495
   * @param {function} callback - Callback to invoke. First parameter will be the raw message,
2496
   * and the second is a function to retrieve the deserialized message.
2497
   * @returns {undefined}
2498
   */
2499
  _runWithMessageType(Type, callback) {
2500
    let message = new Type();
866✔
2501

2502
    callback(message.toRawROS(), () => {
866✔
2503
      let result = new Type();
817✔
2504
      result.deserialize(message.refObject);
817✔
2505

2506
      return result;
817✔
2507
    });
2508

2509
    Type.destroyRawROS(message);
866✔
2510
  }
2511

2512
  _addActionClient(actionClient) {
2513
    this._actionClients.push(actionClient);
52✔
2514
    this.syncHandles();
52✔
2515
  }
2516

2517
  _addActionServer(actionServer) {
2518
    this._actionServers.push(actionServer);
52✔
2519
    this.syncHandles();
52✔
2520
  }
2521

2522
  _getValidatedTopic(topicName, noDemangle) {
2523
    if (noDemangle) {
6!
UNCOV
2524
      return topicName;
×
2525
    }
2526
    const fqTopicName = rclnodejs.expandTopicName(
6✔
2527
      topicName,
2528
      this.name(),
2529
      this.namespace()
2530
    );
2531
    validateFullTopicName(fqTopicName);
6✔
2532
    return rclnodejs.remapTopicName(this.handle, fqTopicName);
6✔
2533
  }
2534

2535
  _getValidatedServiceName(serviceName, noDemangle) {
2536
    if (typeof serviceName !== 'string') {
4!
UNCOV
2537
      throw new TypeValidationError('serviceName', serviceName, 'string', {
×
2538
        nodeName: this.name(),
2539
      });
2540
    }
2541

2542
    if (noDemangle) {
4!
UNCOV
2543
      return serviceName;
×
2544
    }
2545

2546
    const resolvedServiceName = this.resolveServiceName(serviceName);
4✔
2547
    rclnodejs.validateTopicName(resolvedServiceName);
4✔
2548
    return resolvedServiceName;
4✔
2549
  }
2550
}
2551

2552
/**
2553
 * Create an Options instance initialized with default values.
2554
 * @returns {Options} - The new initialized instance.
2555
 * @static
2556
 * @example
2557
 * {
2558
 *   enableTypedArray: true,
2559
 *   isRaw: false,
2560
 *   qos: QoS.profileDefault,
2561
 *   contentFilter: undefined,
2562
 *   serializationMode: 'default',
2563
 * }
2564
 */
2565
Node.getDefaultOptions = function () {
26✔
2566
  return {
8,434✔
2567
    enableTypedArray: true,
2568
    isRaw: false,
2569
    qos: QoS.profileDefault,
2570
    contentFilter: undefined,
2571
    serializationMode: 'default',
2572
  };
2573
};
2574

2575
module.exports = Node;
26✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc