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

RobotWebTools / rclnodejs / 23889856338

02 Apr 2026 07:44AM UTC coverage: 85.351% (-0.5%) from 85.83%
23889856338

Pull #1468

github

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

1522 of 1939 branches covered (78.49%)

Branch coverage included in aggregate %.

57 of 85 new or added lines in 2 files covered. (67.06%)

3116 of 3495 relevant lines covered (89.16%)

444.34 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 = [],
104✔
87
    useGlobalArguments = true
104✔
88
  ) {
89
    super();
925✔
90

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

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

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

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

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

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

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

173
    this._parameterEventPublisher = this.createPublisher(
893✔
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();
893✔
180

181
    // override cli parameterOverrides with those specified in options
182
    if (options.parameterOverrides.length > 0) {
893✔
183
      for (const parameter of options.parameterOverrides) {
12✔
184
        if (!(parameter instanceof Parameter)) {
17✔
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);
16✔
195
      }
196
    }
197

198
    // initialize _parameters from parameterOverrides
199
    if (options.automaticallyDeclareParametersFromOverrides) {
892✔
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();
892✔
212
    this._timeSource = new TimeSource(this);
892✔
213
    this._timeSource.attachClock(this._clock);
892✔
214

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

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

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

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

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

266
    for (const subscription of subscriptionsReady) {
4,992✔
267
      if (subscription.isDestroyed()) continue;
398✔
268
      if (subscription.isRaw) {
389✔
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(
388✔
277
        subscription.typeClass,
278
        (message, deserialize) => {
279
          if (subscription.wantsMessageInfo) {
388✔
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);
384✔
292
            if (success) {
384✔
293
              subscription.processResponse(deserialize());
381✔
294
            }
295
          }
296
        }
297
      );
298
    }
299

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

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

306
    for (const client of clientsReady) {
4,992✔
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,992✔
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,992✔
340
      if (actionClient.isDestroyed()) continue;
128!
341

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

344
      if (properties.isGoalResponseReady) {
128✔
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) {
128✔
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) {
128✔
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) {
128✔
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) {
128✔
405
        this._runWithMessageType(
73✔
406
          actionClient.typeClass.impl.GoalStatusArray,
407
          (message, deserialize) => {
408
            let success = rclnodejs.actionTakeStatus(
73✔
409
              actionClient.handle,
410
              message
411
            );
412
            if (success) {
73!
413
              actionClient.processStatusMessage(deserialize());
73✔
414
            }
415
          }
416
        );
417
      }
418
    }
419

420
    for (const actionServer of actionServersReady) {
4,992✔
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,992✔
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,619✔
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);
398✔
561
    if (index > -1) {
398✔
562
      array.splice(index, 1);
396✔
563
    }
564
  }
565

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

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

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

582
  _validateOptions(options) {
583
    if (
8,261✔
584
      options !== undefined &&
8,365✔
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,241✔
593
      return Node.getDefaultOptions();
8,199✔
594
    }
595

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

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

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

608
    if (options.serializationMode === undefined) {
42✔
609
      options = Object.assign(options, { serializationMode: 'default' });
25✔
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;
41✔
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 {QoSOverridingOptions} [options.qosOverridingOptions] - If provided, declares read-only ROS parameters
713
   *  for the specified QoS policies (e.g. `qos_overrides./topic.publisher.depth`). These can be overridden at
714
   *  startup via `--ros-args -p` or `--params-file`. If qos is a profile string, it will be resolved to a
715
   *  mutable QoS object before overrides are applied.
716
   * @param {PublisherEventCallbacks} eventCallbacks - The event callbacks for the publisher.
717
   * @return {Publisher} - An instance of Publisher.
718
   */
719
  createPublisher(typeClass, topic, options, eventCallbacks) {
720
    return this._createPublisher(
1,272✔
721
      typeClass,
722
      topic,
723
      options,
724
      Publisher,
725
      eventCallbacks
726
    );
727
  }
728

729
  _createPublisher(typeClass, topic, options, publisherClass, eventCallbacks) {
730
    if (typeof typeClass === 'string' || typeof typeClass === 'object') {
1,275✔
731
      typeClass = loader.loadInterface(typeClass);
1,251✔
732
    }
733
    options = this._validateOptions(options);
1,268✔
734

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

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

778
    let publisher = publisherClass.createPublisher(
1,247✔
779
      this,
780
      typeClass,
781
      topic,
782
      options,
783
      eventCallbacks
784
    );
785
    debug('Finish creating publisher, topic = %s.', topic);
1,237✔
786
    this._publishers.push(publisher);
1,237✔
787
    return publisher;
1,237✔
788
  }
789

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

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

837
    if (typeof options === 'function') {
460✔
838
      callback = options;
343✔
839
      options = undefined;
343✔
840
    }
841
    options = this._validateOptions(options);
460✔
842

843
    if (typeof typeClass !== 'function') {
450✔
844
      throw new TypeValidationError('typeClass', typeClass, 'function', {
4✔
845
        nodeName: this.name(),
846
        entityType: 'subscription',
847
      });
848
    }
849
    if (typeof topic !== 'string') {
446✔
850
      throw new TypeValidationError('topic', topic, 'string', {
6✔
851
        nodeName: this.name(),
852
        entityType: 'subscription',
853
      });
854
    }
855
    if (typeof callback !== 'function') {
440!
856
      throw new TypeValidationError('callback', callback, 'function', {
×
857
        nodeName: this.name(),
858
        entityType: 'subscription',
859
        entityName: topic,
860
      });
861
    }
862
    if (
440!
863
      eventCallbacks &&
444✔
864
      !(eventCallbacks instanceof SubscriptionEventCallbacks)
865
    ) {
866
      throw new TypeValidationError(
×
867
        'eventCallbacks',
868
        eventCallbacks,
869
        'SubscriptionEventCallbacks',
870
        {
871
          nodeName: this.name(),
872
          entityType: 'subscription',
873
          entityName: topic,
874
        }
875
      );
876
    }
877

878
    // Apply QoS overriding options if provided
879
    if (options.qosOverridingOptions) {
440✔
880
      const resolvedTopic = this.resolveTopicName(topic);
2✔
881
      if (typeof options.qos === 'string' || !(options.qos instanceof QoS)) {
2!
NEW
882
        options.qos = _resolveQoS(options.qos);
×
883
      }
884
      declareQosParameters(
2✔
885
        'subscription',
886
        this,
887
        resolvedTopic,
888
        options.qos,
889
        options.qosOverridingOptions
890
      );
891
    }
892

893
    let subscription = Subscription.createSubscription(
440✔
894
      this,
895
      typeClass,
896
      topic,
897
      options,
898
      callback,
899
      eventCallbacks
900
    );
901
    debug('Finish creating subscription, topic = %s.', topic);
429✔
902
    this._subscriptions.push(subscription);
429✔
903
    this.syncHandles();
429✔
904

905
    return subscription;
429✔
906
  }
907

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

928
    const subscription = this.createSubscription(
7✔
929
      typeClass,
930
      topic,
931
      options,
932
      (message) => {
933
        if (observableSubscription) {
8!
934
          observableSubscription._emit(message);
8✔
935
        }
936
      },
937
      eventCallbacks
938
    );
939

940
    observableSubscription = new ObservableSubscription(subscription);
7✔
941
    return observableSubscription;
7✔
942
  }
943

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

961
    if (typeof typeClass !== 'function') {
189✔
962
      throw new TypeValidationError('typeClass', typeClass, 'function', {
8✔
963
        nodeName: this.name(),
964
        entityType: 'client',
965
      });
966
    }
967
    if (typeof serviceName !== 'string') {
181✔
968
      throw new TypeValidationError('serviceName', serviceName, 'string', {
12✔
969
        nodeName: this.name(),
970
        entityType: 'client',
971
      });
972
    }
973

974
    let client = Client.createClient(
169✔
975
      this.handle,
976
      serviceName,
977
      typeClass,
978
      options
979
    );
980
    debug('Finish creating client, service = %s.', serviceName);
159✔
981
    this._clients.push(client);
159✔
982
    this.syncHandles();
159✔
983

984
    return client;
159✔
985
  }
986

987
  /**
988
   * This callback is called when a request is sent to service
989
   * @callback RequestCallback
990
   * @param {Object} request - The request sent to the service
991
   * @param {Response} response - The response to client.
992
        Use [response.send()]{@link Response#send} to send response object to client
993
   * @return {undefined}
994
   * @see [Node.createService]{@link Node#createService}
995
   * @see [Client.sendRequest]{@link Client#sendRequest}
996
   * @see {@link Client}
997
   * @see {@link Service}
998
   * @see {@link Response#send}
999
   */
1000

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

1019
    if (typeof options === 'function') {
5,376✔
1020
      callback = options;
5,353✔
1021
      options = undefined;
5,353✔
1022
    }
1023
    options = this._validateOptions(options);
5,376✔
1024

1025
    if (typeof typeClass !== 'function') {
5,366✔
1026
      throw new TypeValidationError('typeClass', typeClass, 'function', {
4✔
1027
        nodeName: this.name(),
1028
        entityType: 'service',
1029
      });
1030
    }
1031
    if (typeof serviceName !== 'string') {
5,362✔
1032
      throw new TypeValidationError('serviceName', serviceName, 'string', {
6✔
1033
        nodeName: this.name(),
1034
        entityType: 'service',
1035
      });
1036
    }
1037
    if (typeof callback !== 'function') {
5,356!
1038
      throw new TypeValidationError('callback', callback, 'function', {
×
1039
        nodeName: this.name(),
1040
        entityType: 'service',
1041
        entityName: serviceName,
1042
      });
1043
    }
1044

1045
    let service = Service.createService(
5,356✔
1046
      this.handle,
1047
      serviceName,
1048
      typeClass,
1049
      options,
1050
      callback
1051
    );
1052
    debug('Finish creating service, service = %s.', serviceName);
5,346✔
1053
    this._services.push(service);
5,346✔
1054
    this.syncHandles();
5,346✔
1055

1056
    return service;
5,346✔
1057
  }
