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

RobotWebTools / rclnodejs / 19852408168

02 Dec 2025 08:42AM UTC coverage: 82.719% (-0.03%) from 82.751%
19852408168

Pull #1336

github

web-flow
Merge f0627beb2 into 2221b4ae6
Pull Request #1336: Support logging rosout

1099 of 1457 branches covered (75.43%)

Branch coverage included in aggregate %.

29 of 30 new or added lines in 3 files covered. (96.67%)

32 existing lines in 1 file now uncovered.

2491 of 2883 relevant lines covered (86.4%)

486.03 hits per line

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

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

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

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

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

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

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

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

113
    this._context = context;
781✔
114
    this.context.onNodeCreated(this);
781✔
115

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

138
    if (this._enableRosout) {
781✔
139
      rclnodejs.initRosoutPublisherForNode(this.handle);
780✔
140
    }
141

142
    this._parameterEventPublisher = this.createPublisher(
781✔
143
      PARAMETER_EVENT_MSG_TYPE,
144
      PARAMETER_EVENT_TOPIC
145
    );
146

147
    // initialize _parameterOverrides from parameters defined on the commandline
148
    this._parameterOverrides = this._getNativeParameterOverrides();
781✔
149

150
    // override cli parameterOverrides with those specified in options
151
    if (options.parameterOverrides.length > 0) {
781✔
152
      for (const parameter of options.parameterOverrides) {
8✔
153
        if ((!parameter) instanceof Parameter) {
13!
154
          throw new TypeValidationError(
×
155
            'parameterOverride',
156
            parameter,
157
            'Parameter instance',
158
            {
159
              nodeName: name,
160
            }
161
          );
162
        }
163
        this._parameterOverrides.set(parameter.name, parameter);
13✔
164
      }
165
    }
166

167
    // initialize _parameters from parameterOverrides
168
    if (options.automaticallyDeclareParametersFromOverrides) {
781✔
169
      for (const parameter of this._parameterOverrides.values()) {
5✔
170
        parameter.validate();
10✔
171
        const descriptor = ParameterDescriptor.fromParameter(parameter);
10✔
172
        this._parameters.set(parameter.name, parameter);
10✔
173
        this._parameterDescriptors.set(parameter.name, descriptor);
10✔
174
      }
175
    }
176

177
    // Clock that has support for ROS time.
178
    // Note: parameter overrides and parameter event publisher need to be ready at this point
179
    // to be able to declare 'use_sim_time' if it was not declared yet.
180
    this._clock = new Clock.ROSClock();
781✔
181
    this._timeSource = new TimeSource(this);
781✔
182
    this._timeSource.attachClock(this._clock);
781✔
183

184
    if (options.startParameterServices) {
781✔
185
      this._parameterService = new ParameterService(this);
775✔
186
      this._parameterService.start();
775✔
187
    }
188

189
    if (
781!
190
      DistroUtils.getDistroId() >= DistroUtils.getDistroId('jazzy') &&
1,562✔
191
      options.startTypeDescriptionService
192
    ) {
193
      this._typeDescriptionService = new TypeDescriptionService(this);
781✔
194
      this._typeDescriptionService.start();
781✔
195
    }
196
  }
197

198
  execute(handles) {
199
    let timersReady = this._timers.filter((timer) =>
5,520✔
200
      handles.includes(timer.handle)
4,606✔
201
    );
202
    let guardsReady = this._guards.filter((guard) =>
5,520✔
203
      handles.includes(guard.handle)
3✔
204
    );
205
    let subscriptionsReady = this._subscriptions.filter((subscription) =>
5,520✔
206
      handles.includes(subscription.handle)
480✔
207
    );
208
    let clientsReady = this._clients.filter((client) =>
5,520✔
209
      handles.includes(client.handle)
313✔
210
    );
211
    let servicesReady = this._services.filter((service) =>
5,520✔
212
      handles.includes(service.handle)
20,813✔
213
    );
214
    let actionClientsReady = this._actionClients.filter((actionClient) =>
5,520✔
215
      handles.includes(actionClient.handle)
156✔
216
    );
217
    let actionServersReady = this._actionServers.filter((actionServer) =>
5,520✔
218
      handles.includes(actionServer.handle)
156✔
219
    );
220
    let eventsReady = this._events.filter((event) =>
5,520✔
221
      handles.includes(event.handle)
4✔
222
    );
223

224
    timersReady.forEach((timer) => {
5,520✔
225
      if (timer.isReady()) {
4,595✔
226
        rclnodejs.callTimer(timer.handle);
4,547✔
227
        timer.callback();
4,547✔
228
      }
229
    });
230

231
    eventsReady.forEach((event) => {
5,520✔
232
      event.takeData();
4✔
233
    });
234

235
    for (const subscription of subscriptionsReady) {
5,520✔
236
      if (subscription.isDestroyed()) continue;
438✔
237
      if (subscription.isRaw) {
431✔
238
        let rawMessage = rclnodejs.rclTakeRaw(subscription.handle);
1✔
239
        if (rawMessage) {
1!
240
          subscription.processResponse(rawMessage);
1✔
241
        }
242
        continue;
1✔
243
      }
244

245
      this._runWithMessageType(
430✔
246
        subscription.typeClass,
247
        (message, deserialize) => {
248
          let success = rclnodejs.rclTake(subscription.handle, message);
430✔
249
          if (success) {
430✔
250
            subscription.processResponse(deserialize());
370✔
251
          }
252
        }
253
      );
254
    }
255

256
    for (const guard of guardsReady) {
5,520✔
257
      if (guard.isDestroyed()) continue;
3!
258

259
      guard.callback();
3✔
260
    }
261

262
    for (const client of clientsReady) {
5,520✔
263
      if (client.isDestroyed()) continue;
159!
264
      this._runWithMessageType(
159✔
265
        client.typeClass.Response,
266
        (message, deserialize) => {
267
          let sequenceNumber = rclnodejs.rclTakeResponse(
159✔
268
            client.handle,
269
            message
270
          );
271
          if (sequenceNumber !== undefined) {
159✔
272
            client.processResponse(sequenceNumber, deserialize());
86✔
273
          }
274
        }
275
      );
276
    }
277

278
    for (const service of servicesReady) {
5,520✔
279
      if (service.isDestroyed()) continue;
177!
280
      this._runWithMessageType(
177✔
281
        service.typeClass.Request,
282
        (message, deserialize) => {
283
          let header = rclnodejs.rclTakeRequest(
177✔
284
            service.handle,
285
            this.handle,
286
            message
287
          );
288
          if (header) {
177✔
289
            service.processRequest(header, deserialize());
91✔
290
          }
291
        }
292
      );
293
    }
294

295
    for (const actionClient of actionClientsReady) {
5,520✔
296
      if (actionClient.isDestroyed()) continue;
73!
297

298
      const properties = actionClient.handle.properties;
73✔
299

300
      if (properties.isGoalResponseReady) {
73✔
301
        this._runWithMessageType(
34✔
302
          actionClient.typeClass.impl.SendGoalService.Response,
303
          (message, deserialize) => {
304
            let sequence = rclnodejs.actionTakeGoalResponse(
34✔
305
              actionClient.handle,
306
              message
307
            );
308
            if (sequence != undefined) {
34✔
309
              actionClient.processGoalResponse(sequence, deserialize());
30✔
310
            }
311
          }
312
        );
313
      }
314

315
      if (properties.isCancelResponseReady) {
73✔
316
        this._runWithMessageType(
6✔
317
          actionClient.typeClass.impl.CancelGoal.Response,
318
          (message, deserialize) => {
319
            let sequence = rclnodejs.actionTakeCancelResponse(
6✔
320
              actionClient.handle,
321
              message
322
            );
323
            if (sequence != undefined) {
6✔
324
              actionClient.processCancelResponse(sequence, deserialize());
4✔
325
            }
326
          }
327
        );
328
      }
329

330
      if (properties.isResultResponseReady) {
73✔
331
        this._runWithMessageType(
15✔
332
          actionClient.typeClass.impl.GetResultService.Response,
333
          (message, deserialize) => {
334
            let sequence = rclnodejs.actionTakeResultResponse(
15✔
335
              actionClient.handle,
336
              message
337
            );
338
            if (sequence != undefined) {
15!
339
              actionClient.processResultResponse(sequence, deserialize());
15✔
340
            }
341
          }
342
        );
343
      }
344

345
      if (properties.isFeedbackReady) {
73✔
346
        this._runWithMessageType(
7✔
347
          actionClient.typeClass.impl.FeedbackMessage,
348
          (message, deserialize) => {
349
            let success = rclnodejs.actionTakeFeedback(
7✔
350
              actionClient.handle,
351
              message
352
            );
353
            if (success) {
7✔
354
              actionClient.processFeedbackMessage(deserialize());
5✔
355
            }
356
          }
357
        );
358
      }
359

360
      if (properties.isStatusReady) {
73✔
361
        this._runWithMessageType(
37✔
362
          actionClient.typeClass.impl.GoalStatusArray,
363
          (message, deserialize) => {
364
            let success = rclnodejs.actionTakeStatus(
37✔
365
              actionClient.handle,
366
              message
367
            );
368
            if (success) {
37✔
369
              actionClient.processStatusMessage(deserialize());
34✔
370
            }
371
          }
372
        );
373
      }
374
    }
375

376
    for (const actionServer of actionServersReady) {
5,520✔
377
      if (actionServer.isDestroyed()) continue;
92!
378

379
      const properties = actionServer.handle.properties;
92✔
380

381
      if (properties.isGoalRequestReady) {
92✔
382
        this._runWithMessageType(
31✔
383
          actionServer.typeClass.impl.SendGoalService.Request,
384
          (message, deserialize) => {
385
            const result = rclnodejs.actionTakeGoalRequest(
31✔
386
              actionServer.handle,
387
              message
388
            );
389
            if (result) {
31✔
390
              actionServer.processGoalRequest(result, deserialize());
30✔
391
            }
392
          }
393
        );
394
      }
395

396
      if (properties.isCancelRequestReady) {
92✔
397
        this._runWithMessageType(
7✔
398
          actionServer.typeClass.impl.CancelGoal.Request,
399
          (message, deserialize) => {
400
            const result = rclnodejs.actionTakeCancelRequest(
7✔
401
              actionServer.handle,
402
              message
403
            );
404
            if (result) {
7✔
405
              actionServer.processCancelRequest(result, deserialize());
4✔
406
            }
407
          }
408
        );
409
      }
410

411
      if (properties.isResultRequestReady) {
92✔
412
        this._runWithMessageType(
22✔
413
          actionServer.typeClass.impl.GetResultService.Request,
414
          (message, deserialize) => {
415
            const result = rclnodejs.actionTakeResultRequest(
22✔
416
              actionServer.handle,
417
              message
418
            );
419
            if (result) {
22✔
420
              actionServer.processResultRequest(result, deserialize());
15✔
421
            }
422
          }
423
        );
424
      }
425

426
      if (properties.isGoalExpired) {
92✔
427
        let GoalInfoArray = ActionInterfaces.GoalInfo.ArrayType;
4✔
428
        let message = new GoalInfoArray(actionServer._goalHandles.size);
4✔
429
        let count = rclnodejs.actionExpireGoals(
4✔
430
          actionServer.handle,
431
          actionServer._goalHandles.size,
432
          message._refArray.buffer
433
        );
434
        if (count > 0) {
4✔
435
          actionServer.processGoalExpired(message, count);
3✔
436
        }
437
        GoalInfoArray.freeArray(message);
4✔
438
      }
439
    }
440

441
    // At this point it is safe to clear the cache of any
442
    // destroyed entity references
443
    Entity._gcHandles();
5,520✔
444
  }
