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

RobotWebTools / rclnodejs / 23888194586

02 Apr 2026 06:57AM UTC coverage: 85.514% (-0.3%) from 85.83%
23888194586

Pull #1468

github

web-flow
Merge 7152ab21f into bd80eec87
Pull Request #1468: Add QoS overriding options for publishers and subscriptions

1524 of 1936 branches covered (78.72%)

Branch coverage included in aggregate %.

57 of 80 new or added lines in 2 files covered. (71.25%)

49 existing lines in 1 file now uncovered.

3116 of 3490 relevant lines covered (89.28%)

439.31 hits per line

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

86.94
/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 = [],
103✔
87
    useGlobalArguments = true
103✔
88
  ) {
89
    super();
923✔
90

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

98
    this._init(nodeName, namespace, options, context, args, useGlobalArguments);
901✔
99
    debug(
890✔
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) {
901✔
108
      return options;
899✔
109
    }
110
    const defaults = NodeOptions.defaultOptions;
2✔
111
    return {
2✔
112
      startParameterServices:
113
        options.startParameterServices ?? defaults.startParameterServices,
4✔
114
      parameterOverrides:
115
        options.parameterOverrides ?? defaults.parameterOverrides,
4✔
116
      automaticallyDeclareParametersFromOverrides:
117
        options.automaticallyDeclareParametersFromOverrides ??
4✔
118
        defaults.automaticallyDeclareParametersFromOverrides,
119
      startTypeDescriptionService:
120
        options.startTypeDescriptionService ??
4✔
121
        defaults.startTypeDescriptionService,
122
      enableRosout: options.enableRosout ?? defaults.enableRosout,
3✔
123
      rosoutQos: options.rosoutQos ?? defaults.rosoutQos,
4✔
124
    };
125
  }
126

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

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

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

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

169
    if (this._enableRosout) {
891✔
170
      rclnodejs.initRosoutPublisherForNode(this.handle);
889✔
171
    }
172

173
    this._parameterEventPublisher = this.createPublisher(
891✔
174
      PARAMETER_EVENT_MSG_TYPE,
175
      PARAMETER_EVENT_TOPIC
176
    );
177

178
    // initialize _parameterOverrides from parameters defined on the commandline
179
    this._parameterOverrides = this._getNativeParameterOverrides();
891✔
180

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

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

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

215
    if (options.startParameterServices) {
890✔
216
      this._parameterService = new ParameterService(this);
884✔
217
      this._parameterService.start();
884✔
218
    }
219

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

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

255
    timersReady.forEach((timer) => {
4,635✔
256
      if (timer.isReady()) {
3,836!
257
        rclnodejs.callTimer(timer.handle);
3,836✔
258
        timer.callback();
3,836✔
259
      }
260
    });
261

262
    eventsReady.forEach((event) => {
4,635✔
263
      event.takeData();
4✔
264
    });
265

266
    for (const subscription of subscriptionsReady) {
4,635✔
267
      if (subscription.isDestroyed()) continue;
399✔
268
      if (subscription.isRaw) {
390✔
269
        let rawMessage = rclnodejs.rclTakeRaw(subscription.handle);
1✔
270
        if (rawMessage) {
1!
271
          subscription.processResponse(rawMessage);
1✔
272
        }
273
        continue;
1✔
274
      }
275

276
      this._runWithMessageType(
389✔
277
        subscription.typeClass,
278
        (message, deserialize) => {
279
          if (subscription.wantsMessageInfo) {
389✔
280
            let rawInfo = rclnodejs.rclTakeWithInfo(
4✔
281
              subscription.handle,
282
              message
283
            );
284
            if (rawInfo) {
4!
285
              subscription.processResponse(
4✔
286
                deserialize(),
287
                new MessageInfo(rawInfo)
288
              );
289
            }
290
          } else {
291
            let success = rclnodejs.rclTake(subscription.handle, message);
385✔
292
            if (success) {
385✔
293
              subscription.processResponse(deserialize());
382✔
294
            }
295
          }
296
        }
297
      );
298
    }
299

300
    for (const guard of guardsReady) {
4,635✔
301
      if (guard.isDestroyed()) continue;
3!
302

303
      guard.callback();
3✔
304
    }
305

306
    for (const client of clientsReady) {
4,635✔
307
      if (client.isDestroyed()) continue;
87!
308
      this._runWithMessageType(
87✔
309
        client.typeClass.Response,
310
        (message, deserialize) => {
311
          let sequenceNumber = rclnodejs.rclTakeResponse(
87✔
312
            client.handle,
313
            message
314
          );
315
          if (sequenceNumber !== undefined) {
87✔
316
            client.processResponse(sequenceNumber, deserialize());
86✔
317
          }
318
        }
319
      );
320
    }
321

322
    for (const service of servicesReady) {
4,635✔
323
      if (service.isDestroyed()) continue;
124!
324
      this._runWithMessageType(
124✔
325
        service.typeClass.Request,
326
        (message, deserialize) => {
327
          let header = rclnodejs.rclTakeRequest(
124✔
328
            service.handle,
329
            this.handle,
330
            message
331
          );
332
          if (header) {
124✔
333
            service.processRequest(header, deserialize());
91✔
334
          }
335
        }
336
      );
337
    }
338

339
    for (const actionClient of actionClientsReady) {
4,635✔
340
      if (actionClient.isDestroyed()) continue;
124!
341

342
      const properties = actionClient.handle.properties;
124✔
343

344
      if (properties.isGoalResponseReady) {
124✔
345
        this._runWithMessageType(
56✔
346
          actionClient.typeClass.impl.SendGoalService.Response,
347
          (message, deserialize) => {
348
            let sequence = rclnodejs.actionTakeGoalResponse(
56✔
349
              actionClient.handle,
350
              message
351
            );
352
            if (sequence != undefined) {
56✔
353
              actionClient.processGoalResponse(sequence, deserialize());
49✔
354
            }
355
          }
356
        );
357
      }
358

359
      if (properties.isCancelResponseReady) {
124✔
360
        this._runWithMessageType(
5✔
361
          actionClient.typeClass.impl.CancelGoal.Response,
362
          (message, deserialize) => {
363
            let sequence = rclnodejs.actionTakeCancelResponse(
5✔
364
              actionClient.handle,
365
              message
366
            );
367
            if (sequence != undefined) {
5!
368
              actionClient.processCancelResponse(sequence, deserialize());
5✔
369
            }
370
          }
371
        );
372
      }
373

374
      if (properties.isResultResponseReady) {
124✔
375
        this._runWithMessageType(
32✔
376
          actionClient.typeClass.impl.GetResultService.Response,
377
          (message, deserialize) => {
378
            let sequence = rclnodejs.actionTakeResultResponse(
32✔
379
              actionClient.handle,
380
              message
381
            );
382
            if (sequence != undefined) {
32!
383
              actionClient.processResultResponse(sequence, deserialize());
32✔
384
            }
385
          }
386
        );
387
      }
388

389
      if (properties.isFeedbackReady) {
124✔
390
        this._runWithMessageType(
9✔
391
          actionClient.typeClass.impl.FeedbackMessage,
392
          (message, deserialize) => {
393
            let success = rclnodejs.actionTakeFeedback(
9✔
394
              actionClient.handle,
395
              message
396
            );
397
            if (success) {
9!
398
              actionClient.processFeedbackMessage(deserialize());
9✔
399
            }
400
          }
401
        );
402
      }
403

404
      if (properties.isStatusReady) {
124✔
405
        this._runWithMessageType(
71✔
406
          actionClient.typeClass.impl.GoalStatusArray,
407
          (message, deserialize) => {
408
            let success = rclnodejs.actionTakeStatus(
71✔
409
              actionClient.handle,
410
              message
411
            );
412
            if (success) {
71!
413
              actionClient.processStatusMessage(deserialize());
71✔
414
            }
415
          }
416
        );
417
      }
418
    }
419

420
    for (const actionServer of actionServersReady) {
4,635✔
421
      if (actionServer.isDestroyed()) continue;
95!
422

423
      const properties = actionServer.handle.properties;
95✔
424

425
      if (properties.isGoalRequestReady) {
95✔
426
        this._runWithMessageType(
54✔
427
          actionServer.typeClass.impl.SendGoalService.Request,
428
          (message, deserialize) => {
429
            const result = rclnodejs.actionTakeGoalRequest(
54✔
430
              actionServer.handle,
431
              message
432
            );
433
            if (result) {
54✔
434
              actionServer.processGoalRequest(result, deserialize());
49✔
435
            }
436
          }
437
        );
438
      }
439

440
      if (properties.isCancelRequestReady) {
95✔
441
        this._runWithMessageType(
5✔
442
          actionServer.typeClass.impl.CancelGoal.Request,
443
          (message, deserialize) => {
444
            const result = rclnodejs.actionTakeCancelRequest(
5✔
445
              actionServer.handle,
446
              message
447
            );
448
            if (result) {
5!
449
              actionServer.processCancelRequest(result, deserialize());
5✔
450
            }
451
          }
452
        );
453
      }
454

455
      if (properties.isResultRequestReady) {
95✔
456
        this._runWithMessageType(
32✔
457
          actionServer.typeClass.impl.GetResultService.Request,
458
          (message, deserialize) => {
459
            const result = rclnodejs.actionTakeResultRequest(
32✔
460
              actionServer.handle,
461
              message
462
            );
463
            if (result) {
32!
464
              actionServer.processResultRequest(result, deserialize());
32✔
465
            }
466
          }
467
        );
468
      }
469

470
      if (properties.isGoalExpired) {
95✔
471
        let numGoals = actionServer._goalHandles.size;
4✔
472
        if (numGoals > 0) {
4!
473
          let GoalInfoArray = ActionInterfaces.GoalInfo.ArrayType;
4✔
474
          let message = new GoalInfoArray(numGoals);
4✔
475
          let count = rclnodejs.actionExpireGoals(
4✔
476
            actionServer.handle,
477
            numGoals,
478
            message._refArray.buffer
479
          );
480
          if (count > 0) {
4!
481
            actionServer.processGoalExpired(message, count);
4✔
482
          }
483
          GoalInfoArray.freeArray(message);
4✔
484
        }
485
      }
486
    }
487

488
    // At this point it is safe to clear the cache of any
489
    // destroyed entity references
490
    Entity._gcHandles();
4,635✔
491
  }
492

493
  /**
494
   * Determine if this node is spinning.
495
   * @returns {boolean} - true when spinning; otherwise returns false.
496
   */
497
  get spinning() {
498
    return this._spinning;
4,617✔
499
  }
500

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

517
  /**
518
   * Use spin().
519
   * @deprecated, since 0.18.0
520
   */
521
  startSpinning(timeout) {
522
    this.spin(timeout);
×
523
  }
524

525
  /**
526
   * Terminate spinning - no further events will be received.
527
   * @returns {undefined}
528
   */