1058

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

1071
    const parameterClient = new ParameterClient(this, remoteNodeName, options);
103✔
1072
    debug(
103✔
1073
      'Finish creating parameter client for remote node = %s.',
1074
      remoteNodeName
1075
    );
1076
    this._parameterClients.push(parameterClient);
103✔
1077

1078
    return parameterClient;
103✔
1079
  }
1080

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

1102
    return watcher;
53✔
1103
  }
1104

1105
  /**
1106
   * Create a guard condition.
1107
   * @param {Function} callback - The callback to be called when the guard condition is triggered.
1108
   * @return {GuardCondition} - An instance of GuardCondition.
1109
   */
1110
  createGuardCondition(callback) {
1111
    if (typeof callback !== 'function') {
3!
1112
      throw new TypeValidationError('callback', callback, 'function', {
×
1113
        nodeName: this.name(),
1114
        entityType: 'guard_condition',
1115
      });
1116
    }
1117

1118
    let guard = GuardCondition.createGuardCondition(callback, this.context);
3✔
1119
    debug('Finish creating guard condition');
3✔
1120
    this._guards.push(guard);
3✔
1121
    this.syncHandles();
3✔
1122

1123
    return guard;
3✔
1124
  }
1125

1126
  /**
1127
   * Destroy all resource allocated by this node, including
1128
   * <code>Timer</code>s/<code>Publisher</code>s/<code>Subscription</code>s
1129
   * /<code>Client</code>s/<code>Service</code>s
1130
   * @return {undefined}
1131
   */