445

446
  /**
447
   * Determine if this node is spinning.
448
   * @returns {boolean} - true when spinning; otherwise returns false.
449
   */
450
  get spinning() {
451
    return this._spinning;
4,425✔
452
  }
453

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

470
  /**
471
   * Use spin().
472
   * @deprecated, since 0.18.0
473
   */
474
  startSpinning(timeout) {
475
    this.spin(timeout);
×
476
  }
477

478
  /**
479
   * Terminate spinning - no further events will be received.
480
   * @returns {undefined}
481
   */
482
  stop() {
483
    super.stop();
611✔
484
    this._spinning = false;
611✔
485
  }
486

487
  /**
488
   * Terminate spinning - no further events will be received.
489
   * @returns {undefined}
490
   * @deprecated since 0.18.0, Use stop().
491
   */
492
  stopSpinning() {
493
    super.stop();
×
494
    this._spinning = false;
×
495
  }
496

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

512
  _removeEntityFromArray(entity, array) {
513
    let index = array.indexOf(entity);
337✔
514
    if (index > -1) {
337✔
515
      array.splice(index, 1);
335✔
516
    }
517
  }
518

519
  _destroyEntity(entity, array, syncHandles = true) {
283✔
520
    if (entity['isDestroyed'] && entity.isDestroyed()) return;
290✔
521

522
    this._removeEntityFromArray(entity, array);
282✔
523
    if (syncHandles) {
282✔
524
      this.syncHandles();
277✔
525
    }
526

527
    if (entity['_destroy']) {
282✔
528
      entity._destroy();
276✔
529
    } else {
530
      // guards and timers
531
      entity.handle.release();
6✔
532
    }
533
  }
534

535
  _validateOptions(options) {
536
    if (
7,318✔
537
      options !== undefined &&
7,388✔
538
      (options === null || typeof options !== 'object')
539
    ) {
540
      throw new TypeValidationError('options', options, 'object', {
20✔
541
        nodeName: this.name(),
542
      });
543
    }
544

545
    if (options === undefined) {
7,298✔
546
      return Node.getDefaultOptions();
7,273✔
547
    }
548

549
    if (options.enableTypedArray === undefined) {
25✔
550
      options = Object.assign(options, { enableTypedArray: true });
12✔
551
    }
552

553
    if (options.qos === undefined) {
25✔
554
      options = Object.assign(options, { qos: QoS.profileDefault });
12✔
555
    }
556

557
    if (options.isRaw === undefined) {
25✔
558
      options = Object.assign(options, { isRaw: false });
14✔
559
    }
560

561
    if (options.serializationMode === undefined) {
25✔
562
      options = Object.assign(options, { serializationMode: 'default' });
8✔
563
    } else if (!isValidSerializationMode(options.serializationMode)) {
17✔
564
      throw new ValidationError(
1✔
565
        `Invalid serializationMode: ${options.serializationMode}. Valid modes are: 'default', 'plain', 'json'`,
566
        {
567
          code: 'INVALID_SERIALIZATION_MODE',
568
          argumentName: 'serializationMode',
569
          providedValue: options.serializationMode,
570
          expectedType: "'default' | 'plain' | 'json'",
571
          nodeName: this.name(),
572
        }
573
      );
574
    }
575

576
    return options;
24✔
577
  }
578

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

593
    if (typeof period !== 'bigint') {
58✔
594
      throw new TypeValidationError('period', period, 'bigint', {
1✔
595
        nodeName: this.name(),
596
      });
597
    }
598
    if (typeof callback !== 'function') {
57✔
599
      throw new TypeValidationError('callback', callback, 'function', {
1✔
600
        nodeName: this.name(),
601
      });
602
    }
603

604
    const timerClock = clock || this._clock;
56✔
605
    let timerHandle = rclnodejs.createTimer(
56✔
606
      timerClock.handle,
607
      this.context.handle,
608
      period
609
    );
610
    let timer = new Timer(timerHandle, period, callback);
56✔
611
    debug('Finish creating timer, period = %d.', period);
56✔
612
    this._timers.push(timer);
56✔
613
    this.syncHandles();
56✔
614

615
    return timer;
56✔
616
  }
617

618
  /**
619
   * Create a Rate.
620
   *
621
   * @param {number} hz - The frequency of the rate timer; default is 1 hz.
622
   * @returns {Promise<Rate>} - Promise resolving to new instance of Rate.
623
   */
624
  async createRate(hz = 1) {
4✔
625
    if (typeof hz !== 'number') {
9!
626
      throw new TypeValidationError('hz', hz, 'number', {
×
627
        nodeName: this.name(),
628
      });
629
    }
630

631
    const MAX_RATE_HZ_IN_MILLISECOND = 1000.0;
9✔
632
    if (hz <= 0.0 || hz > MAX_RATE_HZ_IN_MILLISECOND) {
9✔
633
      throw new RangeValidationError(
2✔
634
        'hz',
635
        hz,
636
        `0.0 < hz <= ${MAX_RATE_HZ_IN_MILLISECOND}`,
637
        {
638
          nodeName: this.name(),
639
        }
640
      );
641
    }
642

643
    // lazy initialize rateTimerServer
644
    if (!this._rateTimerServer) {
7✔
645
      this._rateTimerServer = new Rates.RateTimerServer(this);
5✔
646
      await this._rateTimerServer.init();
5✔
647
    }
648

649
    const period = Math.round(1000 / hz);
7✔
650
    const timer = this._rateTimerServer.createTimer(BigInt(period) * 1000000n);
7✔
651
    const rate = new Rates.Rate(hz, timer);
7✔
652

653
    return rate;
7✔
654
  }
655

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

678
  _createPublisher(typeClass, topic, options, publisherClass, eventCallbacks) {
679
    if (typeof typeClass === 'string' || typeof typeClass === 'object') {
1,146✔
680
      typeClass = loader.loadInterface(typeClass);
1,122✔
681
    }
682
    options = this._validateOptions(options);
1,139✔
683

684
    if (typeof typeClass !== 'function') {
1,139✔
685
      throw new TypeValidationError('typeClass', typeClass, 'function', {
8✔
686
        nodeName: this.name(),
687
        entityType: 'publisher',
688
      });
689
    }
690
    if (typeof topic !== 'string') {
1,131✔
691
      throw new TypeValidationError('topic', topic, 'string', {
12✔
692
        nodeName: this.name(),
693
        entityType: 'publisher',
694
      });
695
    }
696
    if (
1,119!
697
      eventCallbacks &&
1,121✔
698
      !(eventCallbacks instanceof PublisherEventCallbacks)
699
    ) {
700
      throw new TypeValidationError(
×
701
        'eventCallbacks',
702
        eventCallbacks,
703
        'PublisherEventCallbacks',
704
        {
705
          nodeName: this.name(),
706
          entityType: 'publisher',
707
          entityName: topic,
708
        }
709
      );
710
    }
711

712
    let publisher = publisherClass.createPublisher(
1,119✔
713
      this,
714
      typeClass,
715
      topic,
716
      options,
717
      eventCallbacks
718
    );
719
    debug('Finish creating publisher, topic = %s.', topic);
1,109✔
720
    this._publishers.push(publisher);
1,109✔
721
    return publisher;
1,109✔
722
  }
723

724
  /**
725
   * This callback is called when a message is published
726
   * @callback SubscriptionCallback
727
   * @param {Object} message - The message published
728
   * @see [Node.createSubscription]{@link Node#createSubscription}
729
   * @see [Node.createPublisher]{@link Node#createPublisher}
730
   * @see {@link Publisher}
731
   * @see {@link Subscription}
732
   */
733

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

767
    if (typeof options === 'function') {
417✔
768
      callback = options;
376✔
769
      options = undefined;
376✔
770
    }
771
    options = this._validateOptions(options);
417✔
772