529
  stop() {
530
    super.stop();
692✔
531
    this._spinning = false;
692✔
532
  }
533

534
  /**
535
   * Terminate spinning - no further events will be received.
536
   * @returns {undefined}
537
   * @deprecated since 0.18.0, Use stop().
538
   */
539
  stopSpinning() {
540
    super.stop();
×
541
    this._spinning = false;
×
542
  }
543

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

559
  _removeEntityFromArray(entity, array) {
560
    let index = array.indexOf(entity);
397✔
561
    if (index > -1) {
397✔
562
      array.splice(index, 1);
395✔
563
    }
564
  }
565

566
  _destroyEntity(entity, array, syncHandles = true) {
337✔
567
    if (entity['isDestroyed'] && entity.isDestroyed()) return;
349✔
568

569
    this._removeEntityFromArray(entity, array);
341✔
570
    if (syncHandles) {
341✔
571
      this.syncHandles();
331✔
572
    }
573

574
    if (entity['_destroy']) {
341✔
575
      entity._destroy();
335✔
576
    } else {
577
      // guards and timers
578
      entity.handle.release();
6✔
579
    }
580
  }
581

582
  _validateOptions(options) {
583
    if (
8,250✔
584
      options !== undefined &&
8,352✔
585
      (options === null || typeof options !== 'object')
586
    ) {
587
      throw new TypeValidationError('options', options, 'object', {
20✔
588
        nodeName: this.name(),
589
      });
590
    }
591

592
    if (options === undefined) {
8,230✔
593
      return Node.getDefaultOptions();
8,189✔
594
    }
595

596
    if (options.enableTypedArray === undefined) {
41✔
597
      options = Object.assign(options, { enableTypedArray: true });
27✔
598
    }
599

600
    if (options.qos === undefined) {
41✔
601
      options = Object.assign(options, { qos: QoS.profileDefault });
20✔
602
    }
603

604
    if (options.isRaw === undefined) {
41✔
605
      options = Object.assign(options, { isRaw: false });
30✔
606
    }
607

608
    if (options.serializationMode === undefined) {
41✔
609
      options = Object.assign(options, { serializationMode: 'default' });
24✔
610
    } else if (!isValidSerializationMode(options.serializationMode)) {
17✔
611
      throw new ValidationError(
1✔
612
        `Invalid serializationMode: ${options.serializationMode}. Valid modes are: 'default', 'plain', 'json'`,
613
        {
614
          code: 'INVALID_SERIALIZATION_MODE',
615
          argumentName: 'serializationMode',
616
          providedValue: options.serializationMode,
617
          expectedType: "'default' | 'plain' | 'json'",
618
          nodeName: this.name(),
619
        }
620
      );
621
    }
622

623
    return options;
40✔
624
  }
625

626
  /**
627
   * Create a Timer.
628
   * @param {bigint} period - The number representing period in nanoseconds.
629
   * @param {function} callback - The callback to be called when timeout.
630
   * @param {Clock} [clock] - The clock which the timer gets time from.
631
   * @return {Timer} - An instance of Timer.
632
   */
633
  createTimer(period, callback, clock = null) {
67✔
634
    if (arguments.length === 3 && !(arguments[2] instanceof Clock)) {
67!
635
      clock = null;
×
636
    } else if (arguments.length === 4) {
67!
637
      clock = arguments[3];
×
638
    }
639

640
    if (typeof period !== 'bigint') {
67✔
641
      throw new TypeValidationError('period', period, 'bigint', {
1✔
642
        nodeName: this.name(),
643
      });
644
    }
645
    if (typeof callback !== 'function') {
66✔
646
      throw new TypeValidationError('callback', callback, 'function', {
1✔
647
        nodeName: this.name(),
648
      });
649
    }
650

651
    const timerClock = clock || this._clock;
65✔
652
    let timerHandle = rclnodejs.createTimer(
65✔
653
      timerClock.handle,
654
      this.context.handle,
655
      period
656
    );
657
    let timer = new Timer(timerHandle, period, callback);
65✔
658
    debug('Finish creating timer, period = %d.', period);
65✔
659
    this._timers.push(timer);
65✔
660
    this.syncHandles();
65✔
661

662
    return timer;
65✔
663
  }
664

665
  /**
666
   * Create a Rate.
667
   *
668
   * @param {number} hz - The frequency of the rate timer; default is 1 hz.
669
   * @returns {Promise<Rate>} - Promise resolving to new instance of Rate.
670
   */
671
  async createRate(hz = 1) {
4✔
672
    if (typeof hz !== 'number') {
9!
673
      throw new TypeValidationError('hz', hz, 'number', {
×
674
        nodeName: this.name(),
675
      });
676
    }
677

678
    const MAX_RATE_HZ_IN_MILLISECOND = 1000.0;
9✔
679
    if (hz <= 0.0 || hz > MAX_RATE_HZ_IN_MILLISECOND) {
9✔
680
      throw new RangeValidationError(
2✔
681
        'hz',
682
        hz,
683
        `0.0 < hz <= ${MAX_RATE_HZ_IN_MILLISECOND}`,
684
        {
685
          nodeName: this.name(),
686
        }
687
      );
688
    }
689

690
    // lazy initialize rateTimerServer
691
    if (!this._rateTimerServer) {
7✔
692
      this._rateTimerServer = new Rates.RateTimerServer(this);
5✔
693
      await this._rateTimerServer.init();
5✔
694
    }
695

696
    const period = Math.round(1000 / hz);
7✔
697
    const timer = this._rateTimerServer.createTimer(BigInt(period) * 1000000n);
7✔
698
    const rate = new Rates.Rate(hz, timer);
7✔
699

700
    return rate;
7✔
701
  }
702

703
  /**
704
   * Create a Publisher.
705
   * @param {function|string|object} typeClass - The ROS message class,
706
        OR a string representing the message class, e.g. 'std_msgs/msg/String',
707
        OR an object representing the message class, e.g. {package: 'std_msgs', type: 'msg', name: 'String'}
708
   * @param {string} topic - The name of the topic.
709
   * @param {object} options - The options argument used to parameterize the publisher.
710
   * @param {boolean} options.enableTypedArray - The topic will use TypedArray if necessary, default: true.
711
   * @param {QoS} options.qos - ROS Middleware "quality of service" settings for the publisher, default: QoS.profileDefault.
712
   * @param {PublisherEventCallbacks} eventCallbacks - The event callbacks for the publisher.
713
   * @return {Publisher} - An instance of Publisher.
714
   */
715
  createPublisher(typeClass, topic, options, eventCallbacks) {
716
    return this._createPublisher(
1,270✔
717
      typeClass,
718
      topic,
719
      options,
720
      Publisher,
721
      eventCallbacks
722
    );
723
  }
724

725
  _createPublisher(typeClass, topic, options, publisherClass, eventCallbacks) {
726
    if (typeof typeClass === 'string' || typeof typeClass === 'object') {
1,273✔
727
      typeClass = loader.loadInterface(typeClass);
1,249✔
728
    }
729
    options = this._validateOptions(options);
1,266✔
730

731
    if (typeof typeClass !== 'function') {
1,266✔
732
      throw new TypeValidationError('typeClass', typeClass, 'function', {
8✔
733
        nodeName: this.name(),
734
        entityType: 'publisher',
735
      });
736
    }
737
    if (typeof topic !== 'string') {
1,258✔
738
      throw new TypeValidationError('topic', topic, 'string', {
12✔
739
        nodeName: this.name(),
740
        entityType: 'publisher',
741
      });
742
    }
743
    if (
1,246!
744
      eventCallbacks &&
1,248✔
745
      !(eventCallbacks instanceof PublisherEventCallbacks)
746
    ) {
UNCOV
747
      throw new TypeValidationError(
×
748
        'eventCallbacks',
749
        eventCallbacks,
750
        'PublisherEventCallbacks',
751
        {
752
          nodeName: this.name(),
753
          entityType: 'publisher',
754
          entityName: topic,
755
        }
756
      );
757
    }
758

759
    // Apply QoS overriding options if provided
760
    if (options.qosOverridingOptions) {
1,246✔
761
      const resolvedTopic = this.resolveTopicName(topic);
5✔
762
      if (typeof options.qos === 'string' || !(options.qos instanceof QoS)) {
5✔
763
        options.qos = _resolveQoS(options.qos);
2✔
764
      }
765
      declareQosParameters(
5✔
766
        'publisher',
767
        this,
768
        resolvedTopic,
769
        options.qos,
770
        options.qosOverridingOptions
771
      );
772
    }
773

774
    let publisher = publisherClass.createPublisher(
1,245✔
775
      this,
776
      typeClass,
777
      topic,
778
      options,
779
      eventCallbacks
780
    );
781
    debug('Finish creating publisher, topic = %s.', topic);
1,235✔
782
    this._publishers.push(publisher);
1,235✔
783
    return publisher;
1,235✔
784
  }
785

786
  /**
787
   * This callback is called when a message is published
788
   * @callback SubscriptionCallback
789
   * @param {Object} message - The message published
790
   * @see [Node.createSubscription]{@link Node#createSubscription}
791
   * @see [Node.createPublisher]{@link Node#createPublisher}
792
   * @see {@link Publisher}
793
   * @see {@link Subscription}
794
   */
795

796
  /**
797
   * Create a Subscription with optional content-filtering.
798
   * @param {function|string|object} typeClass - The ROS message class,
799
        OR a string representing the message class, e.g. 'std_msgs/msg/String',
800
        OR an object representing the message class, e.g. {package: 'std_msgs', type: 'msg', name: 'String'}
801
   * @param {string} topic - The name of the topic.
802
   * @param {object} options - The options argument used to parameterize the subscription.
803
   * @param {boolean} options.enableTypedArray - The topic will use TypedArray if necessary, default: true.
804
   * @param {QoS} options.qos - ROS Middleware "quality of service" settings for the subscription, default: QoS.profileDefault.
805
   * @param {boolean} options.isRaw - The topic is serialized when true, default: false.
806
   * @param {string} [options.serializationMode='default'] - Controls message serialization format:
807
   *  'default': Use native rclnodejs behavior (respects enableTypedArray setting),
808
   *  'plain': Convert TypedArrays to regular arrays,
809
   *  'json': Fully JSON-safe (handles TypedArrays, BigInt, etc.).
810
   * @param {object} [options.contentFilter=undefined] - The content-filter, default: undefined.
811
   *  Confirm that your RMW supports content-filtered topics before use. 
812
   * @param {string} options.contentFilter.expression - Specifies the criteria to select the data samples of
813
   *  interest. It is similar to the WHERE part of an SQL clause.
814
   * @param {string[]} [options.contentFilter.parameters=undefined] - Array of strings that give values to
815
   *  the ‘parameters’ (i.e., "%n" tokens) in the filter_expression. The number of supplied parameters must
816
   *  fit with the requested values in the filter_expression (i.e., the number of %n tokens). default: undefined.
817
   * @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.
818
   * @param {SubscriptionEventCallbacks} eventCallbacks - The event callbacks for the subscription.
819
   * @return {Subscription} - An instance of Subscription.
820
   * @throws {ERROR} - May throw an RMW error if content-filter is malformed. 
821
   * @see {@link SubscriptionCallback}
822
   * @see {@link https://www.omg.org/spec/DDS/1.4/PDF|Content-filter details at DDS 1.4 specification, Annex B}
823
   */