1132
  destroy() {
1133
    if (this.spinning) {
916✔
1134
      this.stop();
600✔
1135
    }
1136

1137
    // Action servers/clients require manual destruction due to circular reference with goal handles.
1138
    this._actionClients.forEach((actionClient) => actionClient.destroy());
916✔
1139
    this._actionServers.forEach((actionServer) => actionServer.destroy());
916✔
1140

1141
    this._parameterClients.forEach((paramClient) => paramClient.destroy());
916✔
1142
    this._parameterWatchers.forEach((watcher) => watcher.destroy());
916✔
1143
    this._parameterEventHandlers.forEach((handler) => handler.destroy());
916✔
1144

1145
    this.context.onNodeDestroyed(this);
916✔
1146

1147
    if (this._enableRosout) {
916✔
1148
      rclnodejs.finiRosoutPublisherForNode(this.handle);
891✔
1149
      this._enableRosout = false;
891✔
1150
    }
1151

1152
    this.handle.release();
916✔
1153
    this._clock = null;
916✔
1154
    this._timers = [];
916✔
1155
    this._publishers = [];
916✔
1156
    this._subscriptions = [];
916✔
1157
    this._clients = [];
916✔
1158
    this._services = [];
916✔
1159
    this._guards = [];
916✔
1160
    this._actionClients = [];
916✔
1161
    this._actionServers = [];
916✔
1162
    this._parameterClients = [];
916✔
1163
    this._parameterWatchers = [];
916✔
1164
    this._parameterEventHandlers = [];
916✔
1165

1166
    if (this._rateTimerServer) {
916✔
1167
      this._rateTimerServer.shutdown();
5✔
1168
      this._rateTimerServer = null;
5✔
1169
    }
1170
  }
1171

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

1197
  /**
1198
   * Destroy a Subscription.
1199
   * @param {Subscription} subscription - The Subscription to be destroyed.
1200
   * @return {undefined}
1201
   */
1202
  destroySubscription(subscription) {
1203
    if (!(subscription instanceof Subscription)) {
111✔
1204
      throw new TypeValidationError(
2✔
1205
        'subscription',
1206
        subscription,
1207
        'Subscription instance',
1208
        {
1209
          nodeName: this.name(),
1210
        }
1211
      );
1212
    }
1213
    if (subscription.events) {
109✔
1214
      subscription.events.forEach((event) => {
1✔
1215
        this._destroyEntity(event, this._events);
1✔
1216
      });
1217
      subscription.events = [];
1✔
1218
    }
1219

1220
    this._destroyEntity(subscription, this._subscriptions);
109✔
1221
  }
1222

1223
  /**
1224
   * Destroy a Client.
1225
   * @param {Client} client - The Client to be destroyed.
1226
   * @return {undefined}
1227
   */
1228
  destroyClient(client) {
1229
    if (!(client instanceof Client)) {
117✔
1230
      throw new TypeValidationError('client', client, 'Client instance', {
2✔
1231
        nodeName: this.name(),
1232
      });
1233
    }
1234
    this._destroyEntity(client, this._clients);
115✔
1235
  }
1236

1237
  /**
1238
   * Destroy a Service.
1239
   * @param {Service} service - The Service to be destroyed.
1240
   * @return {undefined}
1241
   */
1242
  destroyService(service) {
1243
    if (!(service instanceof Service)) {
8✔
1244
      throw new TypeValidationError('service', service, 'Service instance', {
2✔
1245
        nodeName: this.name(),
1246
      });
1247
    }
1248
    this._destroyEntity(service, this._services);
6✔
1249
  }
1250

1251
  /**
1252
   * Destroy a ParameterClient.
1253
   * @param {ParameterClient} parameterClient - The ParameterClient to be destroyed.
1254
   * @return {undefined}
1255
   */
1256
  destroyParameterClient(parameterClient) {
1257
    if (!(parameterClient instanceof ParameterClient)) {
54!
1258
      throw new TypeError('Invalid argument');
×
1259
    }
1260
    this._removeEntityFromArray(parameterClient, this._parameterClients);
54✔
1261
    parameterClient.destroy();
54✔
1262
  }
1263

1264
  /**
1265
   * Destroy a ParameterWatcher.
1266
   * @param {ParameterWatcher} watcher - The ParameterWatcher to be destroyed.
1267
   * @return {undefined}
1268
   */
1269
  destroyParameterWatcher(watcher) {
1270
    if (!(watcher instanceof ParameterWatcher)) {
1!
1271
      throw new TypeError('Invalid argument');
×
1272
    }
1273
    this._removeEntityFromArray(watcher, this._parameterWatchers);
1✔
1274
    watcher.destroy();
1✔
1275
  }
1276

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

1296
  /**
1297
   * Destroy a ParameterEventHandler.
1298
   * @param {ParameterEventHandler} handler - The handler to be destroyed.
1299
   * @return {undefined}
1300
   */
1301
  destroyParameterEventHandler(handler) {
1302
    if (!(handler instanceof ParameterEventHandler)) {
1!
1303
      throw new TypeError('Invalid argument');
×
1304
    }
1305
    this._removeEntityFromArray(handler, this._parameterEventHandlers);
1✔
1306
    handler.destroy();
1✔
1307
  }
1308

1309
  /**
1310
   * Destroy a Timer.
1311
   * @param {Timer} timer - The Timer to be destroyed.
1312
   * @return {undefined}
1313
   */
1314
  destroyTimer(timer) {
1315
    if (!(timer instanceof Timer)) {
8✔
1316
      throw new TypeValidationError('timer', timer, 'Timer instance', {
2✔
1317
        nodeName: this.name(),
1318
      });
1319
    }
1320
    this._destroyEntity(timer, this._timers);
6✔
1321
  }
1322

1323
  /**
1324
   * Destroy a guard condition.
1325
   * @param {GuardCondition} guard - The guard condition to be destroyed.
1326
   * @return {undefined}
1327
   */