773
    if (typeof typeClass !== 'function') {
407✔
774
      throw new TypeValidationError('typeClass', typeClass, 'function', {
4✔
775
        nodeName: this.name(),
776
        entityType: 'subscription',
777
      });
778
    }
779
    if (typeof topic !== 'string') {
403✔
780
      throw new TypeValidationError('topic', topic, 'string', {
6✔
781
        nodeName: this.name(),
782
        entityType: 'subscription',
783
      });
784
    }
785
    if (typeof callback !== 'function') {
397!
786
      throw new TypeValidationError('callback', callback, 'function', {
×
787
        nodeName: this.name(),
788
        entityType: 'subscription',
789
        entityName: topic,
790
      });
791
    }
792
    if (
397!
793
      eventCallbacks &&
401✔
794
      !(eventCallbacks instanceof SubscriptionEventCallbacks)
795
    ) {
796
      throw new TypeValidationError(
×
797
        'eventCallbacks',
798
        eventCallbacks,
799
        'SubscriptionEventCallbacks',
800
        {
801
          nodeName: this.name(),
802
          entityType: 'subscription',
803
          entityName: topic,
804
        }
805
      );
806
    }
807

808
    let subscription = Subscription.createSubscription(
397✔
809
      this,
810
      typeClass,
811
      topic,
812
      options,
813
      callback,
814
      eventCallbacks
815
    );
816
    debug('Finish creating subscription, topic = %s.', topic);
386✔
817
    this._subscriptions.push(subscription);
386✔
818
    this.syncHandles();
386✔
819

820
    return subscription;
386✔
821
  }
822

823
  /**
824
   * Create a Client.
825
   * @param {function|string|object} typeClass - The ROS message class,
826
        OR a string representing the message class, e.g. 'std_msgs/msg/String',
827
        OR an object representing the message class, e.g. {package: 'std_msgs', type: 'msg', name: 'String'}
828
   * @param {string} serviceName - The service name to request.
829
   * @param {object} options - The options argument used to parameterize the client.
830
   * @param {boolean} options.enableTypedArray - The response will use TypedArray if necessary, default: true.
831
   * @param {QoS} options.qos - ROS Middleware "quality of service" settings for the client, default: QoS.profileDefault.
832
   * @return {Client} - An instance of Client.
833
   */
834
  createClient(typeClass, serviceName, options) {
835
    if (typeof typeClass === 'string' || typeof typeClass === 'object') {
196✔
836
      typeClass = loader.loadInterface(typeClass);
186✔
837
    }
838
    options = this._validateOptions(options);
189✔
839

840
    if (typeof typeClass !== 'function') {
189✔
841
      throw new TypeValidationError('typeClass', typeClass, 'function', {
8✔
842
        nodeName: this.name(),
843
        entityType: 'client',
844
      });
845
    }
846
    if (typeof serviceName !== 'string') {
181✔
847
      throw new TypeValidationError('serviceName', serviceName, 'string', {
12✔
848
        nodeName: this.name(),
849
        entityType: 'client',
850
      });
851
    }
852

853
    let client = Client.createClient(
169✔
854
      this.handle,
855
      serviceName,
856
      typeClass,
857
      options
858
    );
859
    debug('Finish creating client, service = %s.', serviceName);
159✔
860
    this._clients.push(client);
159✔
861
    this.syncHandles();
159✔
862

863
    return client;
159✔
864
  }
865

866
  /**
867
   * This callback is called when a request is sent to service
868
   * @callback RequestCallback
869
   * @param {Object} request - The request sent to the service
870
   * @param {Response} response - The response to client.
871
        Use [response.send()]{@link Response#send} to send response object to client
872
   * @return {undefined}
873
   * @see [Node.createService]{@link Node#createService}
874
   * @see [Client.sendRequest]{@link Client#sendRequest}
875
   * @see {@link Client}
876
   * @see {@link Service}
877
   * @see {@link Response#send}
878
   */
879

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

898
    if (typeof options === 'function') {
4,716✔
899
      callback = options;
4,693✔
900
      options = undefined;
4,693✔
901
    }
902
    options = this._validateOptions(options);
4,716✔
903

904
    if (typeof typeClass !== 'function') {
4,706✔
905
      throw new TypeValidationError('typeClass', typeClass, 'function', {
4✔
906
        nodeName: this.name(),
907
        entityType: 'service',
908
      });
909
    }
910
    if (typeof serviceName !== 'string') {
4,702✔
911
      throw new TypeValidationError('serviceName', serviceName, 'string', {
6✔
912
        nodeName: this.name(),
913
        entityType: 'service',
914
      });
915
    }
916
    if (typeof callback !== 'function') {
4,696!
917
      throw new TypeValidationError('callback', callback, 'function', {
×
918
        nodeName: this.name(),
919
        entityType: 'service',
920
        entityName: serviceName,
921
      });
922
    }
923

924
    let service = Service.createService(
4,696✔
925
      this.handle,
926
      serviceName,
927
      typeClass,
928
      options,
929
      callback
930
    );
931
    debug('Finish creating service, service = %s.', serviceName);
4,686✔
932
    this._services.push(service);
4,686✔
933
    this.syncHandles();
4,686✔
934

935
    return service;
4,686✔
936
  }
937

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

950
    const parameterClient = new ParameterClient(this, remoteNodeName, options);
103✔
951
    debug(
103✔
952
      'Finish creating parameter client for remote node = %s.',
953
      remoteNodeName
954
    );
955
    this._parameterClients.push(parameterClient);
103✔
956

957
    return parameterClient;
103✔
958
  }
959

960
  /**
961
   * Create a ParameterWatcher for watching parameter changes on a remote node.
962
   * @param {string} remoteNodeName - The name of the remote node whose parameters to watch.
963
   * @param {string[]} parameterNames - Array of parameter names to watch.
964
   * @param {object} [options] - Options for parameter watcher.
965
   * @param {number} [options.timeout=5000] - Default timeout in milliseconds for service calls.
966
   * @return {ParameterWatcher} - An instance of ParameterWatcher.
967
   */
968
  createParameterWatcher(remoteNodeName, parameterNames, options = {}) {
56✔
969
    const watcher = new ParameterWatcher(
57✔
970
      this,
971
      remoteNodeName,
972
      parameterNames,
973
      options
974
    );
975
    debug(
53✔
976
      'Finish creating parameter watcher for remote node = %s.',
977
      remoteNodeName
978
    );
979
    this._parameterWatchers.push(watcher);
53✔
980

981
    return watcher;
53✔
982
  }
983

984
  /**
985
   * Create a guard condition.
986
   * @param {Function} callback - The callback to be called when the guard condition is triggered.
987
   * @return {GuardCondition} - An instance of GuardCondition.
988
   */
989
  createGuardCondition(callback) {
990
    if (typeof callback !== 'function') {
3!
991
      throw new TypeValidationError('callback', callback, 'function', {
×
992
        nodeName: this.name(),
993
        entityType: 'guard_condition',
994
      });
995
    }
996

997
    let guard = GuardCondition.createGuardCondition(callback, this.context);
3✔
998
    debug('Finish creating guard condition');
3✔
999
    this._guards.push(guard);
3✔
1000
    this.syncHandles();
3✔
1001

1002
    return guard;
3✔
1003
  }
1004

1005
  /**
1006
   * Destroy all resource allocated by this node, including
1007
   * <code>Timer</code>s/<code>Publisher</code>s/<code>Subscription</code>s
1008
   * /<code>Client</code>s/<code>Service</code>s
1009
   * @return {undefined}
1010
   */
1011
  destroy() {
1012
    if (this.spinning) {
803✔
1013
      this.stop();
528✔
1014
    }
1015

1016
    // Action servers/clients require manual destruction due to circular reference with goal handles.
1017
    this._actionClients.forEach((actionClient) => actionClient.destroy());
803✔
1018
    this._actionServers.forEach((actionServer) => actionServer.destroy());
803✔
1019

1020
    this._parameterClients.forEach((paramClient) => paramClient.destroy());
803✔
1021
    this._parameterWatchers.forEach((watcher) => watcher.destroy());
803✔
1022

1023
    this.context.onNodeDestroyed(this);
803✔
1024

1025
    if (this._enableRosout) {
803✔
1026
      rclnodejs.finiRosoutPublisherForNode(this.handle);
780✔
1027
      this._enableRosout = false;
780✔
1028
    }
1029

1030
    this.handle.release();
803✔
1031
    this._clock = null;
803✔
1032
    this._timers = [];
803✔
1033
    this._publishers = [];
803✔
1034
    this._subscriptions = [];
803✔
1035
    this._clients = [];
803✔
1036
    this._services = [];
803✔
1037
    this._guards = [];
803✔
1038
    this._actionClients = [];
803✔
1039
    this._actionServers = [];
803✔
1040
    this._parameterClients = [];
803✔
1041
    this._parameterWatchers = [];
803✔
1042

1043
    if (this._rateTimerServer) {
803✔
1044
      this._rateTimerServer.shutdown();
5✔
1045
      this._rateTimerServer = null;
5✔
1046
    }
1047
  }
1048

1049
  /**
1050
   * Destroy a Publisher.
1051
   * @param {Publisher} publisher - The Publisher to be destroyed.
1052
   * @return {undefined}
1053
   */
1054
  destroyPublisher(publisher) {
1055
    if (!(publisher instanceof Publisher)) {
9✔
1056
      throw new TypeValidationError(
2✔
1057
        'publisher',
1058
        publisher,
1059
        'Publisher instance',
1060
        {
1061
          nodeName: this.name(),
1062
        }
1063
      );
1064
    }
1065
    if (publisher.events) {
7!
1066
      publisher.events.forEach((event) => {
×
1067
        this._destroyEntity(event, this._events);
×
1068
      });
1069
      publisher.events = [];
×
1070
    }
1071
    this._destroyEntity(publisher, this._publishers, false);
7✔
1072
  }
