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

RobotWebTools / rclnodejs / 19071410104

04 Nov 2025 02:08PM UTC coverage: 83.072% (+0.4%) from 82.711%
19071410104

Pull #1320

github

web-flow
Merge 9cad4567e into 3ad842cc4
Pull Request #1320: feat: add structured error handling with class error hierarchy

1032 of 1365 branches covered (75.6%)

Branch coverage included in aggregate %.

161 of 239 new or added lines in 25 files covered. (67.36%)

29 existing lines in 1 file now uncovered.

2354 of 2711 relevant lines covered (86.83%)

459.93 hits per line

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

87.63
/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 Publisher = require('./publisher.js');
26✔
43
const QoS = require('./qos.js');
26✔
44
const Rates = require('./rate.js');
26✔
45
const Service = require('./service.js');
26✔
46
const Subscription = require('./subscription.js');
26✔
47
const TimeSource = require('./time_source.js');
26✔
48
const Timer = require('./timer.js');
26✔
49
const TypeDescriptionService = require('./type_description_service.js');
26✔
50
const Entity = require('./entity.js');
26✔
51
const { SubscriptionEventCallbacks } = require('../lib/event_handler.js');
26✔
52
const { PublisherEventCallbacks } = require('../lib/event_handler.js');
26✔
53
const { validateFullTopicName } = require('./validator.js');
26✔
54

55
// Parameter event publisher constants
56
const PARAMETER_EVENT_MSG_TYPE = 'rcl_interfaces/msg/ParameterEvent';
26✔
57
const PARAMETER_EVENT_TOPIC = 'parameter_events';
26✔
58

59
/**
60
 * @class - Class representing a Node in ROS
61
 */
62

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

83
    if (typeof nodeName !== 'string') {
721✔
84
      throw new TypeValidationError('nodeName', nodeName, 'string');
12✔
85
    }
86
    if (typeof namespace !== 'string') {
709✔
87
      throw new TypeValidationError('namespace', namespace, 'string');
10✔
88
    }
89

90
    this._init(nodeName, namespace, options, context, args, useGlobalArguments);
699✔
91
    debug(
690✔
92
      'Finish initializing node, name = %s and namespace = %s.',
93
      nodeName,
94
      namespace
95
    );
96
  }
97

98
  _init(name, namespace, options, context, args, useGlobalArguments) {
99
    this.handle = rclnodejs.createNode(
699✔
100
      name,
101
      namespace,
102
      context.handle,
103
      args,
104
      useGlobalArguments
105
    );
106
    Object.defineProperty(this, 'handle', {
690✔
107
      configurable: false,
108
      writable: false,
109
    }); // make read-only
110

111
    this._context = context;
690✔
112
    this.context.onNodeCreated(this);
690✔
113

114
    this._publishers = [];
690✔
115
    this._subscriptions = [];
690✔
116
    this._clients = [];
690✔
117
    this._services = [];
690✔
118
    this._timers = [];
690✔
119
    this._guards = [];
690✔
120
    this._events = [];
690✔
121
    this._actionClients = [];
690✔
122
    this._actionServers = [];
690✔
123
    this._parameterClients = [];
690✔
124
    this._rateTimerServer = null;
690✔
125
    this._parameterDescriptors = new Map();
690✔
126
    this._parameters = new Map();
690✔
127
    this._parameterService = null;
690✔
128
    this._typeDescriptionService = null;
690✔
129
    this._parameterEventPublisher = null;
690✔
130
    this._setParametersCallbacks = [];
690✔
131
    this._logger = new Logging(rclnodejs.getNodeLoggerName(this.handle));
690✔
132
    this._spinning = false;
690✔
133

134
    this._parameterEventPublisher = this.createPublisher(
690✔
135
      PARAMETER_EVENT_MSG_TYPE,
136
      PARAMETER_EVENT_TOPIC
137
    );
138

139
    // initialize _parameterOverrides from parameters defined on the commandline
140
    this._parameterOverrides = this._getNativeParameterOverrides();
690✔
141

142
    // override cli parameterOverrides with those specified in options
143
    if (options.parameterOverrides.length > 0) {
690✔
144
      for (const parameter of options.parameterOverrides) {
8✔
145
        if ((!parameter) instanceof Parameter) {
13!
NEW
146
          throw new TypeValidationError(
×
147
            'parameterOverride',
148
            parameter,
149
            'Parameter instance',
150
            {
151
              nodeName: name,
152
            }
153
          );
154
        }
155
        this._parameterOverrides.set(parameter.name, parameter);
13✔
156
      }
157
    }
158

159
    // initialize _parameters from parameterOverrides
160
    if (options.automaticallyDeclareParametersFromOverrides) {
690✔
161
      for (const parameter of this._parameterOverrides.values()) {
5✔
162
        parameter.validate();
10✔
163
        const descriptor = ParameterDescriptor.fromParameter(parameter);
10✔
164
        this._parameters.set(parameter.name, parameter);
10✔
165
        this._parameterDescriptors.set(parameter.name, descriptor);
10✔
166
      }
167
    }
168

169
    // Clock that has support for ROS time.
170
    // Note: parameter overrides and parameter event publisher need to be ready at this point
171
    // to be able to declare 'use_sim_time' if it was not declared yet.
172
    this._clock = new Clock.ROSClock();
690✔
173
    this._timeSource = new TimeSource(this);
690✔
174
    this._timeSource.attachClock(this._clock);
690✔
175

176
    if (options.startParameterServices) {
690✔
177
      this._parameterService = new ParameterService(this);
684✔
178
      this._parameterService.start();
684✔
179
    }
180

181
    if (
690!
182
      DistroUtils.getDistroId() >= DistroUtils.getDistroId('jazzy') &&
1,380✔
183
      options.startTypeDescriptionService
184
    ) {
185
      this._typeDescriptionService = new TypeDescriptionService(this);
690✔
186
      this._typeDescriptionService.start();
690✔
187
    }
188
  }
189

190
  execute(handles) {
191
    let timersReady = this._timers.filter((timer) =>
5,510✔
192
      handles.includes(timer.handle)
4,631✔
193
    );
194
    let guardsReady = this._guards.filter((guard) =>
5,510✔
195
      handles.includes(guard.handle)
3✔
196
    );
197
    let subscriptionsReady = this._subscriptions.filter((subscription) =>
5,510✔
198
      handles.includes(subscription.handle)
448✔
199
    );
200
    let clientsReady = this._clients.filter((client) =>
5,510✔
201
      handles.includes(client.handle)
285✔
202
    );
203
    let servicesReady = this._services.filter((service) =>
5,510✔
204
      handles.includes(service.handle)
20,760✔
205
    );
206
    let actionClientsReady = this._actionClients.filter((actionClient) =>
5,510✔
207
      handles.includes(actionClient.handle)
156✔
208
    );
209
    let actionServersReady = this._actionServers.filter((actionServer) =>
5,510✔
210
      handles.includes(actionServer.handle)
156✔
211
    );
212
    let eventsReady = this._events.filter((event) =>
5,510✔
213
      handles.includes(event.handle)
4✔
214
    );
215

216
    timersReady.forEach((timer) => {
5,510✔
217
      if (timer.isReady()) {
4,621✔
218
        rclnodejs.callTimer(timer.handle);
4,575✔
219
        timer.callback();
4,575✔
220
      }
221
    });
222

223
    eventsReady.forEach((event) => {
5,510✔
224
      event.takeData();
4✔
225
    });
226

227
    for (const subscription of subscriptionsReady) {
5,510✔
228
      if (subscription.isDestroyed()) continue;
411✔
229
      if (subscription.isRaw) {
404✔
230
        let rawMessage = rclnodejs.rclTakeRaw(subscription.handle);
1✔
231
        if (rawMessage) {
1!
232
          subscription.processResponse(rawMessage);
1✔
233
        }
234
        continue;
1✔
235
      }
236

237
      this._runWithMessageType(
403✔
238
        subscription.typeClass,
239
        (message, deserialize) => {
240
          let success = rclnodejs.rclTake(subscription.handle, message);
403✔
241
          if (success) {
403✔
242
            subscription.processResponse(deserialize());
349✔
243
          }
244
        }
245
      );
246
    }
247

248
    for (const guard of guardsReady) {
5,510✔
249
      if (guard.isDestroyed()) continue;
3!
250

251
      guard.callback();
3✔
252
    }
253

254
    for (const client of clientsReady) {
5,510✔
255
      if (client.isDestroyed()) continue;
155!
256
      this._runWithMessageType(
155✔
257
        client.typeClass.Response,
258
        (message, deserialize) => {
259
          let sequenceNumber = rclnodejs.rclTakeResponse(
155✔
260
            client.handle,
261
            message
262
          );
263
          if (sequenceNumber !== undefined) {
155✔
264
            client.processResponse(sequenceNumber, deserialize());
84✔
265
          }
266
        }
267
      );
268
    }
269

270
    for (const service of servicesReady) {
5,510✔
271
      if (service.isDestroyed()) continue;
173!
272
      this._runWithMessageType(
173✔
273
        service.typeClass.Request,
274
        (message, deserialize) => {
275
          let header = rclnodejs.rclTakeRequest(
173✔
276
            service.handle,
277
            this.handle,
278
            message
279
          );
280
          if (header) {
173✔
281
            service.processRequest(header, deserialize());
89✔
282
          }
283
        }
284
      );
285
    }
286

287
    for (const actionClient of actionClientsReady) {
5,510✔
288
      if (actionClient.isDestroyed()) continue;
74!
289

290
      const properties = actionClient.handle.properties;
74✔
291

292
      if (properties.isGoalResponseReady) {
74✔
293
        this._runWithMessageType(
35✔
294
          actionClient.typeClass.impl.SendGoalService.Response,
295
          (message, deserialize) => {
296
            let sequence = rclnodejs.actionTakeGoalResponse(
35✔
297
              actionClient.handle,
298
              message
299
            );
300
            if (sequence != undefined) {
35✔
301
              actionClient.processGoalResponse(sequence, deserialize());
30✔
302
            }
303
          }
304
        );
305
      }
306

307
      if (properties.isCancelResponseReady) {
74✔
308
        this._runWithMessageType(
6✔
309
          actionClient.typeClass.impl.CancelGoal.Response,
310
          (message, deserialize) => {
311
            let sequence = rclnodejs.actionTakeCancelResponse(
6✔
312
              actionClient.handle,
313
              message
314
            );
315
            if (sequence != undefined) {
6✔
316
              actionClient.processCancelResponse(sequence, deserialize());
4✔
317
            }
318
          }
319
        );
320
      }
321

322
      if (properties.isResultResponseReady) {
74✔
323
        this._runWithMessageType(
15✔
324
          actionClient.typeClass.impl.GetResultService.Response,
325
          (message, deserialize) => {
326
            let sequence = rclnodejs.actionTakeResultResponse(
15✔
327
              actionClient.handle,
328
              message
329
            );
330
            if (sequence != undefined) {
15!
331
              actionClient.processResultResponse(sequence, deserialize());
15✔
332
            }
333
          }
334
        );
335
      }
336

337
      if (properties.isFeedbackReady) {
74✔
338
        this._runWithMessageType(
8✔
339
          actionClient.typeClass.impl.FeedbackMessage,
340
          (message, deserialize) => {
341
            let success = rclnodejs.actionTakeFeedback(
8✔
342
              actionClient.handle,
343
              message
344
            );
345
            if (success) {
8✔
346
              actionClient.processFeedbackMessage(deserialize());
5✔
347
            }
348
          }
349
        );
350
      }
351

352
      if (properties.isStatusReady) {
74✔
353
        this._runWithMessageType(
39✔
354
          actionClient.typeClass.impl.GoalStatusArray,
355
          (message, deserialize) => {
356
            let success = rclnodejs.actionTakeStatus(
39✔
357
              actionClient.handle,
358
              message
359
            );
360
            if (success) {
39✔
361
              actionClient.processStatusMessage(deserialize());
34✔
362
            }
363
          }
364
        );
365
      }
366
    }
367

368
    for (const actionServer of actionServersReady) {
5,510✔
369
      if (actionServer.isDestroyed()) continue;
92!
370

371
      const properties = actionServer.handle.properties;
92✔
372

373
      if (properties.isGoalRequestReady) {
92✔
374
        this._runWithMessageType(
31✔
375
          actionServer.typeClass.impl.SendGoalService.Request,
376
          (message, deserialize) => {
377
            const result = rclnodejs.actionTakeGoalRequest(
31✔
378
              actionServer.handle,
379
              message
380
            );
381
            if (result) {
31✔
382
              actionServer.processGoalRequest(result, deserialize());
30✔
383
            }
384
          }
385
        );
386
      }
387

388
      if (properties.isCancelRequestReady) {
92✔
389
        this._runWithMessageType(
7✔
390
          actionServer.typeClass.impl.CancelGoal.Request,
391
          (message, deserialize) => {
392
            const result = rclnodejs.actionTakeCancelRequest(
7✔
393
              actionServer.handle,
394
              message
395
            );
396
            if (result) {
7✔
397
              actionServer.processCancelRequest(result, deserialize());
4✔
398
            }
399
          }
400
        );
401
      }
402

403
      if (properties.isResultRequestReady) {
92✔
404
        this._runWithMessageType(
23✔
405
          actionServer.typeClass.impl.GetResultService.Request,
406
          (message, deserialize) => {
407
            const result = rclnodejs.actionTakeResultRequest(
23✔
408
              actionServer.handle,
409
              message
410
            );
411
            if (result) {
23✔
412
              actionServer.processResultRequest(result, deserialize());
15✔
413
            }
414
          }
415
        );
416
      }
417

418
      if (properties.isGoalExpired) {
92✔
419
        let GoalInfoArray = ActionInterfaces.GoalInfo.ArrayType;
4✔
420
        let message = new GoalInfoArray(actionServer._goalHandles.size);
4✔
421
        let count = rclnodejs.actionExpireGoals(
4✔
422
          actionServer.handle,
423
          actionServer._goalHandles.size,
424
          message._refArray.buffer
425
        );
426
        if (count > 0) {
4✔
427
          actionServer.processGoalExpired(message, count);
3✔
428
        }
429
        GoalInfoArray.freeArray(message);
4✔
430
      }
431
    }
432

433
    // At this point it is safe to clear the cache of any
434
    // destroyed entity references
435
    Entity._gcHandles();
5,510✔
436
  }