1328
  destroyGuardCondition(guard) {
1329
    if (!(guard instanceof GuardCondition)) {
3!
1330
      throw new TypeValidationError('guard', guard, 'GuardCondition instance', {
×
1331
        nodeName: this.name(),
1332
      });
1333
    }
1334
    this._destroyEntity(guard, this._guards);
3✔
1335
  }
1336

1337
  /**
1338
   * Get the name of the node.
1339
   * @return {string}
1340
   */
1341
  name() {
1342
    return rclnodejs.getNodeName(this.handle);
5,335✔
1343
  }
1344

1345
  /**
1346
   * Get the namespace of the node.
1347
   * @return {string}
1348
   */
1349
  namespace() {
1350
    return rclnodejs.getNamespace(this.handle);
4,950✔
1351
  }
1352

1353
  /**
1354
   * Get the context in which this node was created.
1355
   * @return {Context}
1356
   */
1357
  get context() {
1358
    return this._context;
6,469✔
1359
  }
1360

1361
  /**
1362
   * Get the nodes logger.
1363
   * @returns {Logger} - The logger for the node.
1364
   */
1365
  getLogger() {
1366
    return this._logger;
243✔
1367
  }
1368

1369
  /**
1370
   * Get the clock used by the node.
1371
   * @returns {Clock} - The nodes clock.
1372
   */
1373
  getClock() {
1374
    return this._clock;
116✔
1375
  }
1376

1377
  /**
1378
   * Get the current time using the node's clock.
1379
   * @returns {Timer} - The current time.
1380
   */
1381
  now() {
1382
    return this.getClock().now();
2✔
1383
  }
1384

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

1401
  /**
1402
   * Get the list of published topics discovered by the provided node for the remote node name.
1403
   * @param {string} nodeName - The name of the node.
1404
   * @param {string} namespace - The name of the namespace.
1405
   * @param {boolean} noDemangle - If true topic names and types returned will not be demangled, default: false.
1406
   * @return {Array<{name: string, types: Array<string>}>} - An array of the names and types.
1407
   */
1408
  getSubscriptionNamesAndTypesByNode(nodeName, namespace, noDemangle = false) {
×
1409
    return rclnodejs.getSubscriptionNamesAndTypesByNode(
×
1410
      this.handle,
1411
      nodeName,
1412
      namespace,
1413
      noDemangle
1414
    );
1415
  }
1416

1417
  /**
1418
   * Get service names and types for which a remote node has servers.
1419
   * @param {string} nodeName - The name of the node.
1420
   * @param {string} namespace - The name of the namespace.
1421
   * @return {Array<{name: string, types: Array<string>}>} - An array of the names and types.
1422
   */
1423
  getServiceNamesAndTypesByNode(nodeName, namespace) {
1424
    return rclnodejs.getServiceNamesAndTypesByNode(
×
1425
      this.handle,
1426
      nodeName,
1427
      namespace
1428
    );
1429
  }
1430

1431
  /**
1432
   * Get service names and types for which a remote node has clients.
1433
   * @param {string} nodeName - The name of the node.
1434
   * @param {string} namespace - The name of the namespace.
1435
   * @return {Array<{name: string, types: Array<string>}>} - An array of the names and types.
1436
   */
1437
  getClientNamesAndTypesByNode(nodeName, namespace) {
1438
    return rclnodejs.getClientNamesAndTypesByNode(
×
1439
      this.handle,
1440
      nodeName,
1441
      namespace
1442
    );
1443
  }
1444

1445
  /**
1446
   * Get the list of topics discovered by the provided node.
1447
   * @param {boolean} noDemangle - If true topic names and types returned will not be demangled, default: false.
1448
   * @return {Array<{name: string, types: Array<string>}>} - An array of the names and types.
1449
   */
1450
  getTopicNamesAndTypes(noDemangle = false) {
×
1451
    return rclnodejs.getTopicNamesAndTypes(this.handle, noDemangle);
×
1452
  }
1453

1454
  /**
1455
   * Get the list of services discovered by the provided node.
1456
   * @return {Array<{name: string, types: Array<string>}>} - An array of the names and types.
1457
   */
1458
  getServiceNamesAndTypes() {
1459
    return rclnodejs.getServiceNamesAndTypes(this.handle);
2✔
1460
  }
1461

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

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

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

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

1586
  /**
1587
   * Get the list of nodes discovered by the provided node.
1588
   * @return {Array<string>} - An array of the names.
1589
   */
1590
  getNodeNames() {
1591
    return this.getNodeNamesAndNamespaces().map((item) => item.name);
41✔
1592
  }
1593

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

1602
  /**
1603
   * Get the list of nodes and their namespaces with enclaves discovered by the provided node.
1604
   * @return {Array<{name: string, namespace: string, enclave: string}>} An array of the names, namespaces and enclaves.
1605
   */
1606
  getNodeNamesAndNamespacesWithEnclaves() {
1607
    return rclnodejs.getNodeNames(this.handle, /*getEnclaves=*/ true);
1✔
1608
  }
1609

1610
  /**
1611
   * Return the number of publishers on a given topic.
1612
   * @param {string} topic - The name of the topic.
1613
   * @returns {number} - Number of publishers on the given topic.
1614
   */
1615
  countPublishers(topic) {
1616
    let expandedTopic = rclnodejs.expandTopicName(
6✔
1617
      topic,
1618
      this.name(),
1619
      this.namespace()
1620
    );
1621
    rclnodejs.validateTopicName(expandedTopic);
6✔
1622

1623
    return rclnodejs.countPublishers(this.handle, expandedTopic);
6✔
1624
  }
1625

1626
  /**
1627
   * Return the number of subscribers on a given topic.
1628
   * @param {string} topic - The name of the topic.
1629
   * @returns {number} - Number of subscribers on the given topic.
1630
   */