1073

1074
  /**
1075
   * Destroy a Subscription.
1076
   * @param {Subscription} subscription - The Subscription to be destroyed.
1077
   * @return {undefined}
1078
   */
1079
  destroySubscription(subscription) {
1080
    if (!(subscription instanceof Subscription)) {
78✔
1081
      throw new TypeValidationError(
2✔
1082
        'subscription',
1083
        subscription,
1084
        'Subscription instance',
1085
        {
1086
          nodeName: this.name(),
1087
        }
1088
      );
1089
    }
1090
    if (subscription.events) {
76✔
1091
      subscription.events.forEach((event) => {
1✔
1092
        this._destroyEntity(event, this._events);
1✔
1093
      });
1094
      subscription.events = [];
1✔
1095
    }
1096

1097
    this._destroyEntity(subscription, this._subscriptions);
76✔
1098
  }
1099

1100
  /**
1101
   * Destroy a Client.
1102
   * @param {Client} client - The Client to be destroyed.
1103
   * @return {undefined}
1104
   */
1105
  destroyClient(client) {
1106
    if (!(client instanceof Client)) {
117✔
1107
      throw new TypeValidationError('client', client, 'Client instance', {
2✔
1108
        nodeName: this.name(),
1109
      });
1110
    }
1111
    this._destroyEntity(client, this._clients);
115✔
1112
  }
1113

1114
  /**
1115
   * Destroy a Service.
1116
   * @param {Service} service - The Service to be destroyed.
1117
   * @return {undefined}
1118
   */
1119
  destroyService(service) {
1120
    if (!(service instanceof Service)) {
8✔
1121
      throw new TypeValidationError('service', service, 'Service instance', {
2✔
1122
        nodeName: this.name(),
1123
      });
1124
    }
1125
    this._destroyEntity(service, this._services);
6✔
1126
  }
1127

1128
  /**
1129
   * Destroy a ParameterClient.
1130
   * @param {ParameterClient} parameterClient - The ParameterClient to be destroyed.
1131
   * @return {undefined}
1132
   */
1133
  destroyParameterClient(parameterClient) {
1134
    if (!(parameterClient instanceof ParameterClient)) {
54!
1135
      throw new TypeError('Invalid argument');
×
1136
    }
1137
    this._removeEntityFromArray(parameterClient, this._parameterClients);
54✔
1138
    parameterClient.destroy();
54✔
1139
  }
1140

1141
  /**
1142
   * Destroy a ParameterWatcher.
1143
   * @param {ParameterWatcher} watcher - The ParameterWatcher to be destroyed.
1144
   * @return {undefined}
1145
   */
1146
  destroyParameterWatcher(watcher) {
1147
    if (!(watcher instanceof ParameterWatcher)) {
1!
1148
      throw new TypeError('Invalid argument');
×
1149
    }
1150
    this._removeEntityFromArray(watcher, this._parameterWatchers);
1✔
1151
    watcher.destroy();
1✔
1152
  }
1153

1154
  /**
1155
   * Destroy a Timer.
1156
   * @param {Timer} timer - The Timer to be destroyed.
1157
   * @return {undefined}
1158
   */
1159
  destroyTimer(timer) {
1160
    if (!(timer instanceof Timer)) {
8✔
1161
      throw new TypeValidationError('timer', timer, 'Timer instance', {
2✔
1162
        nodeName: this.name(),
1163
      });
1164
    }
1165
    this._destroyEntity(timer, this._timers);
6✔
1166
  }
1167

1168
  /**
1169
   * Destroy a guard condition.
1170
   * @param {GuardCondition} guard - The guard condition to be destroyed.
1171
   * @return {undefined}
1172
   */
1173
  destroyGuardCondition(guard) {
1174
    if (!(guard instanceof GuardCondition)) {
3!
1175
      throw new TypeValidationError('guard', guard, 'GuardCondition instance', {
×
1176
        nodeName: this.name(),
1177
      });
1178
    }
1179
    this._destroyEntity(guard, this._guards);
3✔
1180
  }
1181

1182
  /**
1183
   * Get the name of the node.
1184
   * @return {string}
1185
   */
1186
  name() {
1187
    return rclnodejs.getNodeName(this.handle);
4,634✔
1188
  }
1189

1190
  /**
1191
   * Get the namespace of the node.
1192
   * @return {string}
1193
   */
1194
  namespace() {
1195
    return rclnodejs.getNamespace(this.handle);
4,374✔
1196
  }
1197

1198
  /**
1199
   * Get the context in which this node was created.
1200
   * @return {Context}
1201
   */
1202
  get context() {
1203
    return this._context;
6,042✔
1204
  }
1205

1206
  /**
1207
   * Get the nodes logger.
1208
   * @returns {Logger} - The logger for the node.
1209
   */
1210
  getLogger() {
1211
    return this._logger;
141✔
1212
  }
1213

1214
  /**
1215
   * Get the clock used by the node.
1216
   * @returns {Clock} - The nodes clock.
1217
   */
1218
  getClock() {
1219
    return this._clock;
86✔
1220
  }
1221

1222
  /**
1223
   * Get the current time using the node's clock.
1224
   * @returns {Timer} - The current time.
1225
   */
1226
  now() {
1227
    return this.getClock().now();
2✔
1228
  }
1229

1230
  /**
1231
   * Get the list of published topics discovered by the provided node for the remote node name.
1232
   * @param {string} nodeName - The name of the node.
1233
   * @param {string} namespace - The name of the namespace.
1234
   * @param {boolean} noDemangle - If true topic names and types returned will not be demangled, default: false.
1235
   * @return {Array<{name: string, types: Array<string>}>} - An array of the names and types.
1236
   */
1237
  getPublisherNamesAndTypesByNode(nodeName, namespace, noDemangle = false) {
2✔
1238
    return rclnodejs.getPublisherNamesAndTypesByNode(
2✔
1239
      this.handle,
1240
      nodeName,
1241
      namespace,
1242
      noDemangle
1243
    );
1244
  }
1245

1246
  /**
1247
   * Get the list of published topics discovered by the provided node for the remote node name.
1248
   * @param {string} nodeName - The name of the node.
1249
   * @param {string} namespace - The name of the namespace.
1250
   * @param {boolean} noDemangle - If true topic names and types returned will not be demangled, default: false.
1251
   * @return {Array<{name: string, types: Array<string>}>} - An array of the names and types.
1252
   */
1253
  getSubscriptionNamesAndTypesByNode(nodeName, namespace, noDemangle = false) {
×
1254
    return rclnodejs.getSubscriptionNamesAndTypesByNode(
×
1255
      this.handle,
1256
      nodeName,
1257
      namespace,
1258
      noDemangle
1259
    );
1260
  }
1261

1262
  /**
1263
   * Get service names and types for which a remote node has servers.
1264
   * @param {string} nodeName - The name of the node.
1265
   * @param {string} namespace - The name of the namespace.
1266
   * @return {Array<{name: string, types: Array<string>}>} - An array of the names and types.
1267
   */
1268
  getServiceNamesAndTypesByNode(nodeName, namespace) {
1269
    return rclnodejs.getServiceNamesAndTypesByNode(
×
1270
      this.handle,
1271
      nodeName,
1272
      namespace
1273
    );
1274
  }
1275

1276
  /**
1277
   * Get service names and types for which a remote node has clients.
1278
   * @param {string} nodeName - The name of the node.
1279
   * @param {string} namespace - The name of the namespace.
1280
   * @return {Array<{name: string, types: Array<string>}>} - An array of the names and types.
1281
   */
1282
  getClientNamesAndTypesByNode(nodeName, namespace) {
1283
    return rclnodejs.getClientNamesAndTypesByNode(
×
1284
      this.handle,
1285
      nodeName,
1286
      namespace
1287
    );
1288
  }
1289

1290
  /**
1291
   * Get the list of topics discovered by the provided node.
1292
   * @param {boolean} noDemangle - If true topic names and types returned will not be demangled, default: false.
1293
   * @return {Array<{name: string, types: Array<string>}>} - An array of the names and types.
1294
   */
1295
  getTopicNamesAndTypes(noDemangle = false) {
×
1296
    return rclnodejs.getTopicNamesAndTypes(this.handle, noDemangle);
×
1297
  }
1298

1299
  /**
1300
   * Get the list of services discovered by the provided node.
1301
   * @return {Array<{name: string, types: Array<string>}>} - An array of the names and types.
1302
   */
1303
  getServiceNamesAndTypes() {
1304
    return rclnodejs.getServiceNamesAndTypes(this.handle);
2✔
1305
  }
1306

1307
  /**
1308
   * Return a list of publishers on a given topic.
1309
   *
1310
   * The returned parameter is a list of TopicEndpointInfo objects, where each will contain
1311
   * the node name, node namespace, topic type, topic endpoint's GID, and its QoS profile.
1312
   *
1313
   * When the `no_mangle` parameter is `true`, the provided `topic` should be a valid
1314
   * topic name for the middleware (useful when combining ROS with native middleware (e.g. DDS)
1315
   * apps).  When the `no_mangle` parameter is `false`, the provided `topic` should
1316
   * follow ROS topic name conventions.
1317
   *
1318
   * `topic` may be a relative, private, or fully qualified topic name.
1319
   *  A relative or private topic will be expanded using this node's namespace and name.
1320
   *  The queried `topic` is not remapped.
1321
   *
1322
   * @param {string} topic - The topic on which to find the publishers.
1323
   * @param {boolean} [noDemangle=false] - If `true`, `topic` needs to be a valid middleware topic
1324
   *                               name, otherwise it should be a valid ROS topic name. Defaults to `false`.
1325
   * @returns {Array} - list of publishers
1326
   */