437

438
  /**
439
   * Determine if this node is spinning.
440
   * @returns {boolean} - true when spinning; otherwise returns false.
441
   */
442
  get spinning() {
443
    return this._spinning;
4,252✔
444
  }
445

446
  /**
447
   * Trigger the event loop to continuously check for and route.
448
   * incoming events.
449
   * @param {Node} node - The node to be spun up.
450
   * @param {number} [timeout=10] - Timeout to wait in milliseconds. Block forever if negative. Don't wait if 0.
451
   * @throws {Error} If the node is already spinning.
452
   * @return {undefined}
453
   */
454
  spin(timeout = 10) {
15✔
455
    if (this.spinning) {
529!
456
      throw new Error('The node is already spinning.');
×
457
    }
458
    this.start(this.context.handle, timeout);
529✔
459
    this._spinning = true;
529✔
460
  }
461

462
  /**
463
   * Use spin().
464
   * @deprecated, since 0.18.0
465
   */
466
  startSpinning(timeout) {
467
    this.spin(timeout);
×
468
  }
469

470
  /**
471
   * Terminate spinning - no further events will be received.
472
   * @returns {undefined}
473
   */
474
  stop() {
475
    super.stop();
529✔
476
    this._spinning = false;
529✔
477
  }
478

479
  /**
480
   * Terminate spinning - no further events will be received.
481
   * @returns {undefined}
482
   * @deprecated since 0.18.0, Use stop().
483
   */
484
  stopSpinning() {
485
    super.stop();
×
486
    this._spinning = false;
×
487
  }
488

489
  /**
490
   * Spin the node and trigger the event loop to check for one incoming event. Thereafter the node
491
   * will not received additional events until running additional calls to spin() or spinOnce().
492
   * @param {Node} node - The node to be spun.
493
   * @param {number} [timeout=10] - Timeout to wait in milliseconds. Block forever if negative. Don't wait if 0.
494
   * @throws {Error} If the node is already spinning.
495
   * @return {undefined}
496
   */
497
  spinOnce(timeout = 10) {
2✔
498
    if (this.spinning) {
3,009✔
499
      throw new Error('The node is already spinning.');
2✔
500
    }
501
    super.spinOnce(this.context.handle, timeout);
3,007✔
502
  }
503

504
  _removeEntityFromArray(entity, array) {
505
    let index = array.indexOf(entity);
197✔
506
    if (index > -1) {
197✔
507
      array.splice(index, 1);
195✔
508
    }
509
  }
510

511
  _destroyEntity(entity, array, syncHandles = true) {
197✔
512
    if (entity['isDestroyed'] && entity.isDestroyed()) return;
204✔
513

514
    this._removeEntityFromArray(entity, array);
196✔
515
    if (syncHandles) {
196✔
516
      this.syncHandles();
191✔
517
    }
518

519
    if (entity['_destroy']) {
196✔
520
      entity._destroy();
190✔
521
    } else {
522
      // guards and timers
523
      entity.handle.release();
6✔
524
    }
525
  }
526

527
  _validateOptions(options) {
528
    if (
6,500✔
529
      options !== undefined &&
6,566✔
530
      (options === null || typeof options !== 'object')
531
    ) {
532
      throw new TypeValidationError('options', options, 'object', {
20✔
533
        nodeName: this.name(),
534
      });
535
    }
536

537
    if (options === undefined) {
6,480✔
538
      return Node.getDefaultOptions();
6,457✔
539
    }
540

541
    if (options.enableTypedArray === undefined) {
23✔
542
      options = Object.assign(options, { enableTypedArray: true });
11✔
543
    }
544

545
    if (options.qos === undefined) {
23✔
546
      options = Object.assign(options, { qos: QoS.profileDefault });
12✔
547
    }
548

549
    if (options.isRaw === undefined) {
23✔
550
      options = Object.assign(options, { isRaw: false });
13✔
551
    }
552

553
    if (options.serializationMode === undefined) {
23✔
554
      options = Object.assign(options, { serializationMode: 'default' });
7✔
555
    } else if (!isValidSerializationMode(options.serializationMode)) {
16✔
556
      throw new ValidationError(
1✔
557
        `Invalid serializationMode: ${options.serializationMode}. Valid modes are: 'default', 'plain', 'json'`,
558
        {
559
          code: 'INVALID_SERIALIZATION_MODE',
560
          argumentName: 'serializationMode',
561
          providedValue: options.serializationMode,
562
          expectedType: "'default' | 'plain' | 'json'",
563
          nodeName: this.name(),
564
        }
565
      );
566
    }
567

568
    return options;
22✔
569
  }
570

571
  /**
572
   * Create a Timer.
573
   * @param {bigint} period - The number representing period in nanoseconds.
574
   * @param {function} callback - The callback to be called when timeout.
575
   * @param {Clock} [clock] - The clock which the timer gets time from.
576
   * @return {Timer} - An instance of Timer.
577
   */
578
  createTimer(period, callback, clock = null) {
58✔
579
    if (arguments.length === 3 && !(arguments[2] instanceof Clock)) {
58!
580
      clock = null;
×
581
    } else if (arguments.length === 4) {
58!
582
      clock = arguments[3];
×
583
    }
584

585
    if (typeof period !== 'bigint') {
58✔
586
      throw new TypeValidationError('period', period, 'bigint', {
1✔
587
        nodeName: this.name(),
588
      });
589
    }
590
    if (typeof callback !== 'function') {
57✔
591
      throw new TypeValidationError('callback', callback, 'function', {
1✔
592
        nodeName: this.name(),
593
      });
594
    }
595

596
    const timerClock = clock || this._clock;
56✔
597
    let timerHandle = rclnodejs.createTimer(
56✔
598
      timerClock.handle,
599
      this.context.handle,
600
      period
601
    );
602
    let timer = new Timer(timerHandle, period, callback);
56✔
603
    debug('Finish creating timer, period = %d.', period);
56✔
604
    this._timers.push(timer);
56✔
605
    this.syncHandles();
56✔
606

607
    return timer;
56✔
608
  }