1631
  countSubscribers(topic) {
1632
    let expandedTopic = rclnodejs.expandTopicName(
6✔
1633
      topic,
1634
      this.name(),
1635
      this.namespace()
1636
    );
1637
    rclnodejs.validateTopicName(expandedTopic);
6✔
1638

1639
    return rclnodejs.countSubscribers(this.handle, expandedTopic);
6✔
1640
  }
1641

1642
  /**
1643
   * Get the number of clients on a given service name.
1644
   * @param {string} serviceName - the service name
1645
   * @returns {Number}
1646
   */
1647
  countClients(serviceName) {
1648
    if (DistroUtils.getDistroId() <= DistroUtils.getDistroId('humble')) {
2!
1649
      console.warn('countClients is not supported by this version of ROS 2');
×
1650
      return null;
×
1651
    }
1652
    return rclnodejs.countClients(this.handle, serviceName);
2✔
1653
  }
1654

1655
  /**
1656
   * Get the number of services on a given service name.
1657
   * @param {string} serviceName - the service name
1658
   * @returns {Number}
1659
   */
1660
  countServices(serviceName) {
1661
    if (DistroUtils.getDistroId() <= DistroUtils.getDistroId('humble')) {
1!
1662
      console.warn('countServices is not supported by this version of ROS 2');
×
1663
      return null;
×
1664
    }
1665
    return rclnodejs.countServices(this.handle, serviceName);
1✔
1666
  }
1667

1668
  /**
1669
   * Get the list of parameter-overrides found on the commandline and
1670
   * in the NodeOptions.parameter_overrides property.
1671
   *
1672
   * @return {Array<Parameter>} - An array of Parameters.
1673
   */
1674
  getParameterOverrides() {
1675
    return Array.from(this._parameterOverrides.values());
8✔
1676
  }
1677

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

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

1762
    const declaredDescriptors = [];
2,428✔
1763
    const declaredParameters = [];
2,428✔
1764
    const declaredParameterCollisions = [];
2,428✔
1765
    for (let i = 0; i < parameters.length; i++) {
2,428✔
1766
      let parameter =
1767
        !ignoreOverrides && this._parameterOverrides.has(parameters[i].name)
2,428✔
1768
          ? this._parameterOverrides.get(parameters[i].name)
1769
          : parameters[i];
1770

1771
      // stop processing parameters that have already been declared
1772
      if (this._parameters.has(parameter.name)) {
2,428!
1773
        declaredParameterCollisions.push(parameter);
×
1774
        continue;
×
1775
      }
1776

1777
      // create descriptor for parameter if not provided
1778
      let descriptor =
1779
        descriptors.length > 0
2,428✔
1780
          ? descriptors[i]
1781
          : ParameterDescriptor.fromParameter(parameter);
1782

1783
      descriptor.validate();
2,428✔
1784

1785
      declaredDescriptors.push(descriptor);
2,428✔
1786
      declaredParameters.push(parameter);
2,428✔
1787
    }
1788

1789
    if (declaredParameterCollisions.length > 0) {
2,428!
1790
      const errorMsg =
1791
        declaredParameterCollisions.length == 1
×
1792
          ? `Parameter(${declaredParameterCollisions[0]}) already declared.`
1793
          : `Multiple parameters already declared, e.g., Parameter(${declaredParameterCollisions[0]}).`;
1794
      throw new Error(errorMsg);
×
1795
    }
1796

1797
    // register descriptor
1798
    for (const descriptor of declaredDescriptors) {
2,428✔
1799
      this._parameterDescriptors.set(descriptor.name, descriptor);
2,428✔
1800
    }
1801

1802
    const result = this._setParametersAtomically(declaredParameters, true);
2,428✔
1803
    if (!result.successful) {
2,428!
1804
      // unregister descriptors
1805
      for (const descriptor of declaredDescriptors) {
×
1806
        this._parameterDescriptors.delete(descriptor.name);
×
1807
      }
1808

1809
      throw new Error(result.reason);
×
1810
    }
1811

1812
    return this.getParameters(declaredParameters.map((param) => param.name));
2,428✔
1813
  }
1814

1815
  /**
1816
   * Undeclare a parameter.
1817
   *
1818
   * Readonly parameters can not be undeclared or updated.
1819
   * @param {string} name - Name of parameter to undeclare.
1820
   * @return {undefined} -
1821
   */
1822
  undeclareParameter(name) {
1823
    if (!this.hasParameter(name)) return;
1!
1824

1825
    const descriptor = this.getParameterDescriptor(name);
1✔
1826
    if (descriptor.readOnly) {
1!
1827
      throw new Error(
×
1828
        `${name} parameter is read-only and can not be undeclared`
1829
      );
1830
    }
1831

1832
    this._parameters.delete(name);
1✔
1833
    this._parameterDescriptors.delete(name);
1✔
1834
  }
1835

1836
  /**
1837
   * Determine if a parameter has been declared.
1838
   * @param {string} name - name of parameter
1839
   * @returns {boolean} - Return true if parameter is declared; false otherwise.
1840
   */
1841
  hasParameter(name) {
1842
    return this._parameters.has(name);
6,083✔
1843
  }
1844

1845
  /**
1846
   * Get a declared parameter by name.
1847
   *
1848
   * If unable to locate a declared parameter then a
1849
   * parameter with type == PARAMETER_NOT_SET is returned.
1850
   *
1851
   * @param {string} name - The name of the parameter.
1852
   * @return {Parameter} - The parameter.
1853
   */
1854
  getParameter(name) {
1855
    return this.getParameters([name])[0];
1,812✔
1856
  }
1857

1858
  /**
1859
   * Get a list of parameters.
1860
   *
1861
   * Find and return the declared parameters.
1862
   * If no names are provided return all declared parameters.
1863
   *
1864
   * If unable to locate a declared parameter then a
1865
   * parameter with type == PARAMETER_NOT_SET is returned in
1866
   * it's place.
1867
   *
1868
   * @param {string[]} [names] - The names of the declared parameters
1869
   *    to find or null indicating to return all declared parameters.
1870
   * @return {Parameter[]} - The parameters.
1871
   */