824
  createSubscription(typeClass, topic, options, callback, eventCallbacks) {
825
    if (typeof typeClass === 'string' || typeof typeClass === 'object') {
466✔
826
      typeClass = loader.loadInterface(typeClass);
457✔
827
    }
828

829
    if (typeof options === 'function') {
459✔
830
      callback = options;
343✔
831
      options = undefined;
343✔
832
    }
833
    options = this._validateOptions(options);
459✔
834

835
    if (typeof typeClass !== 'function') {
449✔
836
      throw new TypeValidationError('typeClass', typeClass, 'function', {
4✔
837
        nodeName: this.name(),
838
        entityType: 'subscription',
839
      });
840
    }
841
    if (typeof topic !== 'string') {
445✔
842
      throw new TypeValidationError('topic', topic, 'string', {
6✔
843
        nodeName: this.name(),
844
        entityType: 'subscription',
845
      });
846
    }
847
    if (typeof callback !== 'function') {
439!
UNCOV
848
      throw new TypeValidationError('callback', callback, 'function', {
×
849
        nodeName: this.name(),
850
        entityType: 'subscription',
851
        entityName: topic,
852
      });
853
    }
854
    if (
439!
855
      eventCallbacks &&
443✔
856
      !(eventCallbacks instanceof SubscriptionEventCallbacks)
857
    ) {
UNCOV
858
      throw new TypeValidationError(
×
859
        'eventCallbacks',
860
        eventCallbacks,
861
        'SubscriptionEventCallbacks',
862
        {
863
          nodeName: this.name(),
864
          entityType: 'subscription',
865
          entityName: topic,
866
        }
867
      );
868
    }
869

870
    // Apply QoS overriding options if provided
871
    if (options.qosOverridingOptions) {
439✔
872
      const resolvedTopic = this.resolveTopicName(topic);
1✔
873
      if (typeof options.qos === 'string' || !(options.qos instanceof QoS)) {
1!
NEW
UNCOV
874
        options.qos = _resolveQoS(options.qos);
×
875
      }
876
      declareQosParameters(
1✔
877
        'subscription',
878
        this,
879
        resolvedTopic,
880
        options.qos,
881
        options.qosOverridingOptions
882
      );
883
    }
884

885
    let subscription = Subscription.createSubscription(
439✔
886
      this,
887
      typeClass,
888
      topic,
889
      options,
890
      callback,
891
      eventCallbacks
892
    );
893
    debug('Finish creating subscription, topic = %s.', topic);
428✔
894
    this._subscriptions.push(subscription);
428✔
895
    this.syncHandles();
428✔
896

897
    return subscription;
428✔
898
  }
899

900
  /**
901
   * Create a Subscription that returns an RxJS Observable.
902
   * This allows using reactive programming patterns with ROS 2 messages.
903
   *
904
   * @param {function|string|object} typeClass - The ROS message class,
905
   *      OR a string representing the message class, e.g. 'std_msgs/msg/String',
906
   *      OR an object representing the message class, e.g. {package: 'std_msgs', type: 'msg', name: 'String'}
907
   * @param {string} topic - The name of the topic.
908
   * @param {object} [options] - The options argument used to parameterize the subscription.
909
   * @param {boolean} [options.enableTypedArray=true] - The topic will use TypedArray if necessary.
910
   * @param {QoS} [options.qos=QoS.profileDefault] - ROS Middleware "quality of service" settings.
911
   * @param {boolean} [options.isRaw=false] - The topic is serialized when true.
912
   * @param {string} [options.serializationMode='default'] - Controls message serialization format.
913
   * @param {object} [options.contentFilter] - The content-filter (if supported by RMW).
914
   * @param {SubscriptionEventCallbacks} [eventCallbacks] - The event callbacks for the subscription.
915
   * @return {ObservableSubscription} - An ObservableSubscription with an RxJS Observable.
916
   */
917
  createObservableSubscription(typeClass, topic, options, eventCallbacks) {
918
    let observableSubscription = null;
7✔
919

920
    const subscription = this.createSubscription(
7✔
921
      typeClass,
922
      topic,
923
      options,
924
      (message) => {
925
        if (observableSubscription) {
8!
926
          observableSubscription._emit(message);
8✔
927
        }
928
      },
929
      eventCallbacks
930
    );
931

932
    observableSubscription = new ObservableSubscription(subscription);
7✔
933
    return observableSubscription;
7✔
934
  }
935

936
  /**
937
   * Create a Client.
938
   * @param {function|string|object} typeClass - The ROS message class,
939
        OR a string representing the message class, e.g. 'std_msgs/msg/String',
940
        OR an object representing the message class, e.g. {package: 'std_msgs', type: 'msg', name: 'String'}
941
   * @param {string} serviceName - The service name to request.
942
   * @param {object} options - The options argument used to parameterize the client.
943
   * @param {boolean} options.enableTypedArray - The response will use TypedArray if necessary, default: true.
944
   * @param {QoS} options.qos - ROS Middleware "quality of service" settings for the client, default: QoS.profileDefault.
945
   * @return {Client} - An instance of Client.
946
   */
947
  createClient(typeClass, serviceName, options) {
948
    if (typeof typeClass === 'string' || typeof typeClass === 'object') {
196✔
949
      typeClass = loader.loadInterface(typeClass);
186✔
950
    }
951
    options = this._validateOptions(options);
189✔
952

953
    if (typeof typeClass !== 'function') {
189✔
954
      throw new TypeValidationError('typeClass', typeClass, 'function', {
8✔
955
        nodeName: this.name(),
956
        entityType: 'client',
957
      });
958
    }
959
    if (typeof serviceName !== 'string') {
181✔
960
      throw new TypeValidationError('serviceName', serviceName, 'string', {
12✔
961
        nodeName: this.name(),
962
        entityType: 'client',
963
      });
964
    }
965

966
    let client = Client.createClient(
169✔
967
      this.handle,
968
      serviceName,
969
      typeClass,
970
      options
971
    );
972
    debug('Finish creating client, service = %s.', serviceName);
159✔
973
    this._clients.push(client);
159✔
974
    this.syncHandles();
159✔
975

976
    return client;
159✔
977
  }
978

979
  /**
980
   * This callback is called when a request is sent to service
981
   * @callback RequestCallback
982
   * @param {Object} request - The request sent to the service
983
   * @param {Response} response - The response to client.
984
        Use [response.send()]{@link Response#send} to send response object to client
985
   * @return {undefined}
986
   * @see [Node.createService]{@link Node#createService}
987
   * @see [Client.sendRequest]{@link Client#sendRequest}
988
   * @see {@link Client}
989
   * @see {@link Service}
990
   * @see {@link Response#send}
991
   */
992

993
  /**
994
   * Create a Service.
995
   * @param {function|string|object} typeClass - The ROS message class,
996
        OR a string representing the message class, e.g. 'std_msgs/msg/String',
997
        OR an object representing the message class, e.g. {package: 'std_msgs', type: 'msg', name: 'String'}
998
   * @param {string} serviceName - The service name to offer.
999
   * @param {object} options - The options argument used to parameterize the service.
1000
   * @param {boolean} options.enableTypedArray - The request will use TypedArray if necessary, default: true.
1001
   * @param {QoS} options.qos - ROS Middleware "quality of service" settings for the service, default: QoS.profileDefault.
1002
   * @param {RequestCallback} callback - The callback to be called when receiving request.
1003
   * @return {Service} - An instance of Service.
1004
   * @see {@link RequestCallback}
1005
   */
1006
  createService(typeClass, serviceName, options, callback) {
1007
    if (typeof typeClass === 'string' || typeof typeClass === 'object') {
5,377✔
1008
      typeClass = loader.loadInterface(typeClass);
5,367✔
1009
    }
1010

1011
    if (typeof options === 'function') {
5,370✔
1012
      callback = options;
5,347✔
1013
      options = undefined;
5,347✔
1014
    }
1015
    options = this._validateOptions(options);
5,370✔
1016

1017
    if (typeof typeClass !== 'function') {
5,360✔
1018
      throw new TypeValidationError('typeClass', typeClass, 'function', {
4✔
1019
        nodeName: this.name(),
1020
        entityType: 'service',
1021
      });
1022
    }
1023
    if (typeof serviceName !== 'string') {
5,356✔
1024
      throw new TypeValidationError('serviceName', serviceName, 'string', {
6✔
1025
        nodeName: this.name(),
1026
        entityType: 'service',
1027
      });
1028
    }
1029
    if (typeof callback !== 'function') {
5,350!
UNCOV
1030
      throw new TypeValidationError('callback', callback, 'function', {
×
1031
        nodeName: this.name(),
1032
        entityType: 'service',
1033
        entityName: serviceName,
1034
      });
1035
    }
1036

1037
    let service = Service.createService(
5,350✔
1038
      this.handle,
1039
      serviceName,
1040
      typeClass,
1041
      options,
1042
      callback
1043
    );
1044
    debug('Finish creating service, service = %s.', serviceName);
5,340✔
1045
    this._services.push(service);
5,340✔
1046
    this.syncHandles();
5,340✔
1047

1048
    return service;
5,340✔
1049
  }
1050

1051
  /**
1052
   * Create a ParameterClient for accessing parameters on a remote node.
1053
   * @param {string} remoteNodeName - The name of the remote node whose parameters to access.
1054
   * @param {object} [options] - Options for parameter client.
1055
   * @param {number} [options.timeout=5000] - Default timeout in milliseconds for service calls.
1056
   * @return {ParameterClient} - An instance of ParameterClient.
1057
   */
1058
  createParameterClient(remoteNodeName, options = {}) {
50✔
1059
    if (typeof remoteNodeName !== 'string' || remoteNodeName.trim() === '') {
103!
UNCOV
1060
      throw new TypeError('Remote node name must be a non-empty string');
×
1061
    }
1062

1063
    const parameterClient = new ParameterClient(this, remoteNodeName, options);
103✔
1064
    debug(
103✔
1065
      'Finish creating parameter client for remote node = %s.',
1066
      remoteNodeName
1067
    );
1068
    this._parameterClients.push(parameterClient);
103✔
1069

1070
    return parameterClient;
103✔
1071
  }
1072

1073
  /**
1074
   * Create a ParameterWatcher for watching parameter changes on a remote node.
1075
   * @param {string} remoteNodeName - The name of the remote node whose parameters to watch.
1076
   * @param {string[]} parameterNames - Array of parameter names to watch.
1077
   * @param {object} [options] - Options for parameter watcher.
1078
   * @param {number} [options.timeout=5000] - Default timeout in milliseconds for service calls.
1079
   * @return {ParameterWatcher} - An instance of ParameterWatcher.
1080
   */