609

610
  /**
611
   * Create a Rate.
612
   *
613
   * @param {number} hz - The frequency of the rate timer; default is 1 hz.
614
   * @returns {Promise<Rate>} - Promise resolving to new instance of Rate.
615
   */
616
  async createRate(hz = 1) {
4✔
617
    if (typeof hz !== 'number') {
9!
NEW
618
      throw new TypeValidationError('hz', hz, 'number', {
×
619
        nodeName: this.name(),
620
      });
621
    }
622

623
    const MAX_RATE_HZ_IN_MILLISECOND = 1000.0;
9✔
624
    if (hz <= 0.0 || hz > MAX_RATE_HZ_IN_MILLISECOND) {
9✔
625
      throw new RangeValidationError(
2✔
626
        'hz',
627
        hz,
628
        `0.0 < hz <= ${MAX_RATE_HZ_IN_MILLISECOND}`,
629
        {
630
          nodeName: this.name(),
631
        }
632
      );
633
    }
634

635
    // lazy initialize rateTimerServer
636
    if (!this._rateTimerServer) {
7✔
637
      this._rateTimerServer = new Rates.RateTimerServer(this);
5✔
638
      await this._rateTimerServer.init();
5✔
639
    }
640

641
    const period = Math.round(1000 / hz);
7✔
642
    const timer = this._rateTimerServer.createTimer(BigInt(period) * 1000000n);
7✔
643
    const rate = new Rates.Rate(hz, timer);
7✔
644

645
    return rate;
7✔
646
  }
647

648
  /**
649
   * Create a Publisher.
650
   * @param {function|string|object} typeClass - The ROS message class,
651
        OR a string representing the message class, e.g. 'std_msgs/msg/String',
652
        OR an object representing the message class, e.g. {package: 'std_msgs', type: 'msg', name: 'String'}
653
   * @param {string} topic - The name of the topic.
654
   * @param {object} options - The options argument used to parameterize the publisher.
655
   * @param {boolean} options.enableTypedArray - The topic will use TypedArray if necessary, default: true.
656
   * @param {QoS} options.qos - ROS Middleware "quality of service" settings for the publisher, default: QoS.profileDefault.
657
   * @param {PublisherEventCallbacks} eventCallbacks - The event callbacks for the publisher.
658
   * @return {Publisher} - An instance of Publisher.
659
   */
660
  createPublisher(typeClass, topic, options, eventCallbacks) {
661
    return this._createPublisher(
1,051✔
662
      typeClass,
663
      topic,
664
      options,
665
      Publisher,
666
      eventCallbacks
667
    );
668
  }
669

670
  _createPublisher(typeClass, topic, options, publisherClass, eventCallbacks) {
671
    if (typeof typeClass === 'string' || typeof typeClass === 'object') {
1,054✔
672
      typeClass = loader.loadInterface(typeClass);
1,030✔
673
    }
674
    options = this._validateOptions(options);
1,047✔
675

676
    if (typeof typeClass !== 'function') {
1,047✔
677
      throw new TypeValidationError('typeClass', typeClass, 'function', {
8✔
678
        nodeName: this.name(),
679
        entityType: 'publisher',
680
      });
681
    }
682
    if (typeof topic !== 'string') {
1,039✔
683
      throw new TypeValidationError('topic', topic, 'string', {
12✔
684
        nodeName: this.name(),
685
        entityType: 'publisher',
686
      });
687
    }
688
    if (
1,027!
689
      eventCallbacks &&
1,029✔
690
      !(eventCallbacks instanceof PublisherEventCallbacks)
691
    ) {
NEW
692
      throw new TypeValidationError(
×
693
        'eventCallbacks',
694
        eventCallbacks,
695
        'PublisherEventCallbacks',
696
        {
697
          nodeName: this.name(),
698
          entityType: 'publisher',
699
          entityName: topic,
700
        }
701
      );
702
    }
703

704
    let publisher = publisherClass.createPublisher(
1,027✔
705
      this,
706
      typeClass,
707
      topic,
708
      options,
709
      eventCallbacks
710
    );
711
    debug('Finish creating publisher, topic = %s.', topic);
1,017✔
712
    this._publishers.push(publisher);
1,017✔
713
    return publisher;
1,017✔
714
  }
715

716
  /**
717
   * This callback is called when a message is published
718
   * @callback SubscriptionCallback
719
   * @param {Object} message - The message published
720
   * @see [Node.createSubscription]{@link Node#createSubscription}
721
   * @see [Node.createPublisher]{@link Node#createPublisher}
722
   * @see {@link Publisher}
723
   * @see {@link Subscription}
724
   */
725

726
  /**
727
   * Create a Subscription with optional content-filtering.
728
   * @param {function|string|object} typeClass - The ROS message class,
729
        OR a string representing the message class, e.g. 'std_msgs/msg/String',
730
        OR an object representing the message class, e.g. {package: 'std_msgs', type: 'msg', name: 'String'}
731
   * @param {string} topic - The name of the topic.
732
   * @param {object} options - The options argument used to parameterize the subscription.
733
   * @param {boolean} options.enableTypedArray - The topic will use TypedArray if necessary, default: true.
734
   * @param {QoS} options.qos - ROS Middleware "quality of service" settings for the subscription, default: QoS.profileDefault.
735
   * @param {boolean} options.isRaw - The topic is serialized when true, default: false.
736
   * @param {string} [options.serializationMode='default'] - Controls message serialization format:
737
   *  'default': Use native rclnodejs behavior (respects enableTypedArray setting),
738
   *  'plain': Convert TypedArrays to regular arrays,
739
   *  'json': Fully JSON-safe (handles TypedArrays, BigInt, etc.).
740
   * @param {object} [options.contentFilter=undefined] - The content-filter, default: undefined.
741
   *  Confirm that your RMW supports content-filtered topics before use. 
742
   * @param {string} options.contentFilter.expression - Specifies the criteria to select the data samples of
743
   *  interest. It is similar to the WHERE part of an SQL clause.
744
   * @param {string[]} [options.contentFilter.parameters=undefined] - Array of strings that give values to
745
   *  the ‘parameters’ (i.e., "%n" tokens) in the filter_expression. The number of supplied parameters must
746
   *  fit with the requested values in the filter_expression (i.e., the number of %n tokens). default: undefined.
747
   * @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.
748
   * @param {SubscriptionEventCallbacks} eventCallbacks - The event callbacks for the subscription.
749
   * @return {Subscription} - An instance of Subscription.
750
   * @throws {ERROR} - May throw an RMW error if content-filter is malformed. 
751
   * @see {@link SubscriptionCallback}
752
   * @see {@link https://www.omg.org/spec/DDS/1.4/PDF|Content-filter details at DDS 1.4 specification, Annex B}
753
   */
754
  createSubscription(typeClass, topic, options, callback, eventCallbacks) {
755
    if (typeof typeClass === 'string' || typeof typeClass === 'object') {
382✔
756
      typeClass = loader.loadInterface(typeClass);
373✔
757
    }
758

759
    if (typeof options === 'function') {
375✔
760
      callback = options;
335✔
761
      options = undefined;
335✔
762
    }
763
    options = this._validateOptions(options);
375✔
764

765
    if (typeof typeClass !== 'function') {
365✔
766
      throw new TypeValidationError('typeClass', typeClass, 'function', {
4✔
767
        nodeName: this.name(),
768
        entityType: 'subscription',
769
      });
770
    }
771
    if (typeof topic !== 'string') {
361✔
772
      throw new TypeValidationError('topic', topic, 'string', {
6✔
773
        nodeName: this.name(),
774
        entityType: 'subscription',
775
      });
776
    }
777
    if (typeof callback !== 'function') {
355!
NEW
778
      throw new TypeValidationError('callback', callback, 'function', {
×
779
        nodeName: this.name(),
780
        entityType: 'subscription',
781
        entityName: topic,
782
      });
783
    }
784
    if (
355!
785
      eventCallbacks &&
359✔
786
      !(eventCallbacks instanceof SubscriptionEventCallbacks)
787
    ) {
NEW
788
      throw new TypeValidationError(
×
789
        'eventCallbacks',
790
        eventCallbacks,
791
        'SubscriptionEventCallbacks',
792
        {
793
          nodeName: this.name(),
794
          entityType: 'subscription',
795
          entityName: topic,
796
        }
797
      );
798
    }
799

800
    let subscription = Subscription.createSubscription(
355✔
801
      this,
802
      typeClass,
803
      topic,
804
      options,
805
      callback,
806
      eventCallbacks
807
    );
808
    debug('Finish creating subscription, topic = %s.', topic);
344✔
809
    this._subscriptions.push(subscription);
344✔
810
    this.syncHandles();
344✔
811

812
    return subscription;
344✔
813
  }
814

815
  /**
816
   * Create a Client.
817
   * @param {function|string|object} typeClass - The ROS message class,
818
        OR a string representing the message class, e.g. 'std_msgs/msg/String',
819
        OR an object representing the message class, e.g. {package: 'std_msgs', type: 'msg', name: 'String'}
820
   * @param {string} serviceName - The service name to request.
821
   * @param {object} options - The options argument used to parameterize the client.
822
   * @param {boolean} options.enableTypedArray - The response will use TypedArray if necessary, default: true.
823
   * @param {QoS} options.qos - ROS Middleware "quality of service" settings for the client, default: QoS.profileDefault.
824
   * @return {Client} - An instance of Client.
825
   */
826
  createClient(typeClass, serviceName, options) {
827
    if (typeof typeClass === 'string' || typeof typeClass === 'object') {
150✔
828
      typeClass = loader.loadInterface(typeClass);
140✔
829
    }
830
    options = this._validateOptions(options);
143✔
831

832
    if (typeof typeClass !== 'function') {
143✔
833
      throw new TypeValidationError('typeClass', typeClass, 'function', {
8✔
834
        nodeName: this.name(),
835
        entityType: 'client',
836
      });
837
    }
838
    if (typeof serviceName !== 'string') {
135✔
839
      throw new TypeValidationError('serviceName', serviceName, 'string', {
12✔
840
        nodeName: this.name(),
841
        entityType: 'client',
842
      });
843
    }
844

845
    let client = Client.createClient(
123✔
846
      this.handle,
847
      serviceName,
848
      typeClass,
849
      options
850
    );
851
    debug('Finish creating client, service = %s.', serviceName);
113✔
852
    this._clients.push(client);
113✔
853
    this.syncHandles();
113✔
854

855
    return client;
113✔
856
  }