1872
  getParameters(names = []) {
12✔
1873
    let params = [];
4,278✔
1874

1875
    if (names.length == 0) {
4,278✔
1876
      // get all parameters
1877
      params = [...this._parameters.values()];
12✔
1878
      return params;
12✔
1879
    }
1880

1881
    for (const name of names) {
4,266✔
1882
      const param = this.hasParameter(name)
4,273✔
1883
        ? this._parameters.get(name)
1884
        : new Parameter(name, ParameterType.PARAMETER_NOT_SET);
1885

1886
      params.push(param);
4,273✔
1887
    }
1888

1889
    return params;
4,266✔
1890
  }
1891

1892
  /**
1893
   * Get the types of given parameters.
1894
   *
1895
   * Return the types of given parameters.
1896
   *
1897
   * @param {string[]} [names] - The names of the declared parameters.
1898
   * @return {Uint8Array} - The types.
1899
   */
1900
  getParameterTypes(names = []) {
×
1901
    let types = [];
3✔
1902

1903
    for (const name of names) {
3✔
1904
      const descriptor = this._parameterDescriptors.get(name);
7✔
1905
      if (descriptor) {
7!
1906
        types.push(descriptor.type);
7✔
1907
      }
1908
    }
1909
    return types;
3✔
1910
  }
1911

1912
  /**
1913
   * Get the names of all declared parameters.
1914
   *
1915
   * @return {Array<string>} - The declared parameter names or empty array if
1916
   *    no parameters have been declared.
1917
   */
1918
  getParameterNames() {
1919
    return this.getParameters().map((param) => param.name);
53✔
1920
  }
1921

1922
  /**
1923
   * Determine if a parameter descriptor exists.
1924
   *
1925
   * @param {string} name - The name of a descriptor to for.
1926
   * @return {boolean} - true if a descriptor has been declared; otherwise false.
1927
   */
1928
  hasParameterDescriptor(name) {
1929
    return !!this.getParameterDescriptor(name);
2,458✔
1930
  }
1931

1932
  /**
1933
   * Get a declared parameter descriptor by name.
1934
   *
1935
   * If unable to locate a declared parameter descriptor then a
1936
   * descriptor with type == PARAMETER_NOT_SET is returned.
1937
   *
1938
   * @param {string} name - The name of the parameter descriptor to find.
1939
   * @return {ParameterDescriptor} - The parameter descriptor.
1940
   */
1941
  getParameterDescriptor(name) {
1942
    return this.getParameterDescriptors([name])[0];
4,917✔
1943
  }
1944

1945
  /**
1946
   * Find a list of declared ParameterDescriptors.
1947
   *
1948
   * If no names are provided return all declared descriptors.
1949
   *
1950
   * If unable to locate a declared descriptor then a
1951
   * descriptor with type == PARAMETER_NOT_SET is returned in
1952
   * it's place.
1953
   *
1954
   * @param {string[]} [names] - The names of the declared parameter
1955
   *    descriptors to find or null indicating to return all declared descriptors.
1956
   * @return {ParameterDescriptor[]} - The parameter descriptors.
1957
   */
1958
  getParameterDescriptors(names = []) {
×
1959
    let descriptors = [];
4,920✔
1960

1961
    if (names.length == 0) {
4,920!
1962
      // get all parameters
1963
      descriptors = [...this._parameterDescriptors.values()];
×
1964
      return descriptors;
×
1965
    }
1966

1967
    for (const name of names) {
4,920✔
1968
      let descriptor = this._parameterDescriptors.get(name);
4,922✔
1969
      if (!descriptor) {
4,922!
1970
        descriptor = new ParameterDescriptor(
×
1971
          name,
1972
          ParameterType.PARAMETER_NOT_SET
1973
        );
1974
      }
1975
      descriptors.push(descriptor);
4,922✔
1976
    }
1977

1978
    return descriptors;
4,920✔
1979
  }
1980

1981
  /**
1982
   * Replace a declared parameter.
1983
   *
1984
   * The parameter being replaced must be a declared parameter who's descriptor
1985
   * is not readOnly; otherwise an Error is thrown.
1986
   *
1987
   * @param {Parameter} parameter - The new parameter.
1988
   * @return {rclnodejs.rcl_interfaces.msg.SetParameterResult} - The result of the operation.
1989
   */
1990
  setParameter(parameter) {
1991
    const results = this.setParameters([parameter]);
15✔
1992
    return results[0];
15✔
1993
  }
1994

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

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

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

2065
    // give all SetParametersCallbacks a chance to veto this change
2066
    for (const callback of this._setParametersCallbacks) {
2,458✔
2067
      result = callback(parameters);
1,572✔
2068
      if (!result.successful) {
1,572✔
2069
        // a callback has vetoed a parameter change
2070
        return result;
1✔
2071
      }
2072
    }
2073

2074
    // collectively track updates to parameters for use
2075
    // when publishing a ParameterEvent
2076
    const newParameters = [];
2,457✔
2077
    const changedParameters = [];
2,457✔
2078
    const deletedParameters = [];
2,457✔
2079

2080
    for (const parameter of parameters) {
2,457✔
2081
      if (parameter.type == ParameterType.PARAMETER_NOT_SET) {
2,457✔
2082
        this.undeclareParameter(parameter.name);
1✔
2083
        deletedParameters.push(parameter);
1✔
2084
      } else {
2085
        this._parameters.set(parameter.name, parameter);
2,456✔
2086
        if (declareParameterMode) {
2,456✔
2087
          newParameters.push(parameter);
2,428✔
2088
        } else {
2089
          changedParameters.push(parameter);
28✔
2090
        }
2091
      }
2092
    }
2093

2094
    // create ParameterEvent
2095
    const parameterEvent = new (loader.loadInterface(
2,457✔
2096
      PARAMETER_EVENT_MSG_TYPE
2097
    ))();