1081
  createParameterWatcher(remoteNodeName, parameterNames, options = {}) {
56✔
1082
    const watcher = new ParameterWatcher(
57✔
1083
      this,
1084
      remoteNodeName,
1085
      parameterNames,
1086
      options
1087
    );
1088
    debug(
53✔
1089
      'Finish creating parameter watcher for remote node = %s.',
1090
      remoteNodeName
1091
    );
1092
    this._parameterWatchers.push(watcher);
53✔
1093

1094
    return watcher;
53✔
1095
  }
1096

1097
  /**
1098
   * Create a guard condition.
1099
   * @param {Function} callback - The callback to be called when the guard condition is triggered.
1100
   * @return {GuardCondition} - An instance of GuardCondition.
1101
   */
1102
  createGuardCondition(callback) {
1103
    if (typeof callback !== 'function') {
3!
UNCOV
1104
      throw new TypeValidationError('callback', callback, 'function', {
×
1105
        nodeName: this.name(),
1106
        entityType: 'guard_condition',
1107
      });
1108
    }
1109

1110
    let guard = GuardCondition.createGuardCondition(callback, this.context);
3✔
1111
    debug('Finish creating guard condition');
3✔
1112
    this._guards.push(guard);
3✔
1113
    this.syncHandles();
3✔
1114

1115
    return guard;
3✔
1116
  }
1117

1118
  /**
1119
   * Destroy all resource allocated by this node, including
1120
   * <code>Timer</code>s/<code>Publisher</code>s/<code>Subscription</code>s
1121
   * /<code>Client</code>s/<code>Service</code>s
1122
   * @return {undefined}
1123
   */
1124
  destroy() {
1125
    if (this.spinning) {
914✔
1126
      this.stop();
600✔
1127
    }
1128

1129
    // Action servers/clients require manual destruction due to circular reference with goal handles.
1130
    this._actionClients.forEach((actionClient) => actionClient.destroy());
914✔
1131
    this._actionServers.forEach((actionServer) => actionServer.destroy());
914✔
1132

1133
    this._parameterClients.forEach((paramClient) => paramClient.destroy());
914✔
1134
    this._parameterWatchers.forEach((watcher) => watcher.destroy());
914✔
1135
    this._parameterEventHandlers.forEach((handler) => handler.destroy());
914✔
1136

1137
    this.context.onNodeDestroyed(this);
914✔
1138

1139
    if (this._enableRosout) {
914✔
1140
      rclnodejs.finiRosoutPublisherForNode(this.handle);
889✔
1141
      this._enableRosout = false;
889✔
1142
    }
1143

1144
    this.handle.release();
914✔
1145
    this._clock = null;
914✔
1146
    this._timers = [];
914✔
1147
    this._publishers = [];
914✔
1148
    this._subscriptions = [];
914✔
1149
    this._clients = [];
914✔
1150
    this._services = [];
914✔
1151
    this._guards = [];
914✔
1152
    this._actionClients = [];
914✔
1153
    this._actionServers = [];
914✔
1154
    this._parameterClients = [];
914✔
1155
    this._parameterWatchers = [];
914✔
1156
    this._parameterEventHandlers = [];
914✔
1157

1158
    if (this._rateTimerServer) {
914✔
1159
      this._rateTimerServer.shutdown();
5✔
1160
      this._rateTimerServer = null;
5✔
1161
    }
1162
  }
1163

1164
  /**
1165
   * Destroy a Publisher.
1166
   * @param {Publisher} publisher - The Publisher to be destroyed.
1167
   * @return {undefined}
1168
   */
1169
  destroyPublisher(publisher) {
1170
    if (!(publisher instanceof Publisher)) {
14✔
1171
      throw new TypeValidationError(
2✔
1172
        'publisher',
1173
        publisher,
1174
        'Publisher instance',
1175
        {
1176
          nodeName: this.name(),
1177
        }
1178
      );
1179
    }
1180
    if (publisher.events) {
12!
UNCOV
1181
      publisher.events.forEach((event) => {
×
UNCOV
1182
        this._destroyEntity(event, this._events);
×
1183
      });
UNCOV
1184
      publisher.events = [];
×
1185
    }
1186
    this._destroyEntity(publisher, this._publishers, false);
12✔
1187
  }
1188

1189
  /**
1190
   * Destroy a Subscription.
1191
   * @param {Subscription} subscription - The Subscription to be destroyed.
1192
   * @return {undefined}
1193
   */
1194
  destroySubscription(subscription) {
1195
    if (!(subscription instanceof Subscription)) {
110✔
1196
      throw new TypeValidationError(
2✔
1197
        'subscription',
1198
        subscription,
1199
        'Subscription instance',
1200
        {
1201
          nodeName: this.name(),
1202
        }
1203
      );
1204
    }
1205
    if (subscription.events) {
108✔
1206
      subscription.events.forEach((event) => {
1✔
1207
        this._destroyEntity(event, this._events);
1✔
1208
      });
1209
      subscription.events = [];
1✔
1210
    }
1211

1212
    this._destroyEntity(subscription, this._subscriptions);
108✔
1213
  }
1214

1215
  /**
1216
   * Destroy a Client.
1217
   * @param {Client} client - The Client to be destroyed.
1218
   * @return {undefined}
1219
   */
1220
  destroyClient(client) {
1221
    if (!(client instanceof Client)) {
117✔
1222
      throw new TypeValidationError('client', client, 'Client instance', {
2✔
1223
        nodeName: this.name(),
1224
      });
1225
    }
1226
    this._destroyEntity(client, this._clients);
115✔
1227
  }
1228

1229
  /**
1230
   * Destroy a Service.
1231
   * @param {Service} service - The Service to be destroyed.
1232
   * @return {undefined}
1233
   */
1234
  destroyService(service) {
1235
    if (!(service instanceof Service)) {
8✔
1236
      throw new TypeValidationError('service', service, 'Service instance', {
2✔
1237
        nodeName: this.name(),
1238
      });
1239
    }
1240
    this._destroyEntity(service, this._services);
6✔
1241
  }
1242

1243
  /**
1244
   * Destroy a ParameterClient.
1245
   * @param {ParameterClient} parameterClient - The ParameterClient to be destroyed.
1246
   * @return {undefined}
1247
   */
1248
  destroyParameterClient(parameterClient) {
1249
    if (!(parameterClient instanceof ParameterClient)) {
54!
UNCOV
1250
      throw new TypeError('Invalid argument');
×
1251
    }
1252
    this._removeEntityFromArray(parameterClient, this._parameterClients);
54✔
1253
    parameterClient.destroy();
54✔
1254
  }
1255

1256
  /**
1257
   * Destroy a ParameterWatcher.
1258
   * @param {ParameterWatcher} watcher - The ParameterWatcher to be destroyed.
1259
   * @return {undefined}
1260
   */
1261
  destroyParameterWatcher(watcher) {
1262
    if (!(watcher instanceof ParameterWatcher)) {
1!
UNCOV
1263
      throw new TypeError('Invalid argument');
×
1264
    }
1265
    this._removeEntityFromArray(watcher, this._parameterWatchers);
1✔
1266
    watcher.destroy();
1✔
1267
  }
1268

1269
  /**
1270
   * Create a ParameterEventHandler that monitors parameter changes on any node.
1271
   *
1272
   * Unlike {@link ParameterWatcher} which watches specific parameters on a single
1273
   * remote node, ParameterEventHandler can register callbacks for parameters on
1274
   * any node in the ROS 2 graph by subscribing to /parameter_events.
1275
   *
1276
   * @param {object} [options] - Options for the handler
1277
   * @param {object} [options.qos] - QoS profile for the parameter_events subscription
1278
   * @return {ParameterEventHandler} - An instance of ParameterEventHandler
1279
   * @see {@link ParameterEventHandler}
1280
   */
1281
  createParameterEventHandler(options = {}) {
18✔
1282
    const handler = new ParameterEventHandler(this, options);
18✔
1283
    debug('Created ParameterEventHandler on node=%s', this.name());
18✔
1284
    this._parameterEventHandlers.push(handler);
18✔
1285
    return handler;
18✔
1286
  }
1287

1288
  /**
1289
   * Destroy a ParameterEventHandler.
1290
   * @param {ParameterEventHandler} handler - The handler to be destroyed.
1291
   * @return {undefined}
1292
   */
1293
  destroyParameterEventHandler(handler) {
1294
    if (!(handler instanceof ParameterEventHandler)) {
1!
UNCOV
1295
      throw new TypeError('Invalid argument');
×
1296
    }
1297
    this._removeEntityFromArray(handler, this._parameterEventHandlers);
1✔
1298
    handler.destroy();
1✔
1299
  }
1300

1301
  /**
1302
   * Destroy a Timer.
1303
   * @param {Timer} timer - The Timer to be destroyed.
1304
   * @return {undefined}
1305
   */
1306
  destroyTimer(timer) {
1307
    if (!(timer instanceof Timer)) {
8✔
1308
      throw new TypeValidationError('timer', timer, 'Timer instance', {
2✔
1309
        nodeName: this.name(),
1310
      });
1311
    }
1312
    this._destroyEntity(timer, this._timers);
6✔
1313
  }
1314

1315
  /**
1316
   * Destroy a guard condition.
1317
   * @param {GuardCondition} guard - The guard condition to be destroyed.
1318
   * @return {undefined}
1319
   */
1320
  destroyGuardCondition(guard) {
1321
    if (!(guard instanceof GuardCondition)) {
3!
UNCOV
1322
      throw new TypeValidationError('guard', guard, 'GuardCondition instance', {
×
1323
        nodeName: this.name(),
1324
      });
1325
    }
1326
    this._destroyEntity(guard, this._guards);
3✔
1327
  }
1328

1329
  /**
1330
   * Get the name of the node.
1331
   * @return {string}
1332
   */
1333
  name() {
1334
    return rclnodejs.getNodeName(this.handle);
5,323✔
1335
  }
1336

1337
  /**
1338
   * Get the namespace of the node.
1339
   * @return {string}
1340
   */
1341
  namespace() {
1342
    return rclnodejs.getNamespace(this.handle);
4,936✔
1343
  }
1344

1345
  /**
1346
   * Get the context in which this node was created.
1347
   * @return {Context}
1348
   */
1349
  get context() {
1350
    return this._context;
6,463✔
1351
  }
1352

1353
  /**
1354
   * Get the nodes logger.
1355
   * @returns {Logger} - The logger for the node.
1356
   */
1357
  getLogger() {
1358
    return this._logger;
243✔
1359
  }
1360

1361
  /**
1362
   * Get the clock used by the node.
1363
   * @returns {Clock} - The nodes clock.
1364
   */