857

858
  /**
859
   * This callback is called when a request is sent to service
860
   * @callback RequestCallback
861
   * @param {Object} request - The request sent to the service
862
   * @param {Response} response - The response to client.
863
        Use [response.send()]{@link Response#send} to send response object to client
864
   * @return {undefined}
865
   * @see [Node.createService]{@link Node#createService}
866
   * @see [Client.sendRequest]{@link Client#sendRequest}
867
   * @see {@link Client}
868
   * @see {@link Service}
869
   * @see {@link Response#send}
870
   */
871

872
  /**
873
   * Create a Service.
874
   * @param {function|string|object} typeClass - The ROS message class,
875
        OR a string representing the message class, e.g. 'std_msgs/msg/String',
876
        OR an object representing the message class, e.g. {package: 'std_msgs', type: 'msg', name: 'String'}
877
   * @param {string} serviceName - The service name to offer.
878
   * @param {object} options - The options argument used to parameterize the service.
879
   * @param {boolean} options.enableTypedArray - The request will use TypedArray if necessary, default: true.
880
   * @param {QoS} options.qos - ROS Middleware "quality of service" settings for the service, default: QoS.profileDefault.
881
   * @param {RequestCallback} callback - The callback to be called when receiving request.
882
   * @return {Service} - An instance of Service.
883
   * @see {@link RequestCallback}
884
   */
885
  createService(typeClass, serviceName, options, callback) {
886
    if (typeof typeClass === 'string' || typeof typeClass === 'object') {
4,176✔
887
      typeClass = loader.loadInterface(typeClass);
4,166✔
888
    }
889

890
    if (typeof options === 'function') {
4,169✔
891
      callback = options;
4,146✔
892
      options = undefined;
4,146✔
893
    }
894
    options = this._validateOptions(options);
4,169✔
895

896
    if (typeof typeClass !== 'function') {
4,159✔
897
      throw new TypeValidationError('typeClass', typeClass, 'function', {
4✔
898
        nodeName: this.name(),
899
        entityType: 'service',
900
      });
901
    }
902
    if (typeof serviceName !== 'string') {
4,155✔
903
      throw new TypeValidationError('serviceName', serviceName, 'string', {
6✔
904
        nodeName: this.name(),
905
        entityType: 'service',
906
      });
907
    }
908
    if (typeof callback !== 'function') {
4,149!
NEW
909
      throw new TypeValidationError('callback', callback, 'function', {
×
910
        nodeName: this.name(),
911
        entityType: 'service',
912
        entityName: serviceName,
913
      });
914
    }
915

916
    let service = Service.createService(
4,149✔
917
      this.handle,
918
      serviceName,
919
      typeClass,
920
      options,
921
      callback
922
    );
923
    debug('Finish creating service, service = %s.', serviceName);
4,139✔
924
    this._services.push(service);
4,139✔
925
    this.syncHandles();
4,139✔
926

927
    return service;
4,139✔
928
  }
929

930
  /**
931
   * Create a ParameterClient for accessing parameters on a remote node.
932
   * @param {string} remoteNodeName - The name of the remote node whose parameters to access.
933
   * @param {object} [options] - Options for parameter client.
934
   * @param {number} [options.timeout=5000] - Default timeout in milliseconds for service calls.
935
   * @return {ParameterClient} - An instance of ParameterClient.
936
   */
937
  createParameterClient(remoteNodeName, options = {}) {
50✔
938
    if (typeof remoteNodeName !== 'string' || remoteNodeName.trim() === '') {
50!
939
      throw new TypeError('Remote node name must be a non-empty string');
×
940
    }
941

942
    const parameterClient = new ParameterClient(this, remoteNodeName, options);
50✔
943
    debug(
50✔
944
      'Finish creating parameter client for remote node = %s.',
945
      remoteNodeName
946
    );
947
    this._parameterClients.push(parameterClient);
50✔
948

949
    return parameterClient;
50✔
950
  }
951

952
  /**
953
   * Create a guard condition.
954
   * @param {Function} callback - The callback to be called when the guard condition is triggered.
955
   * @return {GuardCondition} - An instance of GuardCondition.
956
   */
957
  createGuardCondition(callback) {
958
    if (typeof callback !== 'function') {
3!
NEW
959
      throw new TypeValidationError('callback', callback, 'function', {
×
960
        nodeName: this.name(),
961
        entityType: 'guard_condition',
962
      });
963
    }
964

965
    let guard = GuardCondition.createGuardCondition(callback, this.context);
3✔
966
    debug('Finish creating guard condition');
3✔
967
    this._guards.push(guard);
3✔
968
    this.syncHandles();
3✔
969

970
    return guard;
3✔
971
  }
972

973
  /**
974
   * Destroy all resource allocated by this node, including
975
   * <code>Timer</code>s/<code>Publisher</code>s/<code>Subscription</code>s
976
   * /<code>Client</code>s/<code>Service</code>s
977
   * @return {undefined}
978
   */
979
  destroy() {
980
    if (this.spinning) {
712✔
981
      this.stop();
526✔
982
    }
983

984
    // Action servers/clients require manual destruction due to circular reference with goal handles.
985
    this._actionClients.forEach((actionClient) => actionClient.destroy());
712✔
986
    this._actionServers.forEach((actionServer) => actionServer.destroy());
712✔
987

988
    this._parameterClients.forEach((paramClient) => paramClient.destroy());
712✔
989

990
    this.context.onNodeDestroyed(this);
712✔
991

992
    this.handle.release();
712✔
993
    this._clock = null;
712✔
994
    this._timers = [];
712✔
995
    this._publishers = [];
712✔
996
    this._subscriptions = [];
712✔
997
    this._clients = [];
712✔
998
    this._services = [];
712✔
999
    this._guards = [];
712✔
1000
    this._actionClients = [];
712✔
1001
    this._actionServers = [];
712✔
1002
    this._parameterClients = [];
712✔
1003

1004
    if (this._rateTimerServer) {
712✔
1005
      this._rateTimerServer.shutdown();
5✔
1006
      this._rateTimerServer = null;
5✔
1007
    }
1008
  }
1009

1010
  /**
1011
   * Destroy a Publisher.
1012
   * @param {Publisher} publisher - The Publisher to be destroyed.
1013
   * @return {undefined}
1014
   */
1015
  destroyPublisher(publisher) {
1016
    if (!(publisher instanceof Publisher)) {
9✔
1017
      throw new TypeValidationError(
2✔
1018
        'publisher',
1019
        publisher,
1020
        'Publisher instance',
1021
        {
1022
          nodeName: this.name(),
1023
        }
1024
      );
1025
    }
1026
    if (publisher.events) {
7!
1027
      publisher.events.forEach((event) => {
×
1028
        this._destroyEntity(event, this._events);
×
1029
      });
1030
      publisher.events = [];
×
1031
    }
1032
    this._destroyEntity(publisher, this._publishers, false);
7✔
1033
  }
1034

1035
  /**
1036
   * Destroy a Subscription.
1037
   * @param {Subscription} subscription - The Subscription to be destroyed.
1038
   * @return {undefined}
1039
   */
1040
  destroySubscription(subscription) {
1041
    if (!(subscription instanceof Subscription)) {
37✔
1042
      throw new TypeValidationError(
2✔
1043
        'subscription',
1044
        subscription,
1045
        'Subscription instance',
1046
        {
1047
          nodeName: this.name(),
1048
        }
1049
      );
1050
    }
1051
    if (subscription.events) {
35✔
1052
      subscription.events.forEach((event) => {
1✔
1053
        this._destroyEntity(event, this._events);
1✔
1054
      });
1055
      subscription.events = [];
1✔
1056
    }
1057

1058
    this._destroyEntity(subscription, this._subscriptions);
35✔
1059
  }
1060

1061
  /**
1062
   * Destroy a Client.
1063
   * @param {Client} client - The Client to be destroyed.
1064
   * @return {undefined}
1065
   */
1066
  destroyClient(client) {
1067
    if (!(client instanceof Client)) {
72✔
1068
      throw new TypeValidationError('client', client, 'Client instance', {
2✔
1069
        nodeName: this.name(),
1070
      });
1071
    }
1072
    this._destroyEntity(client, this._clients);
70✔
1073
  }
1074

1075
  /**
1076
   * Destroy a Service.
1077
   * @param {Service} service - The Service to be destroyed.
1078
   * @return {undefined}
1079
   */
1080
  destroyService(service) {
1081
    if (!(service instanceof Service)) {
8✔
1082
      throw new TypeValidationError('service', service, 'Service instance', {
2✔
1083
        nodeName: this.name(),
1084
      });
1085
    }
1086
    this._destroyEntity(service, this._services);
6✔
1087
  }
1088

1089
  /**
1090
   * Destroy a ParameterClient.
1091
   * @param {ParameterClient} parameterClient - The ParameterClient to be destroyed.
1092
   * @return {undefined}
1093
   */
1094
  destroyParameterClient(parameterClient) {
1095
    if (!(parameterClient instanceof ParameterClient)) {
1!
1096
      throw new TypeError('Invalid argument');
×
1097
    }
1098
    this._removeEntityFromArray(parameterClient, this._parameterClients);
1✔
1099
    parameterClient.destroy();
1✔
1100
  }
1101

1102
  /**
1103
   * Destroy a Timer.
1104
   * @param {Timer} timer - The Timer to be destroyed.
1105
   * @return {undefined}
1106
   */
1107
  destroyTimer(timer) {
1108
    if (!(timer instanceof Timer)) {
8✔
1109
      throw new TypeValidationError('timer', timer, 'Timer instance', {
2✔
1110
        nodeName: this.name(),
1111
      });
1112
    }
1113
    this._destroyEntity(timer, this._timers);
6✔
1114
  }
1115