1327
  getPublishersInfoByTopic(topic, noDemangle = false) {
1✔
1328
    return rclnodejs.getPublishersInfoByTopic(
4✔
1329
      this.handle,
1330
      this._getValidatedTopic(topic, noDemangle),
1331
      noDemangle
1332
    );
1333
  }
1334

1335
  /**
1336
   * Return a list of subscriptions on a given topic.
1337
   *
1338
   * The returned parameter is a list of TopicEndpointInfo objects, where each will contain
1339
   * the node name, node namespace, topic type, topic endpoint's GID, and its QoS profile.
1340
   *
1341
   * When the `no_mangle` parameter is `true`, the provided `topic` should be a valid
1342
   * topic name for the middleware (useful when combining ROS with native middleware (e.g. DDS)
1343
   * apps).  When the `no_mangle` parameter is `false`, the provided `topic` should
1344
   * follow ROS topic name conventions.
1345
   *
1346
   * `topic` may be a relative, private, or fully qualified topic name.
1347
   *  A relative or private topic will be expanded using this node's namespace and name.
1348
   *  The queried `topic` is not remapped.
1349
   *
1350
   * @param {string} topic - The topic on which to find the subscriptions.
1351
   * @param {boolean} [noDemangle=false] -  If `true`, `topic` needs to be a valid middleware topic
1352
                                    name, otherwise it should be a valid ROS topic name. Defaults to `false`.
1353
   * @returns {Array} - list of subscriptions
1354
   */
1355
  getSubscriptionsInfoByTopic(topic, noDemangle = false) {
×
1356
    return rclnodejs.getSubscriptionsInfoByTopic(
2✔
1357
      this.handle,
1358
      this._getValidatedTopic(topic, noDemangle),
1359
      noDemangle
1360
    );
1361
  }
1362

1363
  /**
1364
   * Return a list of clients on a given service.
1365
   *
1366
   * The returned parameter is a list of ServiceEndpointInfo objects, where each will contain
1367
   * the node name, node namespace, service type, service endpoint's GID, and its QoS profile.
1368
   *
1369
   * When the `no_mangle` parameter is `true`, the provided `service` should be a valid
1370
   * service name for the middleware (useful when combining ROS with native middleware (e.g. DDS)
1371
   * apps).  When the `no_mangle` parameter is `false`, the provided `service` should
1372
   * follow ROS service name conventions.
1373
   *
1374
   * `service` may be a relative, private, or fully qualified service name.
1375
   *  A relative or private service will be expanded using this node's namespace and name.
1376
   *  The queried `service` is not remapped.
1377
   *
1378
   * @param {string} service - The service on which to find the clients.
1379
   * @param {boolean} [noDemangle=false] - If `true`, `service` needs to be a valid middleware service
1380
   *                               name, otherwise it should be a valid ROS service name. Defaults to `false`.
1381
   * @returns {Array} - list of clients
1382
   */
1383
  getClientsInfoByService(service, noDemangle = false) {
×
1384
    if (DistroUtils.getDistroId() < DistroUtils.DistroId.ROLLING) {
2!
UNCOV
1385
      console.warn(
×
1386
        'getClientsInfoByService is not supported by this version of ROS 2'
1387
      );
UNCOV
1388
      return null;
×
1389
    }
1390
    return rclnodejs.getClientsInfoByService(
2✔
1391
      this.handle,
1392
      this._getValidatedServiceName(service, noDemangle),
1393
      noDemangle
1394
    );
1395
  }
1396

1397
  /**
1398
   * Return a list of servers on a given service.
1399
   *
1400
   * The returned parameter is a list of ServiceEndpointInfo objects, where each will contain
1401
   * the node name, node namespace, service type, service endpoint's GID, and its QoS profile.
1402
   *
1403
   * When the `no_mangle` parameter is `true`, the provided `service` should be a valid
1404
   * service name for the middleware (useful when combining ROS with native middleware (e.g. DDS)
1405
   * apps).  When the `no_mangle` parameter is `false`, the provided `service` should
1406
   * follow ROS service name conventions.
1407
   *
1408
   * `service` may be a relative, private, or fully qualified service name.
1409
   *  A relative or private service will be expanded using this node's namespace and name.
1410
   *  The queried `service` is not remapped.
1411
   *
1412
   * @param {string} service - The service on which to find the servers.
1413
   * @param {boolean} [noDemangle=false] - If `true`, `service` needs to be a valid middleware service
1414
   *                               name, otherwise it should be a valid ROS service name. Defaults to `false`.
1415
   * @returns {Array} - list of servers
1416
   */
1417
  getServersInfoByService(service, noDemangle = false) {
×
1418
    if (DistroUtils.getDistroId() < DistroUtils.DistroId.ROLLING) {
2!
UNCOV
1419
      console.warn(
×
1420
        'getServersInfoByService is not supported by this version of ROS 2'
1421
      );
UNCOV
1422
      return null;
×
1423
    }
1424
    return rclnodejs.getServersInfoByService(
2✔
1425
      this.handle,
1426
      this._getValidatedServiceName(service, noDemangle),
1427
      noDemangle
1428
    );
1429
  }
1430

1431
  /**
1432
   * Get the list of nodes discovered by the provided node.
1433
   * @return {Array<string>} - An array of the names.
1434
   */
1435
  getNodeNames() {
1436
    return this.getNodeNamesAndNamespaces().map((item) => item.name);
41✔
1437
  }
1438

1439
  /**
1440
   * Get the list of nodes and their namespaces discovered by the provided node.
1441
   * @return {Array<{name: string, namespace: string}>} An array of the names and namespaces.
1442
   */
1443
  getNodeNamesAndNamespaces() {
1444
    return rclnodejs.getNodeNames(this.handle, /*getEnclaves=*/ false);
17✔
1445
  }
1446

1447
  /**
1448
   * Get the list of nodes and their namespaces with enclaves discovered by the provided node.
1449
   * @return {Array<{name: string, namespace: string, enclave: string}>} An array of the names, namespaces and enclaves.
1450
   */
1451
  getNodeNamesAndNamespacesWithEnclaves() {
1452
    return rclnodejs.getNodeNames(this.handle, /*getEnclaves=*/ true);
1✔
1453
  }
1454

1455
  /**
1456
   * Return the number of publishers on a given topic.
1457
   * @param {string} topic - The name of the topic.
1458
   * @returns {number} - Number of publishers on the given topic.
1459
   */
1460
  countPublishers(topic) {
1461
    let expandedTopic = rclnodejs.expandTopicName(
6✔
1462
      topic,
1463
      this.name(),
1464
      this.namespace()
1465
    );
1466
    rclnodejs.validateTopicName(expandedTopic);
6✔
1467

1468
    return rclnodejs.countPublishers(this.handle, expandedTopic);
6✔
1469
  }
1470

1471
  /**
1472
   * Return the number of subscribers on a given topic.
1473
   * @param {string} topic - The name of the topic.
1474
   * @returns {number} - Number of subscribers on the given topic.
1475
   */
1476
  countSubscribers(topic) {
1477
    let expandedTopic = rclnodejs.expandTopicName(
6✔
1478
      topic,
1479
      this.name(),
1480
      this.namespace()
1481
    );
1482
    rclnodejs.validateTopicName(expandedTopic);
6✔
1483

1484
    return rclnodejs.countSubscribers(this.handle, expandedTopic);
6✔
1485
  }
1486

1487
  /**
1488
   * Get the number of clients on a given service name.
1489
   * @param {string} serviceName - the service name
1490
   * @returns {Number}
1491
   */
1492
  countClients(serviceName) {
1493
    if (DistroUtils.getDistroId() <= DistroUtils.getDistroId('humble')) {
2!
UNCOV
1494
      console.warn('countClients is not supported by this version of ROS 2');
×
UNCOV
1495
      return null;
×
1496
    }
1497
    return rclnodejs.countClients(this.handle, serviceName);
2✔
1498
  }
1499

1500
  /**
1501
   * Get the number of services on a given service name.
1502
   * @param {string} serviceName - the service name
1503
   * @returns {Number}
1504
   */
1505
  countServices(serviceName) {
1506
    if (DistroUtils.getDistroId() <= DistroUtils.getDistroId('humble')) {
1!
UNCOV
1507
      console.warn('countServices is not supported by this version of ROS 2');
×
UNCOV
1508
      return null;
×
1509
    }
1510
    return rclnodejs.countServices(this.handle, serviceName);
1✔
1511
  }
1512

1513
  /**
1514
   * Get the list of parameter-overrides found on the commandline and
1515
   * in the NodeOptions.parameter_overrides property.
1516
   *
1517
   * @return {Array<Parameter>} - An array of Parameters.
1518
   */
1519
  getParameterOverrides() {
1520
    return Array.from(this._parameterOverrides.values());
8✔
1521
  }
1522

1523
  /**
1524
   * Declare a parameter.
1525
   *
1526
   * Internally, register a parameter and it's descriptor.
1527
   * If a parameter-override exists, it's value will replace that of the parameter
1528
   * unless ignoreOverride is true.
1529
   * If the descriptor is undefined, then a ParameterDescriptor will be inferred
1530
   * from the parameter's state.
1531
   *
1532
   * If a parameter by the same name has already been declared then an Error is thrown.
1533
   * A parameter must be undeclared before attempting to redeclare it.
1534
   *
1535
   * @param {Parameter} parameter - Parameter to declare.
1536
   * @param {ParameterDescriptor} [descriptor] - Optional descriptor for parameter.
1537
   * @param {boolean} [ignoreOverride] - When true disregard any parameter-override that may be present.
1538
   * @return {Parameter} - The newly declared parameter.
1539
   */