1365
  getClock() {
1366
    return this._clock;
116✔
1367
  }
1368

1369
  /**
1370
   * Get the current time using the node's clock.
1371
   * @returns {Timer} - The current time.
1372
   */
1373
  now() {
1374
    return this.getClock().now();
2✔
1375
  }
1376

1377
  /**
1378
   * Get the list of published topics discovered by the provided node for the remote node name.
1379
   * @param {string} nodeName - The name of the node.
1380
   * @param {string} namespace - The name of the namespace.
1381
   * @param {boolean} noDemangle - If true topic names and types returned will not be demangled, default: false.
1382
   * @return {Array<{name: string, types: Array<string>}>} - An array of the names and types.
1383
   */
1384
  getPublisherNamesAndTypesByNode(nodeName, namespace, noDemangle = false) {
2✔
1385
    return rclnodejs.getPublisherNamesAndTypesByNode(
2✔
1386
      this.handle,
1387
      nodeName,
1388
      namespace,
1389
      noDemangle
1390
    );
1391
  }
1392

1393
  /**
1394
   * Get the list of published topics discovered by the provided node for the remote node name.
1395
   * @param {string} nodeName - The name of the node.
1396
   * @param {string} namespace - The name of the namespace.
1397
   * @param {boolean} noDemangle - If true topic names and types returned will not be demangled, default: false.
1398
   * @return {Array<{name: string, types: Array<string>}>} - An array of the names and types.
1399
   */
1400
  getSubscriptionNamesAndTypesByNode(nodeName, namespace, noDemangle = false) {
×
UNCOV
1401
    return rclnodejs.getSubscriptionNamesAndTypesByNode(
×
1402
      this.handle,
1403
      nodeName,
1404
      namespace,
1405
      noDemangle
1406
    );
1407
  }
1408

1409
  /**
1410
   * Get service names and types for which a remote node has servers.
1411
   * @param {string} nodeName - The name of the node.
1412
   * @param {string} namespace - The name of the namespace.
1413
   * @return {Array<{name: string, types: Array<string>}>} - An array of the names and types.
1414
   */
1415
  getServiceNamesAndTypesByNode(nodeName, namespace) {
UNCOV
1416
    return rclnodejs.getServiceNamesAndTypesByNode(
×
1417
      this.handle,
1418
      nodeName,
1419
      namespace
1420
    );
1421
  }
1422

1423
  /**
1424
   * Get service names and types for which a remote node has clients.
1425
   * @param {string} nodeName - The name of the node.
1426
   * @param {string} namespace - The name of the namespace.
1427
   * @return {Array<{name: string, types: Array<string>}>} - An array of the names and types.
1428
   */
1429
  getClientNamesAndTypesByNode(nodeName, namespace) {
UNCOV
1430
    return rclnodejs.getClientNamesAndTypesByNode(
×
1431
      this.handle,
1432
      nodeName,
1433
      namespace
1434
    );
1435
  }
1436

1437
  /**
1438
   * Get the list of topics discovered by the provided node.
1439
   * @param {boolean} noDemangle - If true topic names and types returned will not be demangled, default: false.
1440
   * @return {Array<{name: string, types: Array<string>}>} - An array of the names and types.
1441
   */
1442
  getTopicNamesAndTypes(noDemangle = false) {
×
UNCOV
1443
    return rclnodejs.getTopicNamesAndTypes(this.handle, noDemangle);
×
1444
  }
1445

1446
  /**
1447
   * Get the list of services discovered by the provided node.
1448
   * @return {Array<{name: string, types: Array<string>}>} - An array of the names and types.
1449
   */
1450
  getServiceNamesAndTypes() {
1451
    return rclnodejs.getServiceNamesAndTypes(this.handle);
2✔
1452
  }
1453

1454
  /**
1455
   * Return a list of publishers on a given topic.
1456
   *
1457
   * The returned parameter is a list of TopicEndpointInfo objects, where each will contain
1458
   * the node name, node namespace, topic type, topic endpoint's GID, and its QoS profile.
1459
   *
1460
   * When the `no_mangle` parameter is `true`, the provided `topic` should be a valid
1461
   * topic name for the middleware (useful when combining ROS with native middleware (e.g. DDS)
1462
   * apps).  When the `no_mangle` parameter is `false`, the provided `topic` should
1463
   * follow ROS topic name conventions.
1464
   *
1465
   * `topic` may be a relative, private, or fully qualified topic name.
1466
   *  A relative or private topic will be expanded using this node's namespace and name.
1467
   *  The queried `topic` is not remapped.
1468
   *
1469
   * @param {string} topic - The topic on which to find the publishers.
1470
   * @param {boolean} [noDemangle=false] - If `true`, `topic` needs to be a valid middleware topic
1471
   *                               name, otherwise it should be a valid ROS topic name. Defaults to `false`.
1472
   * @returns {Array} - list of publishers
1473
   */
1474
  getPublishersInfoByTopic(topic, noDemangle = false) {
1✔
1475
    return rclnodejs.getPublishersInfoByTopic(
4✔
1476
      this.handle,
1477
      this._getValidatedTopic(topic, noDemangle),
1478
      noDemangle
1479
    );
1480
  }
1481

1482
  /**
1483
   * Return a list of subscriptions on a given topic.
1484
   *
1485
   * The returned parameter is a list of TopicEndpointInfo objects, where each will contain
1486
   * the node name, node namespace, topic type, topic endpoint's GID, and its QoS profile.
1487
   *
1488
   * When the `no_mangle` parameter is `true`, the provided `topic` should be a valid
1489
   * topic name for the middleware (useful when combining ROS with native middleware (e.g. DDS)
1490
   * apps).  When the `no_mangle` parameter is `false`, the provided `topic` should
1491
   * follow ROS topic name conventions.
1492
   *
1493
   * `topic` may be a relative, private, or fully qualified topic name.
1494
   *  A relative or private topic will be expanded using this node's namespace and name.
1495
   *  The queried `topic` is not remapped.
1496
   *
1497
   * @param {string} topic - The topic on which to find the subscriptions.
1498
   * @param {boolean} [noDemangle=false] -  If `true`, `topic` needs to be a valid middleware topic
1499
                                    name, otherwise it should be a valid ROS topic name. Defaults to `false`.
1500
   * @returns {Array} - list of subscriptions
1501
   */
1502
  getSubscriptionsInfoByTopic(topic, noDemangle = false) {
×
1503
    return rclnodejs.getSubscriptionsInfoByTopic(
2✔
1504
      this.handle,
1505
      this._getValidatedTopic(topic, noDemangle),
1506
      noDemangle
1507
    );
1508
  }
1509

1510
  /**
1511
   * Return a list of clients on a given service.
1512
   *
1513
   * The returned parameter is a list of ServiceEndpointInfo objects, where each will contain
1514
   * the node name, node namespace, service type, service endpoint's GID, and its QoS profile.
1515
   *
1516
   * When the `no_mangle` parameter is `true`, the provided `service` should be a valid
1517
   * service name for the middleware (useful when combining ROS with native middleware (e.g. DDS)
1518
   * apps).  When the `no_mangle` parameter is `false`, the provided `service` should
1519
   * follow ROS service name conventions.
1520
   *
1521
   * `service` may be a relative, private, or fully qualified service name.
1522
   *  A relative or private service will be expanded using this node's namespace and name.
1523
   *  The queried `service` is not remapped.
1524
   *
1525
   * @param {string} service - The service on which to find the clients.
1526
   * @param {boolean} [noDemangle=false] - If `true`, `service` needs to be a valid middleware service
1527
   *                               name, otherwise it should be a valid ROS service name. Defaults to `false`.
1528
   * @returns {Array} - list of clients
1529
   */
1530
  getClientsInfoByService(service, noDemangle = false) {
×
1531
    if (DistroUtils.getDistroId() < DistroUtils.DistroId.ROLLING) {
2!
UNCOV
1532
      console.warn(
×
1533
        'getClientsInfoByService is not supported by this version of ROS 2'
1534
      );
UNCOV
1535
      return null;
×
1536
    }
1537
    return rclnodejs.getClientsInfoByService(
2✔
1538
      this.handle,
1539
      this._getValidatedServiceName(service, noDemangle),
1540
      noDemangle
1541
    );
1542
  }
1543

1544
  /**
1545
   * Return a list of servers on a given service.
1546
   *
1547
   * The returned parameter is a list of ServiceEndpointInfo objects, where each will contain
1548
   * the node name, node namespace, service type, service endpoint's GID, and its QoS profile.
1549
   *
1550
   * When the `no_mangle` parameter is `true`, the provided `service` should be a valid
1551
   * service name for the middleware (useful when combining ROS with native middleware (e.g. DDS)
1552
   * apps).  When the `no_mangle` parameter is `false`, the provided `service` should
1553
   * follow ROS service name conventions.
1554
   *
1555
   * `service` may be a relative, private, or fully qualified service name.
1556
   *  A relative or private service will be expanded using this node's namespace and name.
1557
   *  The queried `service` is not remapped.
1558
   *
1559
   * @param {string} service - The service on which to find the servers.
1560
   * @param {boolean} [noDemangle=false] - If `true`, `service` needs to be a valid middleware service
1561
   *                               name, otherwise it should be a valid ROS service name. Defaults to `false`.
1562
   * @returns {Array} - list of servers
1563
   */
1564
  getServersInfoByService(service, noDemangle = false) {
×
1565
    if (DistroUtils.getDistroId() < DistroUtils.DistroId.ROLLING) {
2!
UNCOV
1566
      console.warn(
×
1567
        'getServersInfoByService is not supported by this version of ROS 2'
1568
      );
UNCOV
1569
      return null;
×
1570
    }
1571
    return rclnodejs.getServersInfoByService(
2✔
1572
      this.handle,
1573
      this._getValidatedServiceName(service, noDemangle),
1574
      noDemangle
1575
    );
1576
  }
1577

1578
  /**
1579
   * Get the list of nodes discovered by the provided node.
1580
   * @return {Array<string>} - An array of the names.
1581
   */
1582
  getNodeNames() {
1583
    return this.getNodeNamesAndNamespaces().map((item) => item.name);
41✔
1584
  }
1585

1586
  /**
1587
   * Get the list of nodes and their namespaces discovered by the provided node.
1588
   * @return {Array<{name: string, namespace: string}>} An array of the names and namespaces.
1589
   */
1590
  getNodeNamesAndNamespaces() {
1591
    return rclnodejs.getNodeNames(this.handle, /*getEnclaves=*/ false);
17✔
1592
  }
1593

1594
  /**
1595
   * Get the list of nodes and their namespaces with enclaves discovered by the provided node.
1596
   * @return {Array<{name: string, namespace: string, enclave: string}>} An array of the names, namespaces and enclaves.
1597
   */
1598
  getNodeNamesAndNamespacesWithEnclaves() {
1599
    return rclnodejs.getNodeNames(this.handle, /*getEnclaves=*/ true);
1✔
1600
  }