1116
  /**
1117
   * Destroy a guard condition.
1118
   * @param {GuardCondition} guard - The guard condition to be destroyed.
1119
   * @return {undefined}
1120
   */
1121
  destroyGuardCondition(guard) {
1122
    if (!(guard instanceof GuardCondition)) {
3!
NEW
1123
      throw new TypeValidationError('guard', guard, 'GuardCondition instance', {
×
1124
        nodeName: this.name(),
1125
      });
1126
    }
1127
    this._destroyEntity(guard, this._guards);
3✔
1128
  }
1129

1130
  /**
1131
   * Get the name of the node.
1132
   * @return {string}
1133
   */
1134
  name() {
1135
    return rclnodejs.getNodeName(this.handle);
4,047✔
1136
  }
1137

1138
  /**
1139
   * Get the namespace of the node.
1140
   * @return {string}
1141
   */
1142
  namespace() {
1143
    return rclnodejs.getNamespace(this.handle);
3,749✔
1144
  }
1145

1146
  /**
1147
   * Get the context in which this node was created.
1148
   * @return {Context}
1149
   */
1150
  get context() {
1151
    return this._context;
5,687✔
1152
  }
1153

1154
  /**
1155
   * Get the nodes logger.
1156
   * @returns {Logger} - The logger for the node.
1157
   */
1158
  getLogger() {
1159
    return this._logger;
141✔
1160
  }
1161

1162
  /**
1163
   * Get the clock used by the node.
1164
   * @returns {Clock} - The nodes clock.
1165
   */
1166
  getClock() {
1167
    return this._clock;
86✔
1168
  }
1169

1170
  /**
1171
   * Get the current time using the node's clock.
1172
   * @returns {Timer} - The current time.
1173
   */
1174
  now() {
1175
    return this.getClock().now();
2✔
1176
  }
1177

1178
  /**
1179
   * Get the list of published topics discovered by the provided node for the remote node name.
1180
   * @param {string} nodeName - The name of the node.
1181
   * @param {string} namespace - The name of the namespace.
1182
   * @param {boolean} noDemangle - If true topic names and types returned will not be demangled, default: false.
1183
   * @return {Array<{name: string, types: Array<string>}>} - An array of the names and types.
1184
   */
1185
  getPublisherNamesAndTypesByNode(nodeName, namespace, noDemangle = false) {
×
1186
    return rclnodejs.getPublisherNamesAndTypesByNode(
×
1187
      this.handle,
1188
      nodeName,
1189
      namespace,
1190
      noDemangle
1191
    );
1192
  }
1193

1194
  /**
1195
   * Get the list of published topics discovered by the provided node for the remote node name.
1196
   * @param {string} nodeName - The name of the node.
1197
   * @param {string} namespace - The name of the namespace.
1198
   * @param {boolean} noDemangle - If true topic names and types returned will not be demangled, default: false.
1199
   * @return {Array<{name: string, types: Array<string>}>} - An array of the names and types.
1200
   */
1201
  getSubscriptionNamesAndTypesByNode(nodeName, namespace, noDemangle = false) {
×
1202
    return rclnodejs.getSubscriptionNamesAndTypesByNode(
×
1203
      this.handle,
1204
      nodeName,
1205
      namespace,
1206
      noDemangle
1207
    );
1208
  }
1209

1210
  /**
1211
   * Get service names and types for which a remote node has servers.
1212
   * @param {string} nodeName - The name of the node.
1213
   * @param {string} namespace - The name of the namespace.
1214
   * @return {Array<{name: string, types: Array<string>}>} - An array of the names and types.
1215
   */
1216
  getServiceNamesAndTypesByNode(nodeName, namespace) {
1217
    return rclnodejs.getServiceNamesAndTypesByNode(
×
1218
      this.handle,
1219
      nodeName,
1220
      namespace
1221
    );
1222
  }
1223

1224
  /**
1225
   * Get service names and types for which a remote node has clients.
1226
   * @param {string} nodeName - The name of the node.
1227
   * @param {string} namespace - The name of the namespace.
1228
   * @return {Array<{name: string, types: Array<string>}>} - An array of the names and types.
1229
   */
1230
  getClientNamesAndTypesByNode(nodeName, namespace) {
1231
    return rclnodejs.getClientNamesAndTypesByNode(
×
1232
      this.handle,
1233
      nodeName,
1234
      namespace
1235
    );
1236
  }
1237

1238
  /**
1239
   * Get the list of topics discovered by the provided node.
1240
   * @param {boolean} noDemangle - If true topic names and types returned will not be demangled, default: false.
1241
   * @return {Array<{name: string, types: Array<string>}>} - An array of the names and types.
1242
   */
1243
  getTopicNamesAndTypes(noDemangle = false) {
×
1244
    return rclnodejs.getTopicNamesAndTypes(this.handle, noDemangle);
×
1245
  }
1246

1247
  /**
1248
   * Get the list of services discovered by the provided node.
1249
   * @return {Array<{name: string, types: Array<string>}>} - An array of the names and types.
1250
   */
1251
  getServiceNamesAndTypes() {
1252
    return rclnodejs.getServiceNamesAndTypes(this.handle);
2✔
1253
  }
1254

1255
  /**
1256
   * Return a list of publishers on a given topic.
1257
   *
1258
   * The returned parameter is a list of TopicEndpointInfo objects, where each will contain
1259
   * the node name, node namespace, topic type, topic endpoint's GID, and its QoS profile.
1260
   *
1261
   * When the `no_mangle` parameter is `true`, the provided `topic` should be a valid
1262
   * topic name for the middleware (useful when combining ROS with native middleware (e.g. DDS)
1263
   * apps).  When the `no_mangle` parameter is `false`, the provided `topic` should
1264
   * follow ROS topic name conventions.
1265
   *
1266
   * `topic` may be a relative, private, or fully qualified topic name.
1267
   *  A relative or private topic will be expanded using this node's namespace and name.
1268
   *  The queried `topic` is not remapped.
1269
   *
1270
   * @param {string} topic - The topic on which to find the publishers.
1271
   * @param {boolean} [noDemangle=false] - If `true`, `topic` needs to be a valid middleware topic
1272
   *                               name, otherwise it should be a valid ROS topic name. Defaults to `false`.
1273
   * @returns {Array} - list of publishers
1274
   */
1275
  getPublishersInfoByTopic(topic, noDemangle = false) {
×
1276
    return rclnodejs.getPublishersInfoByTopic(
3✔
1277
      this.handle,
1278
      this._getValidatedTopic(topic, noDemangle),
1279
      noDemangle
1280
    );
1281
  }
1282

1283
  /**
1284
   * Return a list of subscriptions on a given topic.
1285
   *
1286
   * The returned parameter is a list of TopicEndpointInfo objects, where each will contain
1287
   * the node name, node namespace, topic type, topic endpoint's GID, and its QoS profile.
1288
   *
1289
   * When the `no_mangle` parameter is `true`, the provided `topic` should be a valid
1290
   * topic name for the middleware (useful when combining ROS with native middleware (e.g. DDS)
1291
   * apps).  When the `no_mangle` parameter is `false`, the provided `topic` should
1292
   * follow ROS topic name conventions.
1293
   *
1294
   * `topic` may be a relative, private, or fully qualified topic name.
1295
   *  A relative or private topic will be expanded using this node's namespace and name.
1296
   *  The queried `topic` is not remapped.
1297
   *
1298
   * @param {string} topic - The topic on which to find the subscriptions.
1299
   * @param {boolean} [noDemangle=false] -  If `true`, `topic` needs to be a valid middleware topic
1300
                                    name, otherwise it should be a valid ROS topic name. Defaults to `false`.
1301
   * @returns {Array} - list of subscriptions
1302
   */
1303
  getSubscriptionsInfoByTopic(topic, noDemangle = false) {
×
1304
    return rclnodejs.getSubscriptionsInfoByTopic(
2✔
1305
      this.handle,
1306
      this._getValidatedTopic(topic, noDemangle),
1307
      noDemangle
1308
    );
1309
  }
1310

1311
  /**
1312
   * Get the list of nodes discovered by the provided node.
1313
   * @return {Array<string>} - An array of the names.
1314
   */
1315
  getNodeNames() {
1316
    return this.getNodeNamesAndNamespaces().map((item) => item.name);
41✔
1317
  }
1318

1319
  /**
1320
   * Get the list of nodes and their namespaces discovered by the provided node.
1321
   * @return {Array<{name: string, namespace: string}>} An array of the names and namespaces.
1322
   */
1323
  getNodeNamesAndNamespaces() {
1324
    return rclnodejs.getNodeNames(this.handle, /*getEnclaves=*/ false);
17✔
1325
  }
1326

1327
  /**
1328
   * Get the list of nodes and their namespaces with enclaves discovered by the provided node.
1329
   * @return {Array<{name: string, namespace: string, enclave: string}>} An array of the names, namespaces and enclaves.
1330
   */
1331
  getNodeNamesAndNamespacesWithEnclaves() {
1332
    return rclnodejs.getNodeNames(this.handle, /*getEnclaves=*/ true);
1✔
1333
  }
1334

1335
  /**
1336
   * Return the number of publishers on a given topic.
1337
   * @param {string} topic - The name of the topic.
1338
   * @returns {number} - Number of publishers on the given topic.
1339
   */
1340
  countPublishers(topic) {
1341
    let expandedTopic = rclnodejs.expandTopicName(
6✔
1342
      topic,
1343
      this.name(),
1344
      this.namespace()
1345
    );
1346
    rclnodejs.validateTopicName(expandedTopic);
6✔
1347

1348
    return rclnodejs.countPublishers(this.handle, expandedTopic);
6✔
1349
  }
1350

1351
  /**
1352
   * Return the number of subscribers on a given topic.
1353
   * @param {string} topic - The name of the topic.
1354
   * @returns {number} - Number of subscribers on the given topic.
1355
   */
1356
  countSubscribers(topic) {
1357
    let expandedTopic = rclnodejs.expandTopicName(
6✔
1358
      topic,
1359
      this.name(),
1360
      this.namespace()
1361
    );
1362
    rclnodejs.validateTopicName(expandedTopic);
6✔
1363

1364
    return rclnodejs.countSubscribers(this.handle, expandedTopic);
6✔
1365
  }