2098

2099
    const { seconds, nanoseconds } = this._clock.now().secondsAndNanoseconds;
2,457✔
2100
    parameterEvent.stamp = {
2,457✔
2101
      sec: Number(seconds),
2102
      nanosec: Number(nanoseconds),
2103
    };
2104

2105
    parameterEvent.node =
2,457✔
2106
      this.namespace() === '/'
2,457✔
2107
        ? this.namespace() + this.name()
2108
        : this.namespace() + '/' + this.name();
2109

2110
    if (newParameters.length > 0) {
2,457✔
2111
      parameterEvent['new_parameters'] = newParameters.map((parameter) =>
2,428✔
2112
        parameter.toParameterMessage()
2,428✔
2113
      );
2114
    }
2115
    if (changedParameters.length > 0) {
2,457✔
2116
      parameterEvent['changed_parameters'] = changedParameters.map(
28✔
2117
        (parameter) => parameter.toParameterMessage()
28✔
2118
      );
2119
    }
2120
    if (deletedParameters.length > 0) {
2,457✔
2121
      parameterEvent['deleted_parameters'] = deletedParameters.map(
1✔
2122
        (parameter) => parameter.toParameterMessage()
1✔
2123
      );
2124
    }
2125

2126
    // Publish ParameterEvent.
2127
    this._parameterEventPublisher.publish(parameterEvent);
2,457✔
2128

2129
    return {
2,457✔
2130
      successful: true,
2131
      reason: '',
2132
    };
2133
  }
2134

2135
  /**
2136
   * This callback is called when declaring a parameter or setting a parameter.
2137
   * The callback is provided a list of parameters and returns a SetParameterResult
2138
   * to indicate approval or veto of the operation.
2139
   *
2140
   * @callback SetParametersCallback
2141
   * @param {Parameter[]} parameters - The message published
2142
   * @returns {rcl_interfaces.msg.SetParameterResult} -
2143
   *
2144
   * @see [Node.addOnSetParametersCallback]{@link Node#addOnSetParametersCallback}
2145
   * @see [Node.removeOnSetParametersCallback]{@link Node#removeOnSetParametersCallback}
2146
   */
2147

2148
  /**
2149
   * Add a callback to the front of the list of callbacks invoked for parameter declaration
2150
   * and setting. No checks are made for duplicate callbacks.
2151
   *
2152
   * @param {SetParametersCallback} callback - The callback to add.
2153
   * @returns {undefined}
2154
   */
2155
  addOnSetParametersCallback(callback) {
2156
    this._setParametersCallbacks.unshift(callback);
910✔
2157
  }
2158

2159
  /**
2160
   * Remove a callback from the list of SetParametersCallbacks.
2161
   * If the callback is not found the process is a nop.
2162
   *
2163
   * @param {SetParametersCallback} callback - The callback to be removed
2164
   * @returns {undefined}
2165
   */
2166
  removeOnSetParametersCallback(callback) {
2167
    const idx = this._setParametersCallbacks.indexOf(callback);
2✔
2168
    if (idx > -1) {
2!
2169
      this._setParametersCallbacks.splice(idx, 1);
2✔
2170
    }
2171
  }
2172

2173
  /**
2174
   * Get the fully qualified name of the node.
2175
   *
2176
   * @returns {string} - String containing the fully qualified name of the node.
2177
   */
2178
  getFullyQualifiedName() {
2179
    return rclnodejs.getFullyQualifiedName(this.handle);
1✔
2180
  }
2181

2182
  /**
2183
   * Get the RMW implementation identifier
2184
   * @returns {string} - The RMW implementation identifier.
2185
   */
2186
  getRMWImplementationIdentifier() {
2187
    return rclnodejs.getRMWImplementationIdentifier();
1✔
2188
  }
2189

2190
  /**
2191
   * Return a topic name expanded and remapped.
2192
   * @param {string} topicName - Topic name to be expanded and remapped.
2193
   * @param {boolean} [onlyExpand=false] - If `true`, remapping rules won't be applied.
2194
   * @returns {string} - A fully qualified topic name.
2195
   */
2196
  resolveTopicName(topicName, onlyExpand = false) {
14✔
2197
    if (typeof topicName !== 'string') {
15!
2198
      throw new TypeValidationError('topicName', topicName, 'string', {
×
2199
        nodeName: this.name(),
2200
      });
2201
    }
2202
    return rclnodejs.resolveName(
15✔
2203
      this.handle,
2204
      topicName,
2205
      onlyExpand,
2206
      /*isService=*/ false
2207
    );
2208
  }
2209

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

2230
  // returns on 1st error or result {successful, reason}
2231
  _validateParameters(parameters = [], declareParameterMode = false) {
×
2232
    for (const parameter of parameters) {
2,458✔
2233
      // detect invalid parameter
2234
      try {
2,458✔
2235
        parameter.validate();
2,458✔
2236
      } catch {
2237
        return {
×
2238
          successful: false,
2239
          reason: `Invalid ${parameter.name}`,
2240
        };
2241
      }
2242

2243
      // detect undeclared parameter
2244
      if (!this.hasParameterDescriptor(parameter.name)) {
2,458!
2245
        return {
×
2246
          successful: false,
2247
          reason: `Parameter ${parameter.name} has not been declared`,
2248
        };
2249
      }
2250

2251
      // detect readonly parameter that can not be updated
2252
      const descriptor = this.getParameterDescriptor(parameter.name);
2,458✔
2253
      if (!declareParameterMode && descriptor.readOnly) {
2,458!
2254
        return {
×
2255
          successful: false,
2256
          reason: `Parameter ${parameter.name} is readonly`,
2257
        };
2258
      }
2259

2260
      // validate parameter against descriptor if not an undeclare action
2261
      if (parameter.type != ParameterType.PARAMETER_NOT_SET) {
2,458✔
2262
        try {
2,457✔
2263
          descriptor.validateParameter(parameter);
2,457✔
2264
        } catch {
2265
          return {
×
2266
            successful: false,
2267
            reason: `Parameter ${parameter.name} does not  readonly`,
2268
          };
2269
        }
2270
      }
2271
    }
2272

2273
    return {
2,458✔
2274
      successful: true,
2275
      reason: null,
2276
    };
2277
  }