1540
  declareParameter(parameter, descriptor, ignoreOverride = false) {
2,144✔
1541
    const parameters = this.declareParameters(
2,145✔
1542
      [parameter],
1543
      descriptor ? [descriptor] : [],
2,145✔
1544
      ignoreOverride
1545
    );
1546
    return parameters.length == 1 ? parameters[0] : null;
2,145!
1547
  }
1548

1549
  /**
1550
   * Declare a list of parameters.
1551
   *
1552
   * Internally register parameters with their corresponding descriptor one by one
1553
   * in the order they are provided. This is an atomic operation. If an error
1554
   * occurs the process halts and no further parameters are declared.
1555
   * Parameters that have already been processed are undeclared.
1556
   *
1557
   * While descriptors is an optional parameter, when provided there must be
1558
   * a descriptor for each parameter; otherwise an Error is thrown.
1559
   * If descriptors is not provided then a descriptor will be inferred
1560
   * from each parameter's state.
1561
   *
1562
   * When a parameter-override is available, the parameter's value
1563
   * will be replaced with that of the parameter-override unless ignoreOverrides
1564
   * is true.
1565
   *
1566
   * If a parameter by the same name has already been declared then an Error is thrown.
1567
   * A parameter must be undeclared before attempting to redeclare it.
1568
   *
1569
   * Prior to declaring the parameters each SetParameterEventCallback registered
1570
   * using setOnParameterEventCallback() is called in succession with the parameters
1571
   * list. Any SetParameterEventCallback that retuns does not return a successful
1572
   * result will cause the entire operation to terminate with no changes to the
1573
   * parameters. When all SetParameterEventCallbacks return successful then the
1574
   * list of parameters is updated.
1575
   *
1576
   * @param {Parameter[]} parameters - The parameters to declare.
1577
   * @param {ParameterDescriptor[]} [descriptors] - Optional descriptors,
1578
   *    a 1-1 correspondence with parameters.
1579
   * @param {boolean} ignoreOverrides - When true, parameter-overrides are
1580
   *    not considered, i.e.,ignored.
1581
   * @return {Parameter[]} - The declared parameters.
1582
   */
1583
  declareParameters(parameters, descriptors = [], ignoreOverrides = false) {
×
1584
    if (!Array.isArray(parameters)) {
2,145!
UNCOV
1585
      throw new TypeValidationError('parameters', parameters, 'Array', {
×
1586
        nodeName: this.name(),
1587
      });
1588
    }
1589
    if (!Array.isArray(descriptors)) {
2,145!
UNCOV
1590
      throw new TypeValidationError('descriptors', descriptors, 'Array', {
×
1591
        nodeName: this.name(),
1592
      });
1593
    }
1594
    if (descriptors.length > 0 && parameters.length !== descriptors.length) {
2,145!
UNCOV
1595
      throw new ValidationError(
×
1596
        'Each parameter must have a corresponding ParameterDescriptor',
1597
        {
1598
          code: 'PARAMETER_DESCRIPTOR_MISMATCH',
1599
          argumentName: 'descriptors',
1600
          providedValue: descriptors.length,
1601
          expectedType: `Array with length ${parameters.length}`,
1602
          nodeName: this.name(),
1603
        }
1604
      );
1605
    }
1606

1607
    const declaredDescriptors = [];
2,145✔
1608
    const declaredParameters = [];
2,145✔
1609
    const declaredParameterCollisions = [];
2,145✔
1610
    for (let i = 0; i < parameters.length; i++) {
2,145✔
1611
      let parameter =
1612
        !ignoreOverrides && this._parameterOverrides.has(parameters[i].name)
2,145✔
1613
          ? this._parameterOverrides.get(parameters[i].name)
1614
          : parameters[i];
1615

1616
      // stop processing parameters that have already been declared
1617
      if (this._parameters.has(parameter.name)) {
2,145!
UNCOV
1618
        declaredParameterCollisions.push(parameter);
×
UNCOV
1619
        continue;
×
1620
      }
1621

1622
      // create descriptor for parameter if not provided
1623
      let descriptor =
1624
        descriptors.length > 0
2,145✔
1625
          ? descriptors[i]
1626
          : ParameterDescriptor.fromParameter(parameter);
1627

1628
      descriptor.validate();
2,145✔
1629

1630
      declaredDescriptors.push(descriptor);
2,145✔
1631
      declaredParameters.push(parameter);
2,145✔
1632
    }
1633

1634
    if (declaredParameterCollisions.length > 0) {
2,145!
1635
      const errorMsg =
UNCOV
1636
        declaredParameterCollisions.length == 1
×
1637
          ? `Parameter(${declaredParameterCollisions[0]}) already declared.`
1638
          : `Multiple parameters already declared, e.g., Parameter(${declaredParameterCollisions[0]}).`;
UNCOV
1639
      throw new Error(errorMsg);
×
1640
    }
1641

1642
    // register descriptor
1643
    for (const descriptor of declaredDescriptors) {
2,145✔
1644
      this._parameterDescriptors.set(descriptor.name, descriptor);
2,145✔
1645
    }
1646

1647
    const result = this._setParametersAtomically(declaredParameters, true);
2,145✔
1648
    if (!result.successful) {
2,145!
1649
      // unregister descriptors
UNCOV
1650
      for (const descriptor of declaredDescriptors) {
×
UNCOV
1651
        this._parameterDescriptors.delete(descriptor.name);
×
1652
      }
1653

UNCOV
1654
      throw new Error(result.reason);
×
1655
    }
1656

1657
    return this.getParameters(declaredParameters.map((param) => param.name));
2,145✔
1658
  }
1659

1660
  /**
1661
   * Undeclare a parameter.
1662
   *
1663
   * Readonly parameters can not be undeclared or updated.
1664
   * @param {string} name - Name of parameter to undeclare.
1665
   * @return {undefined} -
1666
   */
1667
  undeclareParameter(name) {
1668
    if (!this.hasParameter(name)) return;
1!
1669

1670
    const descriptor = this.getParameterDescriptor(name);
1✔
1671
    if (descriptor.readOnly) {
1!
UNCOV
1672
      throw new Error(
×
1673
        `${name} parameter is read-only and can not be undeclared`
1674
      );
1675
    }
1676

1677
    this._parameters.delete(name);
1✔
1678
    this._parameterDescriptors.delete(name);
1✔
1679
  }
1680

1681
  /**
1682
   * Determine if a parameter has been declared.
1683
   * @param {string} name - name of parameter
1684
   * @returns {boolean} - Return true if parameter is declared; false otherwise.
1685
   */
1686
  hasParameter(name) {
1687
    return this._parameters.has(name);
5,323✔
1688
  }
1689

1690
  /**
1691
   * Get a declared parameter by name.
1692
   *
1693
   * If unable to locate a declared parameter then a
1694
   * parameter with type == PARAMETER_NOT_SET is returned.
1695
   *
1696
   * @param {string} name - The name of the parameter.
1697
   * @return {Parameter} - The parameter.
1698
   */
1699
  getParameter(name) {
1700
    return this.getParameters([name])[0];
1,574✔
1701
  }
1702

1703
  /**
1704
   * Get a list of parameters.
1705
   *
1706
   * Find and return the declared parameters.
1707
   * If no names are provided return all declared parameters.
1708
   *
1709
   * If unable to locate a declared parameter then a
1710
   * parameter with type == PARAMETER_NOT_SET is returned in
1711
   * it's place.
1712
   *
1713
   * @param {string[]} [names] - The names of the declared parameters
1714
   *    to find or null indicating to return all declared parameters.
1715
   * @return {Parameter[]} - The parameters.
1716
   */
1717
  getParameters(names = []) {
12✔
1718
    let params = [];
3,757✔
1719

1720
    if (names.length == 0) {
3,757✔
1721
      // get all parameters
1722
      params = [...this._parameters.values()];
12✔
1723
      return params;
12✔
1724
    }
1725

1726
    for (const name of names) {
3,745✔
1727
      const param = this.hasParameter(name)
3,752✔
1728
        ? this._parameters.get(name)
1729
        : new Parameter(name, ParameterType.PARAMETER_NOT_SET);
1730

1731
      params.push(param);
3,752✔
1732
    }
1733

1734
    return params;
3,745✔
1735
  }
1736

1737
  /**
1738
   * Get the types of given parameters.
1739
   *
1740
   * Return the types of given parameters.
1741
   *
1742
   * @param {string[]} [names] - The names of the declared parameters.
1743
   * @return {Uint8Array} - The types.
1744
   */
1745
  getParameterTypes(names = []) {
×
1746
    let types = [];
3✔
1747

1748
    for (const name of names) {
3✔
1749
      const descriptor = this._parameterDescriptors.get(name);
7✔
1750
      if (descriptor) {
7!
1751
        types.push(descriptor.type);
7✔
1752
      }
1753
    }
1754
    return types;
3✔
1755
  }
1756

1757
  /**
1758
   * Get the names of all declared parameters.
1759
   *
1760
   * @return {Array<string>} - The declared parameter names or empty array if
1761
   *    no parameters have been declared.
1762
   */
1763
  getParameterNames() {
1764
    return this.getParameters().map((param) => param.name);
53✔
1765
  }
1766

1767
  /**
1768
   * Determine if a parameter descriptor exists.
1769
   *
1770
   * @param {string} name - The name of a descriptor to for.
1771
   * @return {boolean} - true if a descriptor has been declared; otherwise false.
1772
   */
1773
  hasParameterDescriptor(name) {
1774
    return !!this.getParameterDescriptor(name);
2,170✔
1775
  }
1776

1777
  /**
1778
   * Get a declared parameter descriptor by name.
1779
   *
1780
   * If unable to locate a declared parameter descriptor then a
1781
   * descriptor with type == PARAMETER_NOT_SET is returned.
1782
   *
1783
   * @param {string} name - The name of the parameter descriptor to find.
1784
   * @return {ParameterDescriptor} - The parameter descriptor.
1785
   */