1601

1602
  /**
1603
   * Return the number of publishers on a given topic.
1604
   * @param {string} topic - The name of the topic.
1605
   * @returns {number} - Number of publishers on the given topic.
1606
   */
1607
  countPublishers(topic) {
1608
    let expandedTopic = rclnodejs.expandTopicName(
6✔
1609
      topic,
1610
      this.name(),
1611
      this.namespace()
1612
    );
1613
    rclnodejs.validateTopicName(expandedTopic);
6✔
1614

1615
    return rclnodejs.countPublishers(this.handle, expandedTopic);
6✔
1616
  }
1617

1618
  /**
1619
   * Return the number of subscribers on a given topic.
1620
   * @param {string} topic - The name of the topic.
1621
   * @returns {number} - Number of subscribers on the given topic.
1622
   */
1623
  countSubscribers(topic) {
1624
    let expandedTopic = rclnodejs.expandTopicName(
6✔
1625
      topic,
1626
      this.name(),
1627
      this.namespace()
1628
    );
1629
    rclnodejs.validateTopicName(expandedTopic);
6✔
1630

1631
    return rclnodejs.countSubscribers(this.handle, expandedTopic);
6✔
1632
  }
1633

1634
  /**
1635
   * Get the number of clients on a given service name.
1636
   * @param {string} serviceName - the service name
1637
   * @returns {Number}
1638
   */
1639
  countClients(serviceName) {
1640
    if (DistroUtils.getDistroId() <= DistroUtils.getDistroId('humble')) {
2!
UNCOV
1641
      console.warn('countClients is not supported by this version of ROS 2');
×
UNCOV
1642
      return null;
×
1643
    }
1644
    return rclnodejs.countClients(this.handle, serviceName);
2✔
1645
  }
1646

1647
  /**
1648
   * Get the number of services on a given service name.
1649
   * @param {string} serviceName - the service name
1650
   * @returns {Number}
1651
   */
1652
  countServices(serviceName) {
1653
    if (DistroUtils.getDistroId() <= DistroUtils.getDistroId('humble')) {
1!
UNCOV
1654
      console.warn('countServices is not supported by this version of ROS 2');
×
UNCOV
1655
      return null;
×
1656
    }
1657
    return rclnodejs.countServices(this.handle, serviceName);
1✔
1658
  }
1659

1660
  /**
1661
   * Get the list of parameter-overrides found on the commandline and
1662
   * in the NodeOptions.parameter_overrides property.
1663
   *
1664
   * @return {Array<Parameter>} - An array of Parameters.
1665
   */
1666
  getParameterOverrides() {
1667
    return Array.from(this._parameterOverrides.values());
8✔
1668
  }
1669

1670
  /**
1671
   * Declare a parameter.
1672
   *
1673
   * Internally, register a parameter and it's descriptor.
1674
   * If a parameter-override exists, it's value will replace that of the parameter
1675
   * unless ignoreOverride is true.
1676
   * If the descriptor is undefined, then a ParameterDescriptor will be inferred
1677
   * from the parameter's state.
1678
   *
1679
   * If a parameter by the same name has already been declared then an Error is thrown.
1680
   * A parameter must be undeclared before attempting to redeclare it.
1681
   *
1682
   * @param {Parameter} parameter - Parameter to declare.
1683
   * @param {ParameterDescriptor} [descriptor] - Optional descriptor for parameter.
1684
   * @param {boolean} [ignoreOverride] - When true disregard any parameter-override that may be present.
1685
   * @return {Parameter} - The newly declared parameter.
1686
   */
1687
  declareParameter(parameter, descriptor, ignoreOverride = false) {
2,420✔
1688
    const parameters = this.declareParameters(
2,421✔
1689
      [parameter],
1690
      descriptor ? [descriptor] : [],
2,421✔
1691
      ignoreOverride
1692
    );
1693
    return parameters.length == 1 ? parameters[0] : null;
2,421!
1694
  }
1695

1696
  /**
1697
   * Declare a list of parameters.
1698
   *
1699
   * Internally register parameters with their corresponding descriptor one by one
1700
   * in the order they are provided. This is an atomic operation. If an error
1701
   * occurs the process halts and no further parameters are declared.
1702
   * Parameters that have already been processed are undeclared.
1703
   *
1704
   * While descriptors is an optional parameter, when provided there must be
1705
   * a descriptor for each parameter; otherwise an Error is thrown.
1706
   * If descriptors is not provided then a descriptor will be inferred
1707
   * from each parameter's state.
1708
   *
1709
   * When a parameter-override is available, the parameter's value
1710
   * will be replaced with that of the parameter-override unless ignoreOverrides
1711
   * is true.
1712
   *
1713
   * If a parameter by the same name has already been declared then an Error is thrown.
1714
   * A parameter must be undeclared before attempting to redeclare it.
1715
   *
1716
   * Prior to declaring the parameters each SetParameterEventCallback registered
1717
   * using setOnParameterEventCallback() is called in succession with the parameters
1718
   * list. Any SetParameterEventCallback that retuns does not return a successful
1719
   * result will cause the entire operation to terminate with no changes to the
1720
   * parameters. When all SetParameterEventCallbacks return successful then the
1721
   * list of parameters is updated.
1722
   *
1723
   * @param {Parameter[]} parameters - The parameters to declare.
1724
   * @param {ParameterDescriptor[]} [descriptors] - Optional descriptors,
1725
   *    a 1-1 correspondence with parameters.
1726
   * @param {boolean} ignoreOverrides - When true, parameter-overrides are
1727
   *    not considered, i.e.,ignored.
1728
   * @return {Parameter[]} - The declared parameters.
1729
   */
1730
  declareParameters(parameters, descriptors = [], ignoreOverrides = false) {
×
1731
    if (!Array.isArray(parameters)) {
2,421!
UNCOV
1732
      throw new TypeValidationError('parameters', parameters, 'Array', {
×
1733
        nodeName: this.name(),
1734
      });
1735
    }
1736
    if (!Array.isArray(descriptors)) {
2,421!
UNCOV
1737
      throw new TypeValidationError('descriptors', descriptors, 'Array', {
×
1738
        nodeName: this.name(),
1739
      });
1740
    }
1741
    if (descriptors.length > 0 && parameters.length !== descriptors.length) {
2,421!
UNCOV
1742
      throw new ValidationError(
×
1743
        'Each parameter must have a corresponding ParameterDescriptor',
1744
        {
1745
          code: 'PARAMETER_DESCRIPTOR_MISMATCH',
1746
          argumentName: 'descriptors',
1747
          providedValue: descriptors.length,
1748
          expectedType: `Array with length ${parameters.length}`,
1749
          nodeName: this.name(),
1750
        }
1751
      );
1752
    }
1753

1754
    const declaredDescriptors = [];
2,421✔
1755
    const declaredParameters = [];
2,421✔
1756
    const declaredParameterCollisions = [];
2,421✔
1757
    for (let i = 0; i < parameters.length; i++) {
2,421✔
1758
      let parameter =
1759
        !ignoreOverrides && this._parameterOverrides.has(parameters[i].name)
2,421✔
1760
          ? this._parameterOverrides.get(parameters[i].name)
1761
          : parameters[i];
1762

1763
      // stop processing parameters that have already been declared
1764
      if (this._parameters.has(parameter.name)) {
2,421!
UNCOV
1765
        declaredParameterCollisions.push(parameter);
×
UNCOV
1766
        continue;
×
1767
      }
1768

1769
      // create descriptor for parameter if not provided
1770
      let descriptor =
1771
        descriptors.length > 0
2,421✔
1772
          ? descriptors[i]
1773
          : ParameterDescriptor.fromParameter(parameter);
1774

1775
      descriptor.validate();
2,421✔
1776

1777
      declaredDescriptors.push(descriptor);
2,421✔
1778
      declaredParameters.push(parameter);
2,421✔
1779
    }
1780

1781
    if (declaredParameterCollisions.length > 0) {
2,421!
1782
      const errorMsg =
UNCOV
1783
        declaredParameterCollisions.length == 1
×
1784
          ? `Parameter(${declaredParameterCollisions[0]}) already declared.`
1785
          : `Multiple parameters already declared, e.g., Parameter(${declaredParameterCollisions[0]}).`;
UNCOV
1786
      throw new Error(errorMsg);
×
1787
    }
1788

1789
    // register descriptor
1790
    for (const descriptor of declaredDescriptors) {
2,421✔
1791
      this._parameterDescriptors.set(descriptor.name, descriptor);
2,421✔
1792
    }
1793

1794
    const result = this._setParametersAtomically(declaredParameters, true);
2,421✔
1795
    if (!result.successful) {
2,421!
1796
      // unregister descriptors
UNCOV
1797
      for (const descriptor of declaredDescriptors) {
×
UNCOV
1798
        this._parameterDescriptors.delete(descriptor.name);
×
1799
      }
1800

UNCOV
1801
      throw new Error(result.reason);
×
1802
    }
1803

1804
    return this.getParameters(declaredParameters.map((param) => param.name));
2,421✔
1805
  }
1806

1807
  /**
1808
   * Undeclare a parameter.
1809
   *
1810
   * Readonly parameters can not be undeclared or updated.
1811
   * @param {string} name - Name of parameter to undeclare.
1812
   * @return {undefined} -
1813
   */
1814
  undeclareParameter(name) {
1815
    if (!this.hasParameter(name)) return;
1!
1816

1817
    const descriptor = this.getParameterDescriptor(name);
1✔
1818
    if (descriptor.readOnly) {
1!
UNCOV
1819
      throw new Error(
×
1820
        `${name} parameter is read-only and can not be undeclared`
1821
      );
1822
    }
1823

1824
    this._parameters.delete(name);
1✔
1825
    this._parameterDescriptors.delete(name);
1✔
1826
  }
1827

1828
  /**
1829
   * Determine if a parameter has been declared.
1830
   * @param {string} name - name of parameter
1831
   * @returns {boolean} - Return true if parameter is declared; false otherwise.
1832
   */
1833
  hasParameter(name) {
1834
    return this._parameters.has(name);
6,067✔
1835
  }
1836

1837
  /**
1838
   * Get a declared parameter by name.
1839
   *
1840
   * If unable to locate a declared parameter then a
1841
   * parameter with type == PARAMETER_NOT_SET is returned.
1842
   *
1843
   * @param {string} name - The name of the parameter.
1844
   * @return {Parameter} - The parameter.
1845
   */
1846
  getParameter(name) {
1847
    return this.getParameters([name])[0];
1,807✔
1848
  }
1849

1850
  /**
1851
   * Get a list of parameters.
1852
   *
1853
   * Find and return the declared parameters.
1854
   * If no names are provided return all declared parameters.
1855
   *
1856
   * If unable to locate a declared parameter then a
1857
   * parameter with type == PARAMETER_NOT_SET is returned in
1858
   * it's place.
1859
   *
1860
   * @param {string[]} [names] - The names of the declared parameters
1861
   *    to find or null indicating to return all declared parameters.
1862
   * @return {Parameter[]} - The parameters.
1863
   */