2278

2279
  // Get a Map(nodeName->Parameter[]) of CLI parameter args that
2280
  // apply to 'this' node, .e.g., -p mynode:foo:=bar -p hello:=world
2281
  _getNativeParameterOverrides() {
2282
    const overrides = new Map();
893✔
2283

2284
    // Get native parameters from rcl context->global_arguments.
2285
    // rclnodejs returns an array of objects, 1 for each node e.g., -p my_node:foo:=bar,
2286
    // and a node named '/**' for global parameter rules,
2287
    // i.e., does not include a node identifier, e.g., -p color:=red
2288
    // {
2289
    //   name: string // node name
2290
    //   parameters[] = {
2291
    //     name: string
2292
    //     type: uint
2293
    //     value: object
2294
    // }
2295
    const cliParamOverrideData = rclnodejs.getParameterOverrides(
893✔
2296
      this.context.handle
2297
    );
2298

2299
    // convert native CLI parameterOverrides to Map<nodeName,Array<ParameterOverride>>
2300
    const cliParamOverrides = new Map();
893✔
2301
    if (cliParamOverrideData) {
893✔
2302
      for (let nodeParamData of cliParamOverrideData) {
8✔
2303
        const nodeName = nodeParamData.name;
12✔
2304
        const nodeParamOverrides = [];
12✔
2305
        for (let paramData of nodeParamData.parameters) {
12✔
2306
          const paramOverride = new Parameter(
17✔
2307
            paramData.name,
2308
            paramData.type,
2309
            paramData.value
2310
          );
2311
          nodeParamOverrides.push(paramOverride);
17✔
2312
        }
2313
        cliParamOverrides.set(nodeName, nodeParamOverrides);
12✔
2314
      }
2315
    }
2316

2317
    // collect global CLI global parameters, name == /**
2318
    let paramOverrides = cliParamOverrides.get('/**'); // array of ParameterOverrides
893✔
2319
    if (paramOverrides) {
893✔
2320
      for (const parameter of paramOverrides) {
5✔
2321
        overrides.set(parameter.name, parameter);
6✔
2322
      }
2323
    }
2324

2325
    // merge CLI node parameterOverrides with global parameterOverrides, replace existing
2326
    paramOverrides = cliParamOverrides.get(this.name()); // array of ParameterOverrides
893✔
2327
    if (paramOverrides) {
893✔
2328
      for (const parameter of paramOverrides) {
5✔
2329
        overrides.set(parameter.name, parameter);
7✔
2330
      }
2331
    }
2332

2333
    return overrides;
893✔
2334
  }
2335

2336
  /**
2337
   * Invokes the callback with a raw message of the given type. After the callback completes
2338
   * the message will be destroyed.
2339
   * @param {function} Type - Message type to create.
2340
   * @param {function} callback - Callback to invoke. First parameter will be the raw message,
2341
   * and the second is a function to retrieve the deserialized message.
2342
   * @returns {undefined}
2343
   */
2344
  _runWithMessageType(Type, callback) {
2345
    let message = new Type();
865✔
2346

2347
    callback(message.toRawROS(), () => {
865✔
2348
      let result = new Type();
816✔
2349
      result.deserialize(message.refObject);
816✔
2350

2351
      return result;
816✔
2352
    });
2353

2354
    Type.destroyRawROS(message);
865✔
2355
  }
2356

2357
  _addActionClient(actionClient) {
2358
    this._actionClients.push(actionClient);
52✔
2359
    this.syncHandles();
52✔
2360
  }
2361

2362
  _addActionServer(actionServer) {
2363
    this._actionServers.push(actionServer);
52✔
2364
    this.syncHandles();
52✔
2365
  }
2366

2367
  _getValidatedTopic(topicName, noDemangle) {
2368
    if (noDemangle) {
6!
2369
      return topicName;
×
2370
    }
2371
    const fqTopicName = rclnodejs.expandTopicName(
6✔
2372
      topicName,
2373
      this.name(),
2374
      this.namespace()
2375
    );
2376
    validateFullTopicName(fqTopicName);
6✔
2377
    return rclnodejs.remapTopicName(this.handle, fqTopicName);
6✔
2378
  }
2379

2380
  _getValidatedServiceName(serviceName, noDemangle) {
2381
    if (typeof serviceName !== 'string') {
4!
2382
      throw new TypeValidationError('serviceName', serviceName, 'string', {
×
2383
        nodeName: this.name(),
2384
      });
2385
    }
2386

2387
    if (noDemangle) {
4!
2388
      return serviceName;
×
2389
    }
2390

2391
    const resolvedServiceName = this.resolveServiceName(serviceName);
4✔
2392
    rclnodejs.validateTopicName(resolvedServiceName);
4✔
2393
    return resolvedServiceName;
4✔
2394
  }
2395
}
2396

2397
/**
2398
 * Create an Options instance initialized with default values.
2399
 * @returns {Options} - The new initialized instance.
2400
 * @static
2401
 * @example
2402
 * {
2403
 *   enableTypedArray: true,
2404
 *   isRaw: false,
2405
 *   qos: QoS.profileDefault,
2406
 *   contentFilter: undefined,
2407
 *   serializationMode: 'default',
2408
 * }
2409
 */
2410
Node.getDefaultOptions = function () {
26✔
2411
  return {
8,210✔
2412
    enableTypedArray: true,
2413
    isRaw: false,
2414
    qos: QoS.profileDefault,
2415
    contentFilter: undefined,
2416
    serializationMode: 'default',
2417
  };
2418
};
2419

2420
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