1786
  getParameterDescriptor(name) {
1787
    return this.getParameterDescriptors([name])[0];
4,341✔
1788
  }
1789

1790
  /**
1791
   * Find a list of declared ParameterDescriptors.
1792
   *
1793
   * If no names are provided return all declared descriptors.
1794
   *
1795
   * If unable to locate a declared descriptor then a
1796
   * descriptor with type == PARAMETER_NOT_SET is returned in
1797
   * it's place.
1798
   *
1799
   * @param {string[]} [names] - The names of the declared parameter
1800
   *    descriptors to find or null indicating to return all declared descriptors.
1801
   * @return {ParameterDescriptor[]} - The parameter descriptors.
1802
   */
1803
  getParameterDescriptors(names = []) {
×
1804
    let descriptors = [];
4,344✔
1805

1806
    if (names.length == 0) {
4,344!
1807
      // get all parameters
UNCOV
1808
      descriptors = [...this._parameterDescriptors.values()];
×
UNCOV
1809
      return descriptors;
×
1810
    }
1811

1812
    for (const name of names) {
4,344✔
1813
      let descriptor = this._parameterDescriptors.get(name);
4,346✔
1814
      if (!descriptor) {
4,346!
UNCOV
1815
        descriptor = new ParameterDescriptor(
×
1816
          name,
1817
          ParameterType.PARAMETER_NOT_SET
1818
        );
1819
      }
1820
      descriptors.push(descriptor);
4,346✔
1821
    }
1822

1823
    return descriptors;
4,344✔
1824
  }
1825

1826
  /**
1827
   * Replace a declared parameter.
1828
   *
1829
   * The parameter being replaced must be a declared parameter who's descriptor
1830
   * is not readOnly; otherwise an Error is thrown.
1831
   *
1832
   * @param {Parameter} parameter - The new parameter.
1833
   * @return {rclnodejs.rcl_interfaces.msg.SetParameterResult} - The result of the operation.
1834
   */
1835
  setParameter(parameter) {
1836
    const results = this.setParameters([parameter]);
10✔
1837
    return results[0];
10✔
1838
  }
1839

1840
  /**
1841
   * Replace a list of declared parameters.
1842
   *
1843
   * Declared parameters are replaced in the order they are provided and
1844
   * a ParameterEvent is published for each individual parameter change.
1845
   *
1846
   * Prior to setting the parameters each SetParameterEventCallback registered
1847
   * using setOnParameterEventCallback() is called in succession with the parameters
1848
   * list. Any SetParameterEventCallback that retuns does not return a successful
1849
   * result will cause the entire operation to terminate with no changes to the
1850
   * parameters. When all SetParameterEventCallbacks return successful then the
1851
   * list of parameters is updated.
1852
   *
1853
   * If an error occurs, the process is stopped and returned. Parameters
1854
   * set before an error remain unchanged.
1855
   *
1856
   * @param {Parameter[]} parameters - The parameters to set.
1857
   * @return {rclnodejs.rcl_interfaces.msg.SetParameterResult[]} - A list of SetParameterResult, one for each parameter that was set.
1858
   */
1859
  setParameters(parameters = []) {
×
1860
    return parameters.map((parameter) =>
21✔
1861
      this.setParametersAtomically([parameter])
24✔
1862
    );
1863
  }
1864

1865
  /**
1866
   * Repalce a list of declared parameters atomically.
1867
   *
1868
   * Declared parameters are replaced in the order they are provided.
1869
   * A single ParameterEvent is published collectively for all changed
1870
   * parameters.
1871
   *
1872
   * Prior to setting the parameters each SetParameterEventCallback registered
1873
   * using setOnParameterEventCallback() is called in succession with the parameters
1874
   * list. Any SetParameterEventCallback that retuns does not return a successful
1875
   * result will cause the entire operation to terminate with no changes to the
1876
   * parameters. When all SetParameterEventCallbacks return successful then the
1877
   * list of parameters is updated.d
1878
   *
1879
   * If an error occurs, the process stops immediately. All parameters updated to
1880
   * the point of the error are reverted to their previous state.
1881
   *
1882
   * @param {Parameter[]} parameters - The parameters to set.
1883
   * @return {rclnodejs.rcl_interfaces.msg.SetParameterResult} - describes the result of setting 1 or more parameters.
1884
   */
1885
  setParametersAtomically(parameters = []) {
×
1886
    return this._setParametersAtomically(parameters);
25✔
1887
  }
1888

1889
  /**
1890
   * Internal method for updating parameters atomically.
1891
   *
1892
   * Prior to setting the parameters each SetParameterEventCallback registered
1893
   * using setOnParameterEventCallback() is called in succession with the parameters
1894
   * list. Any SetParameterEventCallback that retuns does not return a successful
1895
   * result will cause the entire operation to terminate with no changes to the
1896
   * parameters. When all SetParameterEventCallbacks return successful then the
1897
   * list of parameters is updated.
1898
   *
1899
   * @param {Paramerter[]} parameters - The parameters to update.
1900
   * @param {boolean} declareParameterMode - When true parameters are being declared;
1901
   *    otherwise they are being changed.
1902
   * @return {SetParameterResult} - A single collective result.
1903
   */
1904
  _setParametersAtomically(parameters = [], declareParameterMode = false) {
25!
1905
    let result = this._validateParameters(parameters, declareParameterMode);
2,170✔
1906
    if (!result.successful) {
2,170!
UNCOV
1907
      return result;
×
1908
    }
1909

1910
    // give all SetParametersCallbacks a chance to veto this change
1911
    for (const callback of this._setParametersCallbacks) {
2,170✔
1912
      result = callback(parameters);
1,395✔
1913
      if (!result.successful) {
1,395✔
1914
        // a callback has vetoed a parameter change
1915
        return result;
1✔
1916
      }
1917
    }
1918

1919
    // collectively track updates to parameters for use
1920
    // when publishing a ParameterEvent
1921
    const newParameters = [];
2,169✔
1922
    const changedParameters = [];
2,169✔
1923
    const deletedParameters = [];
2,169✔
1924

1925
    for (const parameter of parameters) {
2,169✔
1926
      if (parameter.type == ParameterType.PARAMETER_NOT_SET) {
2,169✔
1927
        this.undeclareParameter(parameter.name);
1✔
1928
        deletedParameters.push(parameter);
1✔
1929
      } else {
1930
        this._parameters.set(parameter.name, parameter);
2,168✔
1931
        if (declareParameterMode) {
2,168✔
1932
          newParameters.push(parameter);
2,145✔
1933
        } else {
1934
          changedParameters.push(parameter);
23✔
1935
        }
1936
      }
1937
    }
1938

1939
    // create ParameterEvent
1940
    const parameterEvent = new (loader.loadInterface(
2,169✔
1941
      PARAMETER_EVENT_MSG_TYPE
1942
    ))();
1943

1944
    const { seconds, nanoseconds } = this._clock.now().secondsAndNanoseconds;
2,169✔
1945
    parameterEvent.stamp = {
2,169✔
1946
      sec: Number(seconds),
1947
      nanosec: Number(nanoseconds),
1948
    };
1949

1950
    parameterEvent.node =
2,169✔
1951
      this.namespace() === '/'
2,169✔
1952
        ? this.namespace() + this.name()
1953
        : this.namespace() + '/' + this.name();
1954

1955
    if (newParameters.length > 0) {
2,169✔
1956
      parameterEvent['new_parameters'] = newParameters.map((parameter) =>
2,145✔
1957
        parameter.toParameterMessage()
2,145✔
1958
      );
1959
    }
1960
    if (changedParameters.length > 0) {
2,169✔
1961
      parameterEvent['changed_parameters'] = changedParameters.map(
23✔
1962
        (parameter) => parameter.toParameterMessage()
23✔
1963
      );
1964
    }
1965
    if (deletedParameters.length > 0) {
2,169✔
1966
      parameterEvent['deleted_parameters'] = deletedParameters.map(
1✔
1967
        (parameter) => parameter.toParameterMessage()
1✔
1968
      );
1969
    }
1970

1971
    // Publish ParameterEvent.
1972
    this._parameterEventPublisher.publish(parameterEvent);
2,169✔
1973

1974
    return {
2,169✔
1975
      successful: true,
1976
      reason: '',
1977
    };
1978
  }
1979

1980
  /**
1981
   * This callback is called when declaring a parameter or setting a parameter.
1982
   * The callback is provided a list of parameters and returns a SetParameterResult
1983
   * to indicate approval or veto of the operation.
1984
   *
1985
   * @callback SetParametersCallback
1986
   * @param {Parameter[]} parameters - The message published
1987
   * @returns {rcl_interfaces.msg.SetParameterResult} -
1988
   *
1989
   * @see [Node.addOnSetParametersCallback]{@link Node#addOnSetParametersCallback}
1990
   * @see [Node.removeOnSetParametersCallback]{@link Node#removeOnSetParametersCallback}
1991
   */
1992

1993
  /**
1994
   * Add a callback to the front of the list of callbacks invoked for parameter declaration
1995
   * and setting. No checks are made for duplicate callbacks.
1996
   *
1997
   * @param {SetParametersCallback} callback - The callback to add.
1998
   * @returns {undefined}
1999
   */
2000
  addOnSetParametersCallback(callback) {
2001
    this._setParametersCallbacks.unshift(callback);
789✔
2002
  }
2003

2004
  /**
2005
   * Remove a callback from the list of SetParametersCallbacks.
2006
   * If the callback is not found the process is a nop.
2007
   *
2008
   * @param {SetParametersCallback} callback - The callback to be removed
2009
   * @returns {undefined}
2010
   */