1864
  getParameters(names = []) {
12✔
1865
    let params = [];
4,266✔
1866

1867
    if (names.length == 0) {
4,266✔
1868
      // get all parameters
1869
      params = [...this._parameters.values()];
12✔
1870
      return params;
12✔
1871
    }
1872

1873
    for (const name of names) {
4,254✔
1874
      const param = this.hasParameter(name)
4,261✔
1875
        ? this._parameters.get(name)
1876
        : new Parameter(name, ParameterType.PARAMETER_NOT_SET);
1877

1878
      params.push(param);
4,261✔
1879
    }
1880

1881
    return params;
4,254✔
1882
  }
1883

1884
  /**
1885
   * Get the types of given parameters.
1886
   *
1887
   * Return the types of given parameters.
1888
   *
1889
   * @param {string[]} [names] - The names of the declared parameters.
1890
   * @return {Uint8Array} - The types.
1891
   */
1892
  getParameterTypes(names = []) {
×
1893
    let types = [];
3✔
1894

1895
    for (const name of names) {
3✔
1896
      const descriptor = this._parameterDescriptors.get(name);
7✔
1897
      if (descriptor) {
7!
1898
        types.push(descriptor.type);
7✔
1899
      }
1900
    }
1901
    return types;
3✔
1902
  }
1903

1904
  /**
1905
   * Get the names of all declared parameters.
1906
   *
1907
   * @return {Array<string>} - The declared parameter names or empty array if
1908
   *    no parameters have been declared.
1909
   */
1910
  getParameterNames() {
1911
    return this.getParameters().map((param) => param.name);
53✔
1912
  }
1913

1914
  /**
1915
   * Determine if a parameter descriptor exists.
1916
   *
1917
   * @param {string} name - The name of a descriptor to for.
1918
   * @return {boolean} - true if a descriptor has been declared; otherwise false.
1919
   */
1920
  hasParameterDescriptor(name) {
1921
    return !!this.getParameterDescriptor(name);
2,451✔
1922
  }
1923

1924
  /**
1925
   * Get a declared parameter descriptor by name.
1926
   *
1927
   * If unable to locate a declared parameter descriptor then a
1928
   * descriptor with type == PARAMETER_NOT_SET is returned.
1929
   *
1930
   * @param {string} name - The name of the parameter descriptor to find.
1931
   * @return {ParameterDescriptor} - The parameter descriptor.
1932
   */
1933
  getParameterDescriptor(name) {
1934
    return this.getParameterDescriptors([name])[0];
4,903✔
1935
  }
1936

1937
  /**
1938
   * Find a list of declared ParameterDescriptors.
1939
   *
1940
   * If no names are provided return all declared descriptors.
1941
   *
1942
   * If unable to locate a declared descriptor then a
1943
   * descriptor with type == PARAMETER_NOT_SET is returned in
1944
   * it's place.
1945
   *
1946
   * @param {string[]} [names] - The names of the declared parameter
1947
   *    descriptors to find or null indicating to return all declared descriptors.
1948
   * @return {ParameterDescriptor[]} - The parameter descriptors.
1949
   */
1950
  getParameterDescriptors(names = []) {
×
1951
    let descriptors = [];
4,906✔
1952

1953
    if (names.length == 0) {
4,906!
1954
      // get all parameters
UNCOV
1955
      descriptors = [...this._parameterDescriptors.values()];
×
UNCOV
1956
      return descriptors;
×
1957
    }
1958

1959
    for (const name of names) {
4,906✔
1960
      let descriptor = this._parameterDescriptors.get(name);
4,908✔
1961
      if (!descriptor) {
4,908!
UNCOV
1962
        descriptor = new ParameterDescriptor(
×
1963
          name,
1964
          ParameterType.PARAMETER_NOT_SET
1965
        );
1966
      }
1967
      descriptors.push(descriptor);
4,908✔
1968
    }
1969

1970
    return descriptors;
4,906✔
1971
  }
1972

1973
  /**
1974
   * Replace a declared parameter.
1975
   *
1976
   * The parameter being replaced must be a declared parameter who's descriptor
1977
   * is not readOnly; otherwise an Error is thrown.
1978
   *
1979
   * @param {Parameter} parameter - The new parameter.
1980
   * @return {rclnodejs.rcl_interfaces.msg.SetParameterResult} - The result of the operation.
1981
   */
1982
  setParameter(parameter) {
1983
    const results = this.setParameters([parameter]);
15✔
1984
    return results[0];
15✔
1985
  }
1986

1987
  /**
1988
   * Replace a list of declared parameters.
1989
   *
1990
   * Declared parameters are replaced in the order they are provided and
1991
   * a ParameterEvent is published for each individual parameter change.
1992
   *
1993
   * Prior to setting the parameters each SetParameterEventCallback registered
1994
   * using setOnParameterEventCallback() is called in succession with the parameters
1995
   * list. Any SetParameterEventCallback that retuns does not return a successful
1996
   * result will cause the entire operation to terminate with no changes to the
1997
   * parameters. When all SetParameterEventCallbacks return successful then the
1998
   * list of parameters is updated.
1999
   *
2000
   * If an error occurs, the process is stopped and returned. Parameters
2001
   * set before an error remain unchanged.
2002
   *
2003
   * @param {Parameter[]} parameters - The parameters to set.
2004
   * @return {rclnodejs.rcl_interfaces.msg.SetParameterResult[]} - A list of SetParameterResult, one for each parameter that was set.
2005
   */
2006
  setParameters(parameters = []) {
×
2007
    return parameters.map((parameter) =>
26✔
2008
      this.setParametersAtomically([parameter])
29✔
2009
    );
2010
  }
2011

2012
  /**
2013
   * Repalce a list of declared parameters atomically.
2014
   *
2015
   * Declared parameters are replaced in the order they are provided.
2016
   * A single ParameterEvent is published collectively for all changed
2017
   * parameters.
2018
   *
2019
   * Prior to setting the parameters each SetParameterEventCallback registered
2020
   * using setOnParameterEventCallback() is called in succession with the parameters
2021
   * list. Any SetParameterEventCallback that retuns does not return a successful
2022
   * result will cause the entire operation to terminate with no changes to the
2023
   * parameters. When all SetParameterEventCallbacks return successful then the
2024
   * list of parameters is updated.d
2025
   *
2026
   * If an error occurs, the process stops immediately. All parameters updated to
2027
   * the point of the error are reverted to their previous state.
2028
   *
2029
   * @param {Parameter[]} parameters - The parameters to set.
2030
   * @return {rclnodejs.rcl_interfaces.msg.SetParameterResult} - describes the result of setting 1 or more parameters.
2031
   */
2032
  setParametersAtomically(parameters = []) {
×
2033
    return this._setParametersAtomically(parameters);
30✔
2034
  }
2035

2036
  /**
2037
   * Internal method for updating parameters atomically.
2038
   *
2039
   * Prior to setting the parameters each SetParameterEventCallback registered
2040
   * using setOnParameterEventCallback() is called in succession with the parameters
2041
   * list. Any SetParameterEventCallback that retuns does not return a successful
2042
   * result will cause the entire operation to terminate with no changes to the
2043
   * parameters. When all SetParameterEventCallbacks return successful then the
2044
   * list of parameters is updated.
2045
   *
2046
   * @param {Paramerter[]} parameters - The parameters to update.
2047
   * @param {boolean} declareParameterMode - When true parameters are being declared;
2048
   *    otherwise they are being changed.
2049
   * @return {SetParameterResult} - A single collective result.
2050
   */
2051
  _setParametersAtomically(parameters = [], declareParameterMode = false) {
30!
2052
    let result = this._validateParameters(parameters, declareParameterMode);
2,451✔
2053
    if (!result.successful) {
2,451!
UNCOV
2054
      return result;
×
2055
    }
2056

2057
    // give all SetParametersCallbacks a chance to veto this change
2058
    for (const callback of this._setParametersCallbacks) {
2,451✔
2059
      result = callback(parameters);
1,567✔
2060
      if (!result.successful) {
1,567✔
2061
        // a callback has vetoed a parameter change
2062
        return result;
1✔
2063
      }
2064
    }
2065

2066
    // collectively track updates to parameters for use
2067
    // when publishing a ParameterEvent
2068
    const newParameters = [];
2,450✔
2069
    const changedParameters = [];
2,450✔
2070
    const deletedParameters = [];
2,450✔
2071

2072
    for (const parameter of parameters) {
2,450✔
2073
      if (parameter.type == ParameterType.PARAMETER_NOT_SET) {
2,450✔
2074
        this.undeclareParameter(parameter.name);
1✔
2075
        deletedParameters.push(parameter);
1✔
2076
      } else {
2077
        this._parameters.set(parameter.name, parameter);
2,449✔
2078
        if (declareParameterMode) {
2,449✔
2079
          newParameters.push(parameter);
2,421✔
2080
        } else {
2081
          changedParameters.push(parameter);
28✔
2082
        }
2083
      }
2084
    }
2085

2086
    // create ParameterEvent
2087
    const parameterEvent = new (loader.loadInterface(
2,450✔
2088
      PARAMETER_EVENT_MSG_TYPE
2089
    ))();
2090

2091
    const { seconds, nanoseconds } = this._clock.now().secondsAndNanoseconds;
2,450✔
2092
    parameterEvent.stamp = {
2,450✔
2093
      sec: Number(seconds),
2094
      nanosec: Number(nanoseconds),
2095
    };
2096

2097
    parameterEvent.node =
2,450✔
2098
      this.namespace() === '/'
2,450✔
2099
        ? this.namespace() + this.name()
2100
        : this.namespace() + '/' + this.name();
2101

2102
    if (newParameters.length > 0) {
2,450✔
2103
      parameterEvent['new_parameters'] = newParameters.map((parameter) =>
2,421✔
2104
        parameter.toParameterMessage()
2,421✔
2105
      );
2106
    }
2107
    if (changedParameters.length > 0) {
2,450✔
2108
      parameterEvent['changed_parameters'] = changedParameters.map(
28✔
2109
        (parameter) => parameter.toParameterMessage()
28✔
2110
      );
2111
    }
2112
    if (deletedParameters.length > 0) {
2,450✔
2113
      parameterEvent['deleted_parameters'] = deletedParameters.map(
1✔
2114
        (parameter) => parameter.toParameterMessage()
1✔
2115
      );
2116
    }
2117

2118
    // Publish ParameterEvent.
2119
    this._parameterEventPublisher.publish(parameterEvent);
2,450✔
2120

2121
    return {
2,450✔
2122
      successful: true,
2123
      reason: '',
2124
    };
2125
  }
2126