1366

1367
  /**
1368
   * Get the number of clients on a given service name.
1369
   * @param {string} serviceName - the service name
1370
   * @returns {Number}
1371
   */
1372
  countClients(serviceName) {
1373
    if (DistroUtils.getDistroId() <= DistroUtils.getDistroId('humble')) {
2!
1374
      console.warn('countClients is not supported by this version of ROS 2');
×
1375
      return null;
×
1376
    }
1377
    return rclnodejs.countClients(this.handle, serviceName);
2✔
1378
  }
1379

1380
  /**
1381
   * Get the number of services on a given service name.
1382
   * @param {string} serviceName - the service name
1383
   * @returns {Number}
1384
   */
1385
  countServices(serviceName) {
1386
    if (DistroUtils.getDistroId() <= DistroUtils.getDistroId('humble')) {
1!
1387
      console.warn('countServices is not supported by this version of ROS 2');
×
1388
      return null;
×
1389
    }
1390
    return rclnodejs.countServices(this.handle, serviceName);
1✔
1391
  }
1392

1393
  /**
1394
   * Get the list of parameter-overrides found on the commandline and
1395
   * in the NodeOptions.parameter_overrides property.
1396
   *
1397
   * @return {Array<Parameter>} - An array of Parameters.
1398
   */
1399
  getParameterOverrides() {
1400
    return Array.from(this._parameterOverrides.values());
8✔
1401
  }
1402

1403
  /**
1404
   * Declare a parameter.
1405
   *
1406
   * Internally, register a parameter and it's descriptor.
1407
   * If a parameter-override exists, it's value will replace that of the parameter
1408
   * unless ignoreOverride is true.
1409
   * If the descriptor is undefined, then a ParameterDescriptor will be inferred
1410
   * from the parameter's state.
1411
   *
1412
   * If a parameter by the same name has already been declared then an Error is thrown.
1413
   * A parameter must be undeclared before attempting to redeclare it.
1414
   *
1415
   * @param {Parameter} parameter - Parameter to declare.
1416
   * @param {ParameterDescriptor} [descriptor] - Optional descriptor for parameter.
1417
   * @param {boolean} [ignoreOverride] - When true disregard any parameter-override that may be present.
1418
   * @return {Parameter} - The newly declared parameter.
1419
   */
1420
  declareParameter(parameter, descriptor, ignoreOverride = false) {
1,841✔
1421
    const parameters = this.declareParameters(
1,842✔
1422
      [parameter],
1423
      descriptor ? [descriptor] : [],
1,842✔
1424
      ignoreOverride
1425
    );
1426
    return parameters.length == 1 ? parameters[0] : null;
1,842!
1427
  }
1428

1429
  /**
1430
   * Declare a list of parameters.
1431
   *
1432
   * Internally register parameters with their corresponding descriptor one by one
1433
   * in the order they are provided. This is an atomic operation. If an error
1434
   * occurs the process halts and no further parameters are declared.
1435
   * Parameters that have already been processed are undeclared.
1436
   *
1437
   * While descriptors is an optional parameter, when provided there must be
1438
   * a descriptor for each parameter; otherwise an Error is thrown.
1439
   * If descriptors is not provided then a descriptor will be inferred
1440
   * from each parameter's state.
1441
   *
1442
   * When a parameter-override is available, the parameter's value
1443
   * will be replaced with that of the parameter-override unless ignoreOverrides
1444
   * is true.
1445
   *
1446
   * If a parameter by the same name has already been declared then an Error is thrown.
1447
   * A parameter must be undeclared before attempting to redeclare it.
1448
   *
1449
   * Prior to declaring the parameters each SetParameterEventCallback registered
1450
   * using setOnParameterEventCallback() is called in succession with the parameters
1451
   * list. Any SetParameterEventCallback that retuns does not return a successful
1452
   * result will cause the entire operation to terminate with no changes to the
1453
   * parameters. When all SetParameterEventCallbacks return successful then the
1454
   * list of parameters is updated.
1455
   *
1456
   * @param {Parameter[]} parameters - The parameters to declare.
1457
   * @param {ParameterDescriptor[]} [descriptors] - Optional descriptors,
1458
   *    a 1-1 correspondence with parameters.
1459
   * @param {boolean} ignoreOverrides - When true, parameter-overrides are
1460
   *    not considered, i.e.,ignored.
1461
   * @return {Parameter[]} - The declared parameters.
1462
   */
1463
  declareParameters(parameters, descriptors = [], ignoreOverrides = false) {
×
1464
    if (!Array.isArray(parameters)) {
1,842!
NEW
1465
      throw new TypeValidationError('parameters', parameters, 'Array', {
×
1466
        nodeName: this.name(),
1467
      });
1468
    }
1469
    if (!Array.isArray(descriptors)) {
1,842!
NEW
1470
      throw new TypeValidationError('descriptors', descriptors, 'Array', {
×
1471
        nodeName: this.name(),
1472
      });
1473
    }
1474
    if (descriptors.length > 0 && parameters.length !== descriptors.length) {
1,842!
NEW
1475
      throw new ValidationError(
×
1476
        'Each parameter must have a corresponding ParameterDescriptor',
1477
        {
1478
          code: 'PARAMETER_DESCRIPTOR_MISMATCH',
1479
          argumentName: 'descriptors',
1480
          providedValue: descriptors.length,
1481
          expectedType: `Array with length ${parameters.length}`,
1482
          nodeName: this.name(),
1483
        }
1484
      );
1485
    }
1486

1487
    const declaredDescriptors = [];
1,842✔
1488
    const declaredParameters = [];
1,842✔
1489
    const declaredParameterCollisions = [];
1,842✔
1490
    for (let i = 0; i < parameters.length; i++) {
1,842✔
1491
      let parameter =
1492
        !ignoreOverrides && this._parameterOverrides.has(parameters[i].name)
1,842✔
1493
          ? this._parameterOverrides.get(parameters[i].name)
1494
          : parameters[i];
1495

1496
      // stop processing parameters that have already been declared
1497
      if (this._parameters.has(parameter.name)) {
1,842!
1498
        declaredParameterCollisions.push(parameter);
×
1499
        continue;
×
1500
      }
1501

1502
      // create descriptor for parameter if not provided
1503
      let descriptor =
1504
        descriptors.length > 0
1,842✔
1505
          ? descriptors[i]
1506
          : ParameterDescriptor.fromParameter(parameter);
1507

1508
      descriptor.validate();
1,842✔
1509

1510
      declaredDescriptors.push(descriptor);
1,842✔
1511
      declaredParameters.push(parameter);
1,842✔
1512
    }
1513

1514
    if (declaredParameterCollisions.length > 0) {
1,842!
1515
      const errorMsg =
1516
        declaredParameterCollisions.length == 1
×
1517
          ? `Parameter(${declaredParameterCollisions[0]}) already declared.`
1518
          : `Multiple parameters already declared, e.g., Parameter(${declaredParameterCollisions[0]}).`;
1519
      throw new Error(errorMsg);
×
1520
    }
1521

1522
    // register descriptor
1523
    for (const descriptor of declaredDescriptors) {
1,842✔
1524
      this._parameterDescriptors.set(descriptor.name, descriptor);
1,842✔
1525
    }
1526

1527
    const result = this._setParametersAtomically(declaredParameters, true);
1,842✔
1528
    if (!result.successful) {
1,842!
1529
      // unregister descriptors
1530
      for (const descriptor of declaredDescriptors) {
×
1531
        this._parameterDescriptors.delete(descriptor.name);
×
1532
      }
1533

1534
      throw new Error(result.reason);
×
1535
    }
1536

1537
    return this.getParameters(declaredParameters.map((param) => param.name));
1,842✔
1538
  }
1539

1540
  /**
1541
   * Undeclare a parameter.
1542
   *
1543
   * Readonly parameters can not be undeclared or updated.
1544
   * @param {string} name - Name of parameter to undeclare.
1545
   * @return {undefined} -
1546
   */
1547
  undeclareParameter(name) {
1548
    if (!this.hasParameter(name)) return;
1!
1549

1550
    const descriptor = this.getParameterDescriptor(name);
1✔
1551
    if (descriptor.readOnly) {
1!
1552
      throw new Error(
×
1553
        `${name} parameter is read-only and can not be undeclared`
1554
      );
1555
    }
1556

1557
    this._parameters.delete(name);
1✔
1558
    this._parameterDescriptors.delete(name);
1✔
1559
  }
1560

1561
  /**
1562
   * Determine if a parameter has been declared.
1563
   * @param {string} name - name of parameter
1564
   * @returns {boolean} - Return true if parameter is declared; false otherwise.
1565
   */
1566
  hasParameter(name) {
1567
    return this._parameters.has(name);
4,652✔
1568
  }
1569

1570
  /**
1571
   * Get a declared parameter by name.
1572
   *
1573
   * If unable to locate a declared parameter then a
1574
   * parameter with type == PARAMETER_NOT_SET is returned.
1575
   *
1576
   * @param {string} name - The name of the parameter.
1577
   * @return {Parameter} - The parameter.
1578
   */
1579
  getParameter(name) {
1580
    return this.getParameters([name])[0];
1,392✔
1581
  }
1582

1583
  /**
1584
   * Get a list of parameters.
1585
   *
1586
   * Find and return the declared parameters.
1587
   * If no names are provided return all declared parameters.
1588
   *
1589
   * If unable to locate a declared parameter then a
1590
   * parameter with type == PARAMETER_NOT_SET is returned in
1591
   * it's place.
1592
   *
1593
   * @param {string[]} [names] - The names of the declared parameters
1594
   *    to find or null indicating to return all declared parameters.
1595
   * @return {Parameter[]} - The parameters.
1596
   */
1597
  getParameters(names = []) {
12✔
1598
    let params = [];
3,270✔
1599

1600
    if (names.length == 0) {
3,270✔
1601
      // get all parameters
1602
      params = [...this._parameters.values()];
12✔
1603
      return params;
12✔
1604
    }
1605

1606
    for (const name of names) {
3,258✔
1607
      const param = this.hasParameter(name)
3,263✔
1608
        ? this._parameters.get(name)
1609
        : new Parameter(name, ParameterType.PARAMETER_NOT_SET);
1610

1611
      params.push(param);
3,263✔
1612
    }
1613

1614
    return params;
3,258✔
1615
  }