2011
  removeOnSetParametersCallback(callback) {
2012
    const idx = this._setParametersCallbacks.indexOf(callback);
2✔
2013
    if (idx > -1) {
2!
2014
      this._setParametersCallbacks.splice(idx, 1);
2✔
2015
    }
2016
  }
2017

2018
  /**
2019
   * Get the fully qualified name of the node.
2020
   *
2021
   * @returns {string} - String containing the fully qualified name of the node.
2022
   */
2023
  getFullyQualifiedName() {
2024
    return rclnodejs.getFullyQualifiedName(this.handle);
1✔
2025
  }
2026

2027
  /**
2028
   * Get the RMW implementation identifier
2029
   * @returns {string} - The RMW implementation identifier.
2030
   */
2031
  getRMWImplementationIdentifier() {
2032
    return rclnodejs.getRMWImplementationIdentifier();
1✔
2033
  }
2034

2035
  /**
2036
   * Return a topic name expanded and remapped.
2037
   * @param {string} topicName - Topic name to be expanded and remapped.
2038
   * @param {boolean} [onlyExpand=false] - If `true`, remapping rules won't be applied.
2039
   * @returns {string} - A fully qualified topic name.
2040
   */
2041
  resolveTopicName(topicName, onlyExpand = false) {
2✔
2042
    if (typeof topicName !== 'string') {
3!
UNCOV
2043
      throw new TypeValidationError('topicName', topicName, 'string', {
×
2044
        nodeName: this.name(),
2045
      });
2046
    }
2047
    return rclnodejs.resolveName(
3✔
2048
      this.handle,
2049
      topicName,
2050
      onlyExpand,
2051
      /*isService=*/ false
2052
    );
2053
  }
2054

2055
  /**
2056
   * Return a service name expanded and remapped.
2057
   * @param {string} service - Service name to be expanded and remapped.
2058
   * @param {boolean} [onlyExpand=false] - If `true`, remapping rules won't be applied.
2059
   * @returns {string} - A fully qualified service name.
2060
   */
2061
  resolveServiceName(service, onlyExpand = false) {
6✔
2062
    if (typeof service !== 'string') {
7!
UNCOV
2063
      throw new TypeValidationError('service', service, 'string', {
×
2064
        nodeName: this.name(),
2065
      });
2066
    }
2067
    return rclnodejs.resolveName(
7✔
2068
      this.handle,
2069
      service,
2070
      onlyExpand,
2071
      /*isService=*/ true
2072
    );
2073
  }
2074

2075
  // returns on 1st error or result {successful, reason}
2076
  _validateParameters(parameters = [], declareParameterMode = false) {
×
2077
    for (const parameter of parameters) {
2,170✔
2078
      // detect invalid parameter
2079
      try {
2,170✔
2080
        parameter.validate();
2,170✔
2081
      } catch {
UNCOV
2082
        return {
×
2083
          successful: false,
2084
          reason: `Invalid ${parameter.name}`,
2085
        };
2086
      }
2087

2088
      // detect undeclared parameter
2089
      if (!this.hasParameterDescriptor(parameter.name)) {
2,170!
UNCOV
2090
        return {
×
2091
          successful: false,
2092
          reason: `Parameter ${parameter.name} has not been declared`,
2093
        };
2094
      }
2095

2096
      // detect readonly parameter that can not be updated
2097
      const descriptor = this.getParameterDescriptor(parameter.name);
2,170✔
2098
      if (!declareParameterMode && descriptor.readOnly) {
2,170!
UNCOV
2099
        return {
×
2100
          successful: false,
2101
          reason: `Parameter ${parameter.name} is readonly`,
2102
        };
2103
      }
2104

2105
      // validate parameter against descriptor if not an undeclare action
2106
      if (parameter.type != ParameterType.PARAMETER_NOT_SET) {
2,170✔
2107
        try {
2,169✔
2108
          descriptor.validateParameter(parameter);
2,169✔
2109
        } catch {
UNCOV
2110
          return {
×
2111
            successful: false,
2112
            reason: `Parameter ${parameter.name} does not  readonly`,
2113
          };
2114
        }
2115
      }
2116
    }
2117

2118
    return {
2,170✔
2119
      successful: true,
2120
      reason: null,
2121
    };
2122
  }
2123

2124
  // Get a Map(nodeName->Parameter[]) of CLI parameter args that
2125
  // apply to 'this' node, .e.g., -p mynode:foo:=bar -p hello:=world
2126
  _getNativeParameterOverrides() {
2127
    const overrides = new Map();
781✔
2128

2129
    // Get native parameters from rcl context->global_arguments.
2130
    // rclnodejs returns an array of objects, 1 for each node e.g., -p my_node:foo:=bar,
2131
    // and a node named '/**' for global parameter rules,
2132
    // i.e., does not include a node identifier, e.g., -p color:=red
2133
    // {
2134
    //   name: string // node name
2135
    //   parameters[] = {
2136
    //     name: string
2137
    //     type: uint
2138
    //     value: object
2139
    // }
2140
    const cliParamOverrideData = rclnodejs.getParameterOverrides(
781✔
2141
      this.context.handle
2142
    );
2143

2144
    // convert native CLI parameterOverrides to Map<nodeName,Array<ParameterOverride>>
2145
    const cliParamOverrides = new Map();
781✔
2146
    if (cliParamOverrideData) {
781✔
2147
      for (let nodeParamData of cliParamOverrideData) {
8✔
2148
        const nodeName = nodeParamData.name;
12✔
2149
        const nodeParamOverrides = [];
12✔
2150
        for (let paramData of nodeParamData.parameters) {
12✔
2151
          const paramOverride = new Parameter(
17✔
2152
            paramData.name,
2153
            paramData.type,
2154
            paramData.value
2155
          );
2156
          nodeParamOverrides.push(paramOverride);
17✔
2157
        }
2158
        cliParamOverrides.set(nodeName, nodeParamOverrides);
12✔
2159
      }
2160
    }
2161

2162
    // collect global CLI global parameters, name == /**
2163
    let paramOverrides = cliParamOverrides.get('/**'); // array of ParameterOverrides
781✔
2164
    if (paramOverrides) {
781✔
2165
      for (const parameter of paramOverrides) {
5✔
2166
        overrides.set(parameter.name, parameter);
6✔
2167
      }
2168
    }
2169

2170
    // merge CLI node parameterOverrides with global parameterOverrides, replace existing
2171
    paramOverrides = cliParamOverrides.get(this.name()); // array of ParameterOverrides
781✔
2172
    if (paramOverrides) {
781✔
2173
      for (const parameter of paramOverrides) {
5✔
2174
        overrides.set(parameter.name, parameter);
7✔
2175
      }
2176
    }
2177

2178
    return overrides;
781✔
2179
  }
2180

2181
  /**
2182
   * Invokes the callback with a raw message of the given type. After the callback completes
2183
   * the message will be destroyed.
2184
   * @param {function} Type - Message type to create.
2185
   * @param {function} callback - Callback to invoke. First parameter will be the raw message,
2186
   * and the second is a function to retrieve the deserialized message.
2187
   * @returns {undefined}
2188
   */
2189
  _runWithMessageType(Type, callback) {
2190
    let message = new Type();
925✔
2191

2192
    callback(message.toRawROS(), () => {
925✔
2193
      let result = new Type();
684✔
2194
      result.deserialize(message.refObject);
684✔
2195

2196
      return result;
684✔
2197
    });
2198

2199
    Type.destroyRawROS(message);
925✔
2200
  }
2201

2202
  _addActionClient(actionClient) {
2203
    this._actionClients.push(actionClient);
41✔
2204
    this.syncHandles();
41✔
2205
  }
2206

2207
  _addActionServer(actionServer) {
2208
    this._actionServers.push(actionServer);
41✔
2209
    this.syncHandles();
41✔
2210
  }
2211

2212
  _getValidatedTopic(topicName, noDemangle) {
2213
    if (noDemangle) {
6!
UNCOV
2214
      return topicName;
×
2215
    }
2216
    const fqTopicName = rclnodejs.expandTopicName(
6✔
2217
      topicName,
2218
      this.name(),
2219
      this.namespace()
2220
    );
2221
    validateFullTopicName(fqTopicName);
6✔
2222
    return rclnodejs.remapTopicName(this.handle, fqTopicName);
6✔
2223
  }
2224

2225
  _getValidatedServiceName(serviceName, noDemangle) {
2226
    if (typeof serviceName !== 'string') {
4!
UNCOV
2227
      throw new TypeValidationError('serviceName', serviceName, 'string', {
×
2228
        nodeName: this.name(),
2229
      });
2230
    }
2231

2232
    if (noDemangle) {
4!
UNCOV
2233
      return serviceName;
×
2234
    }
2235

2236
    const resolvedServiceName = this.resolveServiceName(serviceName);
4✔
2237
    rclnodejs.validateTopicName(resolvedServiceName);
4✔
2238
    return resolvedServiceName;
4✔
2239
  }
2240
}
2241

2242
/**
2243
 * Create an Options instance initialized with default values.
2244
 * @returns {Options} - The new initialized instance.
2245
 * @static
2246
 * @example
2247
 * {
2248
 *   enableTypedArray: true,
2249
 *   isRaw: false,
2250
 *   qos: QoS.profileDefault,
2251
 *   contentFilter: undefined,
2252
 *   serializationMode: 'default',
2253
 * }
2254
 */
2255
Node.getDefaultOptions = function () {
26✔
2256
  return {
7,284✔
2257
    enableTypedArray: true,
2258
    isRaw: false,
2259
    qos: QoS.profileDefault,
2260
    contentFilter: undefined,
2261
    serializationMode: 'default',
2262
  };
2263
};
2264

2265
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