2127
  /**
2128
   * This callback is called when declaring a parameter or setting a parameter.
2129
   * The callback is provided a list of parameters and returns a SetParameterResult
2130
   * to indicate approval or veto of the operation.
2131
   *
2132
   * @callback SetParametersCallback
2133
   * @param {Parameter[]} parameters - The message published
2134
   * @returns {rcl_interfaces.msg.SetParameterResult} -
2135
   *
2136
   * @see [Node.addOnSetParametersCallback]{@link Node#addOnSetParametersCallback}
2137
   * @see [Node.removeOnSetParametersCallback]{@link Node#removeOnSetParametersCallback}
2138
   */
2139

2140
  /**
2141
   * Add a callback to the front of the list of callbacks invoked for parameter declaration
2142
   * and setting. No checks are made for duplicate callbacks.
2143
   *
2144
   * @param {SetParametersCallback} callback - The callback to add.
2145
   * @returns {undefined}
2146
   */
2147
  addOnSetParametersCallback(callback) {
2148
    this._setParametersCallbacks.unshift(callback);
908✔
2149
  }
2150

2151
  /**
2152
   * Remove a callback from the list of SetParametersCallbacks.
2153
   * If the callback is not found the process is a nop.
2154
   *
2155
   * @param {SetParametersCallback} callback - The callback to be removed
2156
   * @returns {undefined}
2157
   */
2158
  removeOnSetParametersCallback(callback) {
2159
    const idx = this._setParametersCallbacks.indexOf(callback);
2✔
2160
    if (idx > -1) {
2!
2161
      this._setParametersCallbacks.splice(idx, 1);
2✔
2162
    }
2163
  }
2164

2165
  /**
2166
   * Get the fully qualified name of the node.
2167
   *
2168
   * @returns {string} - String containing the fully qualified name of the node.
2169
   */
2170
  getFullyQualifiedName() {
2171
    return rclnodejs.getFullyQualifiedName(this.handle);
1✔
2172
  }
2173

2174
  /**
2175
   * Get the RMW implementation identifier
2176
   * @returns {string} - The RMW implementation identifier.
2177
   */
2178
  getRMWImplementationIdentifier() {
2179
    return rclnodejs.getRMWImplementationIdentifier();
1✔
2180
  }
2181

2182
  /**
2183
   * Return a topic name expanded and remapped.
2184
   * @param {string} topicName - Topic name to be expanded and remapped.
2185
   * @param {boolean} [onlyExpand=false] - If `true`, remapping rules won't be applied.
2186
   * @returns {string} - A fully qualified topic name.
2187
   */
2188
  resolveTopicName(topicName, onlyExpand = false) {
13✔
2189
    if (typeof topicName !== 'string') {
14!
UNCOV
2190
      throw new TypeValidationError('topicName', topicName, 'string', {
×
2191
        nodeName: this.name(),
2192
      });
2193
    }
2194
    return rclnodejs.resolveName(
14✔
2195
      this.handle,
2196
      topicName,
2197
      onlyExpand,
2198
      /*isService=*/ false
2199
    );
2200
  }
2201

2202
  /**
2203
   * Return a service name expanded and remapped.
2204
   * @param {string} service - Service name to be expanded and remapped.
2205
   * @param {boolean} [onlyExpand=false] - If `true`, remapping rules won't be applied.
2206
   * @returns {string} - A fully qualified service name.
2207
   */
2208
  resolveServiceName(service, onlyExpand = false) {
6✔
2209
    if (typeof service !== 'string') {
7!
UNCOV
2210
      throw new TypeValidationError('service', service, 'string', {
×
2211
        nodeName: this.name(),
2212
      });
2213
    }
2214
    return rclnodejs.resolveName(
7✔
2215
      this.handle,
2216
      service,
2217
      onlyExpand,
2218
      /*isService=*/ true
2219
    );
2220
  }
2221

2222
  // returns on 1st error or result {successful, reason}
2223
  _validateParameters(parameters = [], declareParameterMode = false) {
×
2224
    for (const parameter of parameters) {
2,451✔
2225
      // detect invalid parameter
2226
      try {
2,451✔
2227
        parameter.validate();
2,451✔
2228
      } catch {
UNCOV
2229
        return {
×
2230
          successful: false,
2231
          reason: `Invalid ${parameter.name}`,
2232
        };
2233
      }
2234

2235
      // detect undeclared parameter
2236
      if (!this.hasParameterDescriptor(parameter.name)) {
2,451!
2237
        return {
×
2238
          successful: false,
2239
          reason: `Parameter ${parameter.name} has not been declared`,
2240
        };
2241
      }
2242

2243
      // detect readonly parameter that can not be updated
2244
      const descriptor = this.getParameterDescriptor(parameter.name);
2,451✔
2245
      if (!declareParameterMode && descriptor.readOnly) {
2,451!
UNCOV
2246
        return {
×
2247
          successful: false,
2248
          reason: `Parameter ${parameter.name} is readonly`,
2249
        };
2250
      }
2251

2252
      // validate parameter against descriptor if not an undeclare action
2253
      if (parameter.type != ParameterType.PARAMETER_NOT_SET) {
2,451✔
2254
        try {
2,450✔
2255
          descriptor.validateParameter(parameter);
2,450✔
2256
        } catch {
UNCOV
2257
          return {
×
2258
            successful: false,
2259
            reason: `Parameter ${parameter.name} does not  readonly`,
2260
          };
2261
        }
2262
      }
2263
    }
2264

2265
    return {
2,451✔
2266
      successful: true,
2267
      reason: null,
2268
    };
2269
  }
2270

2271
  // Get a Map(nodeName->Parameter[]) of CLI parameter args that
2272
  // apply to 'this' node, .e.g., -p mynode:foo:=bar -p hello:=world
2273
  _getNativeParameterOverrides() {
2274
    const overrides = new Map();
891✔
2275

2276
    // Get native parameters from rcl context->global_arguments.
2277
    // rclnodejs returns an array of objects, 1 for each node e.g., -p my_node:foo:=bar,
2278
    // and a node named '/**' for global parameter rules,
2279
    // i.e., does not include a node identifier, e.g., -p color:=red
2280
    // {
2281
    //   name: string // node name
2282
    //   parameters[] = {
2283
    //     name: string
2284
    //     type: uint
2285
    //     value: object
2286
    // }
2287
    const cliParamOverrideData = rclnodejs.getParameterOverrides(
891✔
2288
      this.context.handle
2289
    );
2290

2291
    // convert native CLI parameterOverrides to Map<nodeName,Array<ParameterOverride>>
2292
    const cliParamOverrides = new Map();
891✔
2293
    if (cliParamOverrideData) {
891✔
2294
      for (let nodeParamData of cliParamOverrideData) {
8✔
2295
        const nodeName = nodeParamData.name;
12✔
2296
        const nodeParamOverrides = [];
12✔
2297
        for (let paramData of nodeParamData.parameters) {
12✔
2298
          const paramOverride = new Parameter(
17✔
2299
            paramData.name,
2300
            paramData.type,
2301
            paramData.value
2302
          );
2303
          nodeParamOverrides.push(paramOverride);
17✔
2304
        }
2305
        cliParamOverrides.set(nodeName, nodeParamOverrides);
12✔
2306
      }
2307
    }
2308

2309
    // collect global CLI global parameters, name == /**
2310
    let paramOverrides = cliParamOverrides.get('/**'); // array of ParameterOverrides
891✔
2311
    if (paramOverrides) {
891✔
2312
      for (const parameter of paramOverrides) {
5✔
2313
        overrides.set(parameter.name, parameter);
6✔
2314
      }
2315
    }
2316

2317
    // merge CLI node parameterOverrides with global parameterOverrides, replace existing
2318
    paramOverrides = cliParamOverrides.get(this.name()); // array of ParameterOverrides
891✔
2319
    if (paramOverrides) {
891✔
2320
      for (const parameter of paramOverrides) {
5✔
2321
        overrides.set(parameter.name, parameter);
7✔
2322
      }
2323
    }
2324

2325
    return overrides;
891✔
2326
  }
2327

2328
  /**
2329
   * Invokes the callback with a raw message of the given type. After the callback completes
2330
   * the message will be destroyed.
2331
   * @param {function} Type - Message type to create.
2332
   * @param {function} callback - Callback to invoke. First parameter will be the raw message,
2333
   * and the second is a function to retrieve the deserialized message.
2334
   * @returns {undefined}
2335
   */
2336
  _runWithMessageType(Type, callback) {
2337
    let message = new Type();
864✔
2338

2339
    callback(message.toRawROS(), () => {
864✔
2340
      let result = new Type();
815✔
2341
      result.deserialize(message.refObject);
815✔
2342

2343
      return result;
815✔
2344
    });
2345

2346
    Type.destroyRawROS(message);
864✔
2347
  }
2348

2349
  _addActionClient(actionClient) {
2350
    this._actionClients.push(actionClient);
52✔
2351
    this.syncHandles();
52✔
2352
  }
2353

2354
  _addActionServer(actionServer) {
2355
    this._actionServers.push(actionServer);
52✔
2356
    this.syncHandles();
52✔
2357
  }
2358

2359
  _getValidatedTopic(topicName, noDemangle) {
2360
    if (noDemangle) {
6!
UNCOV
2361
      return topicName;
×
2362
    }
2363
    const fqTopicName = rclnodejs.expandTopicName(
6✔
2364
      topicName,
2365
      this.name(),
2366
      this.namespace()
2367
    );
2368
    validateFullTopicName(fqTopicName);
6✔
2369
    return rclnodejs.remapTopicName(this.handle, fqTopicName);
6✔
2370
  }
2371

2372
  _getValidatedServiceName(serviceName, noDemangle) {
2373
    if (typeof serviceName !== 'string') {
4!
UNCOV
2374
      throw new TypeValidationError('serviceName', serviceName, 'string', {
×
2375
        nodeName: this.name(),
2376
      });
2377
    }
2378

2379
    if (noDemangle) {
4!
UNCOV
2380
      return serviceName;
×
2381
    }
2382

2383
    const resolvedServiceName = this.resolveServiceName(serviceName);
4✔
2384
    rclnodejs.validateTopicName(resolvedServiceName);
4✔
2385
    return resolvedServiceName;
4✔
2386
  }
2387
}
2388

2389
/**
2390
 * Create an Options instance initialized with default values.
2391
 * @returns {Options} - The new initialized instance.
2392
 * @static
2393
 * @example
2394
 * {
2395
 *   enableTypedArray: true,
2396
 *   isRaw: false,
2397
 *   qos: QoS.profileDefault,
2398
 *   contentFilter: undefined,
2399
 *   serializationMode: 'default',
2400
 * }
2401
 */
2402
Node.getDefaultOptions = function () {
26✔
2403
  return {
8,200✔
2404
    enableTypedArray: true,
2405
    isRaw: false,
2406
    qos: QoS.profileDefault,
2407
    contentFilter: undefined,
2408
    serializationMode: 'default',
2409
  };
2410
};
2411

2412
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