1616

1617
  /**
1618
   * Get the types of given parameters.
1619
   *
1620
   * Return the types of given parameters.
1621
   *
1622
   * @param {string[]} [names] - The names of the declared parameters.
1623
   * @return {Uint8Array} - The types.
1624
   */
1625
  getParameterTypes(names = []) {
×
1626
    let types = [];
3✔
1627

1628
    for (const name of names) {
3✔
1629
      const descriptor = this._parameterDescriptors.get(name);
7✔
1630
      if (descriptor) {
7!
1631
        types.push(descriptor.type);
7✔
1632
      }
1633
    }
1634
    return types;
3✔
1635
  }
1636

1637
  /**
1638
   * Get the names of all declared parameters.
1639
   *
1640
   * @return {Array<string>} - The declared parameter names or empty array if
1641
   *    no parameters have been declared.
1642
   */
1643
  getParameterNames() {
1644
    return this.getParameters().map((param) => param.name);
53✔
1645
  }
1646

1647
  /**
1648
   * Determine if a parameter descriptor exists.
1649
   *
1650
   * @param {string} name - The name of a descriptor to for.
1651
   * @return {boolean} - true if a descriptor has been declared; otherwise false.
1652
   */
1653
  hasParameterDescriptor(name) {
1654
    return !!this.getParameterDescriptor(name);
1,859✔
1655
  }
1656

1657
  /**
1658
   * Get a declared parameter descriptor by name.
1659
   *
1660
   * If unable to locate a declared parameter descriptor then a
1661
   * descriptor with type == PARAMETER_NOT_SET is returned.
1662
   *
1663
   * @param {string} name - The name of the parameter descriptor to find.
1664
   * @return {ParameterDescriptor} - The parameter descriptor.
1665
   */
1666
  getParameterDescriptor(name) {
1667
    return this.getParameterDescriptors([name])[0];
3,719✔
1668
  }
1669

1670
  /**
1671
   * Find a list of declared ParameterDescriptors.
1672
   *
1673
   * If no names are provided return all declared descriptors.
1674
   *
1675
   * If unable to locate a declared descriptor then a
1676
   * descriptor with type == PARAMETER_NOT_SET is returned in
1677
   * it's place.
1678
   *
1679
   * @param {string[]} [names] - The names of the declared parameter
1680
   *    descriptors to find or null indicating to return all declared descriptors.
1681
   * @return {ParameterDescriptor[]} - The parameter descriptors.
1682
   */
1683
  getParameterDescriptors(names = []) {
×
1684
    let descriptors = [];
3,722✔
1685

1686
    if (names.length == 0) {
3,722!
1687
      // get all parameters
1688
      descriptors = [...this._parameterDescriptors.values()];
×
1689
      return descriptors;
×
1690
    }
1691

1692
    for (const name of names) {
3,722✔
1693
      let descriptor = this._parameterDescriptors.get(name);
3,724✔
1694
      if (!descriptor) {
3,724!
1695
        descriptor = new ParameterDescriptor(
×
1696
          name,
1697
          ParameterType.PARAMETER_NOT_SET
1698
        );
1699
      }
1700
      descriptors.push(descriptor);
3,724✔
1701
    }
1702

1703
    return descriptors;
3,722✔
1704
  }
1705

1706
  /**
1707
   * Replace a declared parameter.
1708
   *
1709
   * The parameter being replaced must be a declared parameter who's descriptor
1710
   * is not readOnly; otherwise an Error is thrown.
1711
   *
1712
   * @param {Parameter} parameter - The new parameter.
1713
   * @return {rclnodejs.rcl_interfaces.msg.SetParameterResult} - The result of the operation.
1714
   */
1715
  setParameter(parameter) {
1716
    const results = this.setParameters([parameter]);
4✔
1717
    return results[0];
4✔
1718
  }
1719

1720
  /**
1721
   * Replace a list of declared parameters.
1722
   *
1723
   * Declared parameters are replaced in the order they are provided and
1724
   * a ParameterEvent is published for each individual parameter change.
1725
   *
1726
   * Prior to setting the parameters each SetParameterEventCallback registered
1727
   * using setOnParameterEventCallback() is called in succession with the parameters
1728
   * list. Any SetParameterEventCallback that retuns does not return a successful
1729
   * result will cause the entire operation to terminate with no changes to the
1730
   * parameters. When all SetParameterEventCallbacks return successful then the
1731
   * list of parameters is updated.
1732
   *
1733
   * If an error occurs, the process is stopped and returned. Parameters
1734
   * set before an error remain unchanged.
1735
   *
1736
   * @param {Parameter[]} parameters - The parameters to set.
1737
   * @return {rclnodejs.rcl_interfaces.msg.SetParameterResult[]} - A list of SetParameterResult, one for each parameter that was set.
1738
   */
1739
  setParameters(parameters = []) {
×
1740
    return parameters.map((parameter) =>
14✔
1741
      this.setParametersAtomically([parameter])
16✔
1742
    );
1743
  }
1744

1745
  /**
1746
   * Repalce a list of declared parameters atomically.
1747
   *
1748
   * Declared parameters are replaced in the order they are provided.
1749
   * A single ParameterEvent is published collectively for all changed
1750
   * parameters.
1751
   *
1752
   * Prior to setting the parameters each SetParameterEventCallback registered
1753
   * using setOnParameterEventCallback() is called in succession with the parameters
1754
   * list. Any SetParameterEventCallback that retuns does not return a successful
1755
   * result will cause the entire operation to terminate with no changes to the
1756
   * parameters. When all SetParameterEventCallbacks return successful then the
1757
   * list of parameters is updated.d
1758
   *
1759
   * If an error occurs, the process stops immediately. All parameters updated to
1760
   * the point of the error are reverted to their previous state.
1761
   *
1762
   * @param {Parameter[]} parameters - The parameters to set.
1763
   * @return {rclnodejs.rcl_interfaces.msg.SetParameterResult} - describes the result of setting 1 or more parameters.
1764
   */
1765
  setParametersAtomically(parameters = []) {
×
1766
    return this._setParametersAtomically(parameters);
17✔
1767
  }
1768

1769
  /**
1770
   * Internal method for updating parameters atomically.
1771
   *
1772
   * Prior to setting the parameters each SetParameterEventCallback registered
1773
   * using setOnParameterEventCallback() is called in succession with the parameters
1774
   * list. Any SetParameterEventCallback that retuns does not return a successful
1775
   * result will cause the entire operation to terminate with no changes to the
1776
   * parameters. When all SetParameterEventCallbacks return successful then the
1777
   * list of parameters is updated.
1778
   *
1779
   * @param {Paramerter[]} parameters - The parameters to update.
1780
   * @param {boolean} declareParameterMode - When true parameters are being declared;
1781
   *    otherwise they are being changed.
1782
   * @return {SetParameterResult} - A single collective result.
1783
   */
1784
  _setParametersAtomically(parameters = [], declareParameterMode = false) {
17!
1785
    let result = this._validateParameters(parameters, declareParameterMode);
1,859✔
1786
    if (!result.successful) {
1,859!
1787
      return result;
×
1788
    }
1789

1790
    // give all SetParametersCallbacks a chance to veto this change
1791
    for (const callback of this._setParametersCallbacks) {
1,859✔
1792
      result = callback(parameters);
1,175✔
1793
      if (!result.successful) {
1,175✔
1794
        // a callback has vetoed a parameter change
1795
        return result;
1✔
1796
      }
1797
    }
1798

1799
    // collectively track updates to parameters for use
1800
    // when publishing a ParameterEvent
1801
    const newParameters = [];
1,858✔
1802
    const changedParameters = [];
1,858✔
1803
    const deletedParameters = [];
1,858✔
1804

1805
    for (const parameter of parameters) {
1,858✔
1806
      if (parameter.type == ParameterType.PARAMETER_NOT_SET) {
1,858✔
1807
        this.undeclareParameter(parameter.name);
1✔
1808
        deletedParameters.push(parameter);
1✔
1809
      } else {
1810
        this._parameters.set(parameter.name, parameter);
1,857✔
1811
        if (declareParameterMode) {
1,857✔
1812
          newParameters.push(parameter);
1,842✔
1813
        } else {
1814
          changedParameters.push(parameter);
15✔
1815
        }
1816
      }
1817
    }
1818

1819
    // create ParameterEvent
1820
    const parameterEvent = new (loader.loadInterface(
1,858✔
1821
      PARAMETER_EVENT_MSG_TYPE
1822
    ))();
1823

1824
    const { seconds, nanoseconds } = this._clock.now().secondsAndNanoseconds;
1,858✔
1825
    parameterEvent.stamp = {
1,858✔
1826
      sec: Number(seconds),
1827
      nanosec: Number(nanoseconds),
1828
    };
1829

1830
    parameterEvent.node =
1,858✔
1831
      this.namespace() === '/'
1,858✔
1832
        ? this.namespace() + this.name()
1833
        : this.namespace() + '/' + this.name();
1834

1835
    if (newParameters.length > 0) {
1,858✔
1836
      parameterEvent['new_parameters'] = newParameters.map((parameter) =>
1,842✔
1837
        parameter.toParameterMessage()
1,842✔
1838
      );
1839
    }
1840
    if (changedParameters.length > 0) {
1,858✔
1841
      parameterEvent['changed_parameters'] = changedParameters.map(
15✔
1842
        (parameter) => parameter.toParameterMessage()
15✔
1843
      );
1844
    }
1845
    if (deletedParameters.length > 0) {
1,858✔
1846
      parameterEvent['deleted_parameters'] = deletedParameters.map(
1✔
1847
        (parameter) => parameter.toParameterMessage()
1✔
1848
      );
1849
    }
1850

1851
    // Publish ParameterEvent.
1852
    this._parameterEventPublisher.publish(parameterEvent);
1,858✔
1853

1854
    return {
1,858✔
1855
      successful: true,
1856
      reason: '',
1857
    };
1858
  }
1859

1860
  /**
1861
   * This callback is called when declaring a parameter or setting a parameter.
1862
   * The callback is provided a list of parameters and returns a SetParameterResult
1863
   * to indicate approval or veto of the operation.
1864
   *
1865
   * @callback SetParametersCallback
1866
   * @param {Parameter[]} parameters - The message published
1867
   * @returns {rcl_interfaces.msg.SetParameterResult} -
1868
   *
1869
   * @see [Node.addOnSetParametersCallback]{@link Node#addOnSetParametersCallback}
1870
   * @see [Node.removeOnSetParametersCallback]{@link Node#removeOnSetParametersCallback}
1871
   */
1872

1873
  /**
1874
   * Add a callback to the front of the list of callbacks invoked for parameter declaration
1875
   * and setting. No checks are made for duplicate callbacks.
1876
   *
1877
   * @param {SetParametersCallback} callback - The callback to add.
1878
   * @returns {undefined}
1879
   */
1880
  addOnSetParametersCallback(callback) {
1881
    this._setParametersCallbacks.unshift(callback);
698✔
1882
  }
1883

1884
  /**
1885
   * Remove a callback from the list of SetParametersCallbacks.
1886
   * If the callback is not found the process is a nop.
1887
   *
1888
   * @param {SetParametersCallback} callback - The callback to be removed
1889
   * @returns {undefined}
1890
   */
1891
  removeOnSetParametersCallback(callback) {
1892
    const idx = this._setParametersCallbacks.indexOf(callback);
2✔
1893
    if (idx > -1) {
2!
1894
      this._setParametersCallbacks.splice(idx, 1);
2✔
1895
    }
1896
  }
1897

1898
  /**
1899
   * Get the fully qualified name of the node.
1900
   *
1901
   * @returns {string} - String containing the fully qualified name of the node.
1902
   */
1903
  getFullyQualifiedName() {
1904
    return rclnodejs.getFullyQualifiedName(this.handle);
1✔
1905
  }
1906

1907
  /**
1908
   * Get the RMW implementation identifier
1909
   * @returns {string} - The RMW implementation identifier.
1910
   */
1911
  getRMWImplementationIdentifier() {
1912
    return rclnodejs.getRMWImplementationIdentifier();
1✔
1913
  }
1914

1915
  /**
1916
   * Return a topic name expanded and remapped.
1917
   * @param {string} topicName - Topic name to be expanded and remapped.
1918
   * @param {boolean} [onlyExpand=false] - If `true`, remapping rules won't be applied.
1919
   * @returns {string} - A fully qualified topic name.
1920
   */
1921
  resolveTopicName(topicName, onlyExpand = false) {
2✔
1922
    if (typeof topicName !== 'string') {
3!
NEW
1923
      throw new TypeValidationError('topicName', topicName, 'string', {
×
1924
        nodeName: this.name(),
1925
      });
1926
    }
1927
    return rclnodejs.resolveName(
3✔
1928
      this.handle,
1929
      topicName,
1930
      onlyExpand,
1931
      /*isService=*/ false
1932
    );
1933
  }
1934

1935
  /**
1936
   * Return a service name expanded and remapped.
1937
   * @param {string} service - Service name to be expanded and remapped.
1938
   * @param {boolean} [onlyExpand=false] - If `true`, remapping rules won't be applied.
1939
   * @returns {string} - A fully qualified service name.
1940
   */
1941
  resolveServiceName(service, onlyExpand = false) {
2✔
1942
    if (typeof service !== 'string') {
3!
NEW
1943
      throw new TypeValidationError('service', service, 'string', {
×
1944
        nodeName: this.name(),
1945
      });
1946
    }
1947
    return rclnodejs.resolveName(
3✔
1948
      this.handle,
1949
      service,
1950
      onlyExpand,
1951
      /*isService=*/ true
1952
    );
1953
  }
1954

1955
  // returns on 1st error or result {successful, reason}
1956
  _validateParameters(parameters = [], declareParameterMode = false) {
×
1957
    for (const parameter of parameters) {
1,859✔
1958
      // detect invalid parameter
1959
      try {
1,859✔
1960
        parameter.validate();
1,859✔
1961
      } catch {
1962
        return {
×
1963
          successful: false,
1964
          reason: `Invalid ${parameter.name}`,
1965
        };
1966
      }
1967

1968
      // detect undeclared parameter
1969
      if (!this.hasParameterDescriptor(parameter.name)) {
1,859!
1970
        return {
×
1971
          successful: false,
1972
          reason: `Parameter ${parameter.name} has not been declared`,
1973
        };
1974
      }
1975

1976
      // detect readonly parameter that can not be updated
1977
      const descriptor = this.getParameterDescriptor(parameter.name);
1,859✔
1978
      if (!declareParameterMode && descriptor.readOnly) {
1,859!
1979
        return {
×
1980
          successful: false,
1981
          reason: `Parameter ${parameter.name} is readonly`,
1982
        };
1983
      }
1984

1985
      // validate parameter against descriptor if not an undeclare action
1986
      if (parameter.type != ParameterType.PARAMETER_NOT_SET) {
1,859✔
1987
        try {
1,858✔
1988
          descriptor.validateParameter(parameter);
1,858✔
1989
        } catch {
1990
          return {
×
1991
            successful: false,
1992
            reason: `Parameter ${parameter.name} does not  readonly`,
1993
          };
1994
        }
1995
      }
1996
    }
1997

1998
    return {
1,859✔
1999
      successful: true,
2000
      reason: null,
2001
    };
2002
  }
2003

2004
  // Get a Map(nodeName->Parameter[]) of CLI parameter args that
2005
  // apply to 'this' node, .e.g., -p mynode:foo:=bar -p hello:=world
2006
  _getNativeParameterOverrides() {
2007
    const overrides = new Map();
690✔
2008

2009
    // Get native parameters from rcl context->global_arguments.
2010
    // rclnodejs returns an array of objects, 1 for each node e.g., -p my_node:foo:=bar,
2011
    // and a node named '/**' for global parameter rules,
2012
    // i.e., does not include a node identifier, e.g., -p color:=red
2013
    // {
2014
    //   name: string // node name
2015
    //   parameters[] = {
2016
    //     name: string
2017
    //     type: uint
2018
    //     value: object
2019
    // }
2020
    const cliParamOverrideData = rclnodejs.getParameterOverrides(
690✔
2021
      this.context.handle
2022
    );
2023

2024
    // convert native CLI parameterOverrides to Map<nodeName,Array<ParameterOverride>>
2025
    const cliParamOverrides = new Map();
690✔
2026
    if (cliParamOverrideData) {
690✔
2027
      for (let nodeParamData of cliParamOverrideData) {
8✔
2028
        const nodeName = nodeParamData.name;
12✔
2029
        const nodeParamOverrides = [];
12✔
2030
        for (let paramData of nodeParamData.parameters) {
12✔
2031
          const paramOverride = new Parameter(
17✔
2032
            paramData.name,
2033
            paramData.type,
2034
            paramData.value
2035
          );
2036
          nodeParamOverrides.push(paramOverride);
17✔
2037
        }
2038
        cliParamOverrides.set(nodeName, nodeParamOverrides);
12✔
2039
      }
2040
    }
2041

2042
    // collect global CLI global parameters, name == /**
2043
    let paramOverrides = cliParamOverrides.get('/**'); // array of ParameterOverrides
690✔
2044
    if (paramOverrides) {
690✔
2045
      for (const parameter of paramOverrides) {
5✔
2046
        overrides.set(parameter.name, parameter);
6✔
2047
      }
2048
    }
2049

2050
    // merge CLI node parameterOverrides with global parameterOverrides, replace existing
2051
    paramOverrides = cliParamOverrides.get(this.name()); // array of ParameterOverrides
690✔
2052
    if (paramOverrides) {
690✔
2053
      for (const parameter of paramOverrides) {
5✔
2054
        overrides.set(parameter.name, parameter);
7✔
2055
      }
2056
    }
2057

2058
    return overrides;
690✔
2059
  }
2060

2061
  /**
2062
   * Invokes the callback with a raw message of the given type. After the callback completes
2063
   * the message will be destroyed.
2064
   * @param {function} Type - Message type to create.
2065
   * @param {function} callback - Callback to invoke. First parameter will be the raw message,
2066
   * and the second is a function to retrieve the deserialized message.
2067
   * @returns {undefined}
2068
   */
2069
  _runWithMessageType(Type, callback) {
2070
    let message = new Type();
895✔
2071

2072
    callback(message.toRawROS(), () => {
895✔
2073
      let result = new Type();
659✔
2074
      result.deserialize(message.refObject);
659✔
2075

2076
      return result;
659✔
2077
    });
2078

2079
    Type.destroyRawROS(message);
895✔
2080
  }
2081

2082
  _addActionClient(actionClient) {
2083
    this._actionClients.push(actionClient);
41✔
2084
    this.syncHandles();
41✔
2085
  }
2086

2087
  _addActionServer(actionServer) {
2088
    this._actionServers.push(actionServer);
41✔
2089
    this.syncHandles();
41✔
2090
  }
2091

2092
  _getValidatedTopic(topicName, noDemangle) {
2093
    if (noDemangle) {
5!
2094
      return topicName;
×
2095
    }
2096
    const fqTopicName = rclnodejs.expandTopicName(
5✔
2097
      topicName,
2098
      this.name(),
2099
      this.namespace()
2100
    );
2101
    validateFullTopicName(fqTopicName);
5✔
2102
    return rclnodejs.remapTopicName(this.handle, fqTopicName);
5✔
2103
  }
2104
}
2105

2106
/**
2107
 * Create an Options instance initialized with default values.
2108
 * @returns {Options} - The new initialized instance.
2109
 * @static
2110
 * @example
2111
 * {
2112
 *   enableTypedArray: true,
2113
 *   isRaw: false,
2114
 *   qos: QoS.profileDefault,
2115
 *   contentFilter: undefined,
2116
 *   serializationMode: 'default',
2117
 * }
2118
 */
2119
Node.getDefaultOptions = function () {
26✔
2120
  return {
6,467✔
2121
    enableTypedArray: true,
2122
    isRaw: false,
2123
    qos: QoS.profileDefault,
2124
    contentFilter: undefined,
2125
    serializationMode: 'default',
2126
  };
2127
};
2128

2129
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