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

RobotWebTools / rclnodejs / 15205221837

23 May 2025 07:50AM UTC coverage: 84.784% (+0.08%) from 84.707%
15205221837

push

github

minggangw
Pump to 1.1.0 (#1147)

721 of 945 branches covered (76.3%)

Branch coverage included in aggregate %.

1792 of 2019 relevant lines covered (88.76%)

1139.11 hits per line

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

89.03
/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('bindings')('rclnodejs');
78✔
18

19
const ActionInterfaces = require('./action/interfaces.js');
78✔
20
const Client = require('./client.js');
78✔
21
const Clock = require('./clock.js');
78✔
22
const Context = require('./context.js');
78✔
23
const debug = require('debug')('rclnodejs:node');
78✔
24
const DistroUtils = require('./distro.js');
78✔
25
const GuardCondition = require('./guard_condition.js');
78✔
26
const loader = require('./interface_loader.js');
78✔
27
const Logging = require('./logging.js');
78✔
28
const NodeOptions = require('./node_options.js');
78✔
29
const {
30
  ParameterType,
31
  Parameter,
32
  ParameterDescriptor,
33
} = require('./parameter.js');
78✔
34
const ParameterService = require('./parameter_service.js');
78✔
35
const Publisher = require('./publisher.js');
78✔
36
const QoS = require('./qos.js');
78✔
37
const Rates = require('./rate.js');
78✔
38
const Service = require('./service.js');
78✔
39
const Subscription = require('./subscription.js');
78✔
40
const TimeSource = require('./time_source.js');
78✔
41
const Timer = require('./timer.js');
78✔
42
const TypeDescriptionService = require('./type_description_service.js');
78✔
43
const Entity = require('./entity.js');
78✔
44

45
// Parameter event publisher constants
46
const PARAMETER_EVENT_MSG_TYPE = 'rcl_interfaces/msg/ParameterEvent';
78✔
47
const PARAMETER_EVENT_TOPIC = 'parameter_events';
78✔
48

49
/**
50
 * @class - Class representing a Node in ROS
51
 */
52

53
class Node extends rclnodejs.ShadowNode {
54
  /**
55
   * Create a ROS2Node.
56
   * model using the {@link https://github.com/ros2/rcl/tree/master/rcl_lifecycle|ros2 client library (rcl) lifecyle api}.
57
   * @param {string} nodeName - The name used to register in ROS.
58
   * @param {string} [namespace=''] - The namespace used in ROS.
59
   * @param {Context} [context=Context.defaultContext()] - The context to create the node in.
60
   * @param {NodeOptions} [options=NodeOptions.defaultOptions] - The options to configure the new node behavior.
61
   * @throws {Error} If the given context is not registered.
62
   */
63
  constructor(
64
    nodeName,
65
    namespace = '',
93✔
66
    context = Context.defaultContext(),
198✔
67
    options = NodeOptions.defaultOptions
204✔
68
  ) {
69
    super();
1,809✔
70

71
    if (typeof nodeName !== 'string' || typeof namespace !== 'string') {
1,809✔
72
      throw new TypeError('Invalid argument.');
66✔
73
    }
74

75
    this._init(nodeName, namespace, options, context);
1,743✔
76
    debug(
1,722✔
77
      'Finish initializing node, name = %s and namespace = %s.',
78
      nodeName,
79
      namespace
80
    );
81
  }
82

83
  _init(name, namespace, options, context) {
84
    this.handle = rclnodejs.createNode(name, namespace, context.handle);
1,743✔
85
    Object.defineProperty(this, 'handle', {
1,722✔
86
      configurable: false,
87
      writable: false,
88
    }); // make read-only
89

90
    this._context = context;
1,722✔
91
    this.context.onNodeCreated(this);
1,722✔
92

93
    this._publishers = [];
1,722✔
94
    this._subscriptions = [];
1,722✔
95
    this._clients = [];
1,722✔
96
    this._services = [];
1,722✔
97
    this._timers = [];
1,722✔
98
    this._guards = [];
1,722✔
99
    this._actionClients = [];
1,722✔
100
    this._actionServers = [];
1,722✔
101
    this._rateTimerServer = null;
1,722✔
102
    this._parameterDescriptors = new Map();
1,722✔
103
    this._parameters = new Map();
1,722✔
104
    this._parameterService = null;
1,722✔
105
    this._typeDescriptionService = null;
1,722✔
106
    this._parameterEventPublisher = null;
1,722✔
107
    this._setParametersCallbacks = [];
1,722✔
108
    this._logger = new Logging(rclnodejs.getNodeLoggerName(this.handle));
1,722✔
109
    this._spinning = false;
1,722✔
110

111
    this._parameterEventPublisher = this.createPublisher(
1,722✔
112
      PARAMETER_EVENT_MSG_TYPE,
113
      PARAMETER_EVENT_TOPIC
114
    );
115

116
    // initialize _parameterOverrides from parameters defined on the commandline
117
    this._parameterOverrides = this._getNativeParameterOverrides();
1,722✔
118

119
    // override cli parameterOverrides with those specified in options
120
    if (options.parameterOverrides.length > 0) {
1,722✔
121
      for (const parameter of options.parameterOverrides) {
24✔
122
        if ((!parameter) instanceof Parameter) {
24!
123
          throw new TypeError(
×
124
            'Parameter-override must be an instance of Parameter.'
125
          );
126
        }
127
        this._parameterOverrides.set(parameter.name, parameter);
24✔
128
      }
129
    }
130

131
    // initialize _parameters from parameterOverrides
132
    if (options.automaticallyDeclareParametersFromOverrides) {
1,722✔
133
      for (const parameter of this._parameterOverrides.values()) {
15✔
134
        parameter.validate();
15✔
135
        const descriptor = ParameterDescriptor.fromParameter(parameter);
15✔
136
        this._parameters.set(parameter.name, parameter);
15✔
137
        this._parameterDescriptors.set(parameter.name, descriptor);
15✔
138
      }
139
    }
140

141
    // Clock that has support for ROS time.
142
    // Note: parameter overrides and parameter event publisher need to be ready at this point
143
    // to be able to declare 'use_sim_time' if it was not declared yet.
144
    this._clock = new Clock.ROSClock();
1,722✔
145
    this._timeSource = new TimeSource(this);
1,722✔
146
    this._timeSource.attachClock(this._clock);
1,722✔
147

148
    if (options.startParameterServices) {
1,722✔
149
      this._parameterService = new ParameterService(this);
1,704✔
150
      this._parameterService.start();
1,704✔
151
    }
152

153
    if (
1,722!
154
      DistroUtils.getDistroId() >= DistroUtils.getDistroId('jazzy') &&
3,444✔
155
      options.startTypeDescriptionService
156
    ) {
157
      this._typeDescriptionService = new TypeDescriptionService(this);
1,722✔
158
      this._typeDescriptionService.start();
1,722✔
159
    }
160
  }
161

162
  execute(handles) {
163
    let timersReady = this._timers.filter((timer) =>
15,844✔
164
      handles.includes(timer.handle)
13,828✔
165
    );
166
    let guardsReady = this._guards.filter((guard) =>
15,844✔
167
      handles.includes(guard.handle)
9✔
168
    );
169
    let subscriptionsReady = this._subscriptions.filter((subscription) =>
15,844✔
170
      handles.includes(subscription.handle)
1,430✔
171
    );
172
    let clientsReady = this._clients.filter((client) =>
15,844✔
173
      handles.includes(client.handle)
307✔
174
    );
175
    let servicesReady = this._services.filter((service) =>
15,844✔
176
      handles.includes(service.handle)
57,267✔
177
    );
178
    let actionClientsReady = this._actionClients.filter((actionClient) =>
15,844✔
179
      handles.includes(actionClient.handle)
472✔
180
    );
181
    let actionServersReady = this._actionServers.filter((actionServer) =>
15,844✔
182
      handles.includes(actionServer.handle)
472✔
183
    );
184

185
    timersReady.forEach((timer) => {
15,844✔
186
      if (timer.isReady()) {
13,798✔
187
        rclnodejs.callTimer(timer.handle);
13,680✔
188
        timer.callback();
13,680✔
189
      }
190
    });
191

192
    for (const subscription of subscriptionsReady) {
15,844✔
193
      if (subscription.isDestroyed()) continue;
1,319✔
194
      if (subscription.isRaw) {
1,289✔
195
        let rawMessage = rclnodejs.rclTakeRaw(subscription.handle);
3✔
196
        if (rawMessage) {
3!
197
          subscription.processResponse(rawMessage);
3✔
198
        }
199
        continue;
3✔
200
      }
201

202
      this._runWithMessageType(
1,286✔
203
        subscription.typeClass,
204
        (message, deserialize) => {
205
          let success = rclnodejs.rclTake(subscription.handle, message);
1,286✔
206
          if (success) {
1,286✔
207
            subscription.processResponse(deserialize());
1,066✔
208
          }
209
        }
210
      );
211
    }
212

213
    for (const guard of guardsReady) {
15,844✔
214
      if (guard.isDestroyed()) continue;
9!
215

216
      guard.callback();
9✔
217
    }
218

219
    for (const client of clientsReady) {
15,844✔
220
      if (client.isDestroyed()) continue;
153!
221
      this._runWithMessageType(
153✔
222
        client.typeClass.Response,
223
        (message, deserialize) => {
224
          let sequenceNumber = rclnodejs.rclTakeResponse(
153✔
225
            client.handle,
226
            message
227
          );
228
          if (sequenceNumber !== undefined) {
153✔
229
            client.processResponse(sequenceNumber, deserialize());
93✔
230
          }
231
        }
232
      );
233
    }
234

235
    for (const service of servicesReady) {
15,844✔
236
      if (service.isDestroyed()) continue;
186!
237
      this._runWithMessageType(
186✔
238
        service.typeClass.Request,
239
        (message, deserialize) => {
240
          let header = rclnodejs.rclTakeRequest(
186✔
241
            service.handle,
242
            this.handle,
243
            message
244
          );
245
          if (header) {
186✔
246
            service.processRequest(header, deserialize());
96✔
247
          }
248
        }
249
      );
250
    }
251

252
    for (const actionClient of actionClientsReady) {
15,844✔
253
      if (actionClient.isDestroyed()) continue;
223!
254

255
      const properties = actionClient.handle.properties;
223✔
256

257
      if (properties.isGoalResponseReady) {
223✔
258
        this._runWithMessageType(
107✔
259
          actionClient.typeClass.impl.SendGoalService.Response,
260
          (message, deserialize) => {
261
            let sequence = rclnodejs.actionTakeGoalResponse(
107✔
262
              actionClient.handle,
263
              message
264
            );
265
            if (sequence != undefined) {
107✔
266
              actionClient.processGoalResponse(sequence, deserialize());
90✔
267
            }
268
          }
269
        );
270
      }
271

272
      if (properties.isCancelResponseReady) {
223✔
273
        this._runWithMessageType(
16✔
274
          actionClient.typeClass.impl.CancelGoal.Response,
275
          (message, deserialize) => {
276
            let sequence = rclnodejs.actionTakeCancelResponse(
16✔
277
              actionClient.handle,
278
              message
279
            );
280
            if (sequence != undefined) {
16✔
281
              actionClient.processCancelResponse(sequence, deserialize());
12✔
282
            }
283
          }
284
        );
285
      }
286

287
      if (properties.isResultResponseReady) {
223✔
288
        this._runWithMessageType(
46✔
289
          actionClient.typeClass.impl.GetResultService.Response,
290
          (message, deserialize) => {
291
            let sequence = rclnodejs.actionTakeResultResponse(
46✔
292
              actionClient.handle,
293
              message
294
            );
295
            if (sequence != undefined) {
46✔
296
              actionClient.processResultResponse(sequence, deserialize());
45✔
297
            }
298
          }
299
        );
300
      }
301

302
      if (properties.isFeedbackReady) {
223✔
303
        this._runWithMessageType(
22✔
304
          actionClient.typeClass.impl.FeedbackMessage,
305
          (message, deserialize) => {
306
            let success = rclnodejs.actionTakeFeedback(
22✔
307
              actionClient.handle,
308
              message
309
            );
310
            if (success) {
22✔
311
              actionClient.processFeedbackMessage(deserialize());
15✔
312
            }
313
          }
314
        );
315
      }
316

317
      if (properties.isStatusReady) {
223✔
318
        this._runWithMessageType(
115✔
319
          actionClient.typeClass.impl.GoalStatusArray,
320
          (message, deserialize) => {
321
            let success = rclnodejs.actionTakeStatus(
115✔
322
              actionClient.handle,
323
              message
324
            );
325
            if (success) {
115✔
326
              actionClient.processStatusMessage(deserialize());
102✔
327
            }
328
          }
329
        );
330
      }
331
    }
332

333
    for (const actionServer of actionServersReady) {
15,844✔
334
      if (actionServer.isDestroyed()) continue;
278!
335

336
      const properties = actionServer.handle.properties;
278✔
337

338
      if (properties.isGoalRequestReady) {
278✔
339
        this._runWithMessageType(
95✔
340
          actionServer.typeClass.impl.SendGoalService.Request,
341
          (message, deserialize) => {
342
            const result = rclnodejs.actionTakeGoalRequest(
95✔
343
              actionServer.handle,
344
              message
345
            );
346
            if (result) {
95✔
347
              actionServer.processGoalRequest(result, deserialize());
90✔
348
            }
349
          }
350
        );
351
      }
352

353
      if (properties.isCancelRequestReady) {
278✔
354
        this._runWithMessageType(
21✔
355
          actionServer.typeClass.impl.CancelGoal.Request,
356
          (message, deserialize) => {
357
            const result = rclnodejs.actionTakeCancelRequest(
21✔
358
              actionServer.handle,
359
              message
360
            );
361
            if (result) {
21✔
362
              actionServer.processCancelRequest(result, deserialize());
12✔
363
            }
364
          }
365
        );
366
      }
367

368
      if (properties.isResultRequestReady) {
278✔
369
        this._runWithMessageType(
69✔
370
          actionServer.typeClass.impl.GetResultService.Request,
371
          (message, deserialize) => {
372
            const result = rclnodejs.actionTakeResultRequest(
69✔
373
              actionServer.handle,
374
              message
375
            );
376
            if (result) {
69✔
377
              actionServer.processResultRequest(result, deserialize());
45✔
378
            }
379
          }
380
        );
381
      }
382

383
      if (properties.isGoalExpired) {
278✔
384
        let GoalInfoArray = ActionInterfaces.GoalInfo.ArrayType;
14✔
385
        let message = new GoalInfoArray(actionServer._goalHandles.size);
14✔
386
        let count = rclnodejs.actionExpireGoals(
14✔
387
          actionServer.handle,
388
          actionServer._goalHandles.size,
389
          message._refArray.buffer
390
        );
391
        if (count > 0) {
14✔
392
          actionServer.processGoalExpired(message, count);
9✔
393
        }
394
        GoalInfoArray.freeArray(message);
14✔
395
      }
396
    }
397

398
    // At this point it is safe to clear the cache of any
399
    // destroyed entity references
400
    Entity._gcHandles();
15,844✔
401
  }
402

403
  /**
404
   * Determine if this node is spinning.
405
   * @returns {boolean} - true when spinning; otherwise returns false.
406
   */
407
  get spinning() {
408
    return this._spinning;
12,093✔
409
  }
410

411
  /**
412
   * Trigger the event loop to continuously check for and route.
413
   * incoming events.
414
   * @param {Node} node - The node to be spun up.
415
   * @param {number} [timeout=10] - Timeout to wait in milliseconds. Block forever if negative. Don't wait if 0.
416
   * @throws {Error} If the node is already spinning.
417
   * @return {undefined}
418
   */
419
  spin(timeout = 10) {
45✔
420
    if (this.spinning) {
1,272!
421
      throw new Error('The node is already spinning.');
×
422
    }
423
    this.start(this.context.handle, timeout);
1,272✔
424
    this._spinning = true;
1,272✔
425
  }
426

427
  /**
428
   * Use spin().
429
   * @deprecated, since 0.18.0
430
   */
431
  startSpinning(timeout) {
432
    this.spin(timeout);
×
433
  }
434

435
  /**
436
   * Terminate spinning - no further events will be received.
437
   * @returns {undefined}
438
   */
439
  stop() {
440
    super.stop();
1,272✔
441
    this._spinning = false;
1,272✔
442
  }
443

444
  /**
445
   * Terminate spinning - no further events will be received.
446
   * @returns {undefined}
447
   * @deprecated since 0.18.0, Use stop().
448
   */
449
  stopSpinning() {
450
    super.stop();
×
451
    this._spinning = false;
×
452
  }
453

454
  /**
455
   * Spin the node and trigger the event loop to check for one incoming event. Thereafter the node
456
   * will not received additional events until running additional calls to spin() or spinOnce().
457
   * @param {Node} node - The node to be spun.
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
  spinOnce(timeout = 10) {
6✔
463
    if (this.spinning) {
9,027✔
464
      throw new Error('The node is already spinning.');
6✔
465
    }
466
    super.spinOnce(this.context.handle, timeout);
9,021✔
467
  }
468

469
  _removeEntityFromArray(entity, array) {
470
    let index = array.indexOf(entity);
342✔
471
    if (index > -1) {
342✔
472
      array.splice(index, 1);
336✔
473
    }
474
  }
475

476
  _destroyEntity(entity, array, syncHandles = true) {
348✔
477
    if (entity['isDestroyed'] && entity.isDestroyed()) return;
366✔
478

479
    this._removeEntityFromArray(entity, array);
342✔
480
    if (syncHandles) {
342✔
481
      this.syncHandles();
330✔
482
    }
483

484
    if (entity['_destroy']) {
342✔
485
      entity._destroy();
324✔
486
    } else {
487
      // guards and timers
488
      entity.handle.release();
18✔
489
    }
490
  }
491

492
  _validateOptions(options) {
493
    if (
16,455✔
494
      options !== undefined &&
16,599✔
495
      (options === null || typeof options !== 'object')
496
    ) {
497
      throw new TypeError('Invalid argument of options');
60✔
498
    }
499

500
    if (options === undefined) {
16,395✔
501
      return Node.getDefaultOptions();
16,353✔
502
    }
503

504
    if (options.enableTypedArray === undefined) {
42✔
505
      options = Object.assign(options, { enableTypedArray: true });
6✔
506
    }
507

508
    if (options.qos === undefined) {
42✔
509
      options = Object.assign(options, { qos: QoS.profileDefault });
12✔
510
    }
511

512
    if (options.isRaw === undefined) {
42✔
513
      options = Object.assign(options, { isRaw: false });
12✔
514
    }
515

516
    return options;
42✔
517
  }
518

519
  /**
520
   * Create a Timer.
521
   * @param {bigint} period - The number representing period in nanoseconds.
522
   * @param {function} callback - The callback to be called when timeout.
523
   * @param {Clock} [clock] - The clock which the timer gets time from.
524
   * @return {Timer} - An instance of Timer.
525
   */
526
  createTimer(period, callback, clock = null) {
174✔
527
    if (arguments.length === 3 && !(arguments[2] instanceof Clock)) {
174!
528
      clock = null;
×
529
    } else if (arguments.length === 4) {
174!
530
      clock = arguments[3];
×
531
    }
532

533
    if (typeof period !== 'bigint' || typeof callback !== 'function') {
174✔
534
      throw new TypeError('Invalid argument');
6✔
535
    }
536

537
    const timerClock = clock || this._clock;
168✔
538
    let timerHandle = rclnodejs.createTimer(
168✔
539
      timerClock.handle,
540
      this.context.handle,
541
      period
542
    );
543
    let timer = new Timer(timerHandle, period, callback);
168✔
544
    debug('Finish creating timer, period = %d.', period);
168✔
545
    this._timers.push(timer);
168✔
546
    this.syncHandles();
168✔
547

548
    return timer;
168✔
549
  }
550

551
  /**
552
   * Create a Rate.
553
   *
554
   * @param {number} hz - The frequency of the rate timer; default is 1 hz.
555
   * @returns {Promise<Rate>} - Promise resolving to new instance of Rate.
556
   */
557
  async createRate(hz = 1) {
12✔
558
    if (typeof hz !== 'number') {
27!
559
      throw new TypeError('Invalid argument');
×
560
    }
561

562
    const MAX_RATE_HZ_IN_MILLISECOND = 1000.0;
27✔
563
    if (hz <= 0.0 || hz > MAX_RATE_HZ_IN_MILLISECOND) {
27✔
564
      throw new RangeError(
6✔
565
        `Hz must be between 0.0 and ${MAX_RATE_HZ_IN_MILLISECOND}`
566
      );
567
    }
568

569
    // lazy initialize rateTimerServer
570
    if (!this._rateTimerServer) {
21✔
571
      this._rateTimerServer = new Rates.RateTimerServer(this);
15✔
572
      await this._rateTimerServer.init();
15✔
573
    }
574

575
    const period = Math.round(1000 / hz);
21✔
576
    const timer = this._rateTimerServer.createTimer(BigInt(period) * 1000000n);
21✔
577
    const rate = new Rates.Rate(hz, timer);
21✔
578

579
    return rate;
21✔
580
  }
581

582
  /**
583
   * Create a Publisher.
584
   * @param {function|string|object} typeClass - The ROS message class,
585
        OR a string representing the message class, e.g. 'std_msgs/msg/String',
586
        OR an object representing the message class, e.g. {package: 'std_msgs', type: 'msg', name: 'String'}
587
   * @param {string} topic - The name of the topic.
588
   * @param {object} options - The options argument used to parameterize the publisher.
589
   * @param {boolean} options.enableTypedArray - The topic will use TypedArray if necessary, default: true.
590
   * @param {QoS} options.qos - ROS Middleware "quality of service" settings for the publisher, default: QoS.profileDefault.
591
   * @return {Publisher} - An instance of Publisher.
592
   */
593
  createPublisher(typeClass, topic, options) {
594
    return this._createPublisher(typeClass, topic, options, Publisher);
2,793✔
595
  }
596

597
  _createPublisher(typeClass, topic, options, publisherClass) {
598
    if (typeof typeClass === 'string' || typeof typeClass === 'object') {
2,802✔
599
      typeClass = loader.loadInterface(typeClass);
2,730✔
600
    }
601
    options = this._validateOptions(options);
2,781✔
602

603
    if (typeof typeClass !== 'function' || typeof topic !== 'string') {
2,781✔
604
      throw new TypeError('Invalid argument');
60✔
605
    }
606

607
    let publisher = publisherClass.createPublisher(
2,721✔
608
      this.handle,
609
      typeClass,
610
      topic,
611
      options
612
    );
613
    debug('Finish creating publisher, topic = %s.', topic);
2,691✔
614
    this._publishers.push(publisher);
2,691✔
615
    return publisher;
2,691✔
616
  }
617

618
  /**
619
   * This callback is called when a message is published
620
   * @callback SubscriptionCallback
621
   * @param {Object} message - The message published
622
   * @see [Node.createSubscription]{@link Node#createSubscription}
623
   * @see [Node.createPublisher]{@link Node#createPublisher}
624
   * @see {@link Publisher}
625
   * @see {@link Subscription}
626
   */
627

628
  /**
629
   * Create a Subscription with optional content-filtering.
630
   * @param {function|string|object} typeClass - The ROS message class,
631
        OR a string representing the message class, e.g. 'std_msgs/msg/String',
632
        OR an object representing the message class, e.g. {package: 'std_msgs', type: 'msg', name: 'String'}
633
   * @param {string} topic - The name of the topic.
634
   * @param {object} options - The options argument used to parameterize the subscription.
635
   * @param {boolean} options.enableTypedArray - The topic will use TypedArray if necessary, default: true.
636
   * @param {QoS} options.qos - ROS Middleware "quality of service" settings for the subscription, default: QoS.profileDefault.
637
   * @param {boolean} options.isRaw - The topic is serialized when true, default: false.
638
   * @param {object} [options.contentFilter=undefined] - The content-filter, default: undefined.
639
   *  Confirm that your RMW supports content-filtered topics before use. 
640
   * @param {string} options.contentFilter.expression - Specifies the criteria to select the data samples of
641
   *  interest. It is similar to the WHERE part of an SQL clause.
642
   * @param {string[]} [options.contentFilter.parameters=undefined] - Array of strings that give values to
643
   *  the ‘parameters’ (i.e., "%n" tokens) in the filter_expression. The number of supplied parameters must
644
   *  fit with the requested values in the filter_expression (i.e., the number of %n tokens). default: undefined.
645
   * @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.
646
   * @return {Subscription} - An instance of Subscription.
647
   * @throws {ERROR} - May throw an RMW error if content-filter is malformed. 
648
   * @see {@link SubscriptionCallback}
649
   * @see {@link https://www.omg.org/spec/DDS/1.4/PDF|Content-filter details at DDS 1.4 specification, Annex B}
650
   */
651
  createSubscription(typeClass, topic, options, callback) {
652
    if (typeof typeClass === 'string' || typeof typeClass === 'object') {
1,125✔
653
      typeClass = loader.loadInterface(typeClass);
1,098✔
654
    }
655

656
    if (typeof options === 'function') {
1,104✔
657
      callback = options;
1,011✔
658
      options = undefined;
1,011✔
659
    }
660
    options = this._validateOptions(options);
1,104✔
661

662
    if (
1,074✔
663
      typeof typeClass !== 'function' ||
3,180✔
664
      typeof topic !== 'string' ||
665
      typeof callback !== 'function'
666
    ) {
667
      throw new TypeError('Invalid argument');
30✔
668
    }
669

670
    let subscription = Subscription.createSubscription(
1,044✔
671
      this.handle,
672
      typeClass,
673
      topic,
674
      options,
675
      callback
676
    );
677
    debug('Finish creating subscription, topic = %s.', topic);
1,011✔
678
    this._subscriptions.push(subscription);
1,011✔
679
    this.syncHandles();
1,011✔
680

681
    return subscription;
1,011✔
682
  }
683

684
  /**
685
   * Create a Client.
686
   * @param {function|string|object} typeClass - The ROS message class,
687
        OR a string representing the message class, e.g. 'std_msgs/msg/String',
688
        OR an object representing the message class, e.g. {package: 'std_msgs', type: 'msg', name: 'String'}
689
   * @param {string} serviceName - The service name to request.
690
   * @param {object} options - The options argument used to parameterize the client.
691
   * @param {boolean} options.enableTypedArray - The response will use TypedArray if necessary, default: true.
692
   * @param {QoS} options.qos - ROS Middleware "quality of service" settings for the client, default: QoS.profileDefault.
693
   * @return {Client} - An instance of Client.
694
   */
695
  createClient(typeClass, serviceName, options) {
696
    if (typeof typeClass === 'string' || typeof typeClass === 'object') {
252✔
697
      typeClass = loader.loadInterface(typeClass);
222✔
698
    }
699
    options = this._validateOptions(options);
231✔
700

701
    if (typeof typeClass !== 'function' || typeof serviceName !== 'string') {
231✔
702
      throw new TypeError('Invalid argument');
60✔
703
    }
704

705
    let client = Client.createClient(
171✔
706
      this.handle,
707
      serviceName,
708
      typeClass,
709
      options
710
    );
711
    debug('Finish creating client, service = %s.', serviceName);
141✔
712
    this._clients.push(client);
141✔
713
    this.syncHandles();
141✔
714

715
    return client;
141✔
716
  }
717

718
  /**
719
   * This callback is called when a request is sent to service
720
   * @callback RequestCallback
721
   * @param {Object} request - The request sent to the service
722
   * @param {Response} response - The response to client.
723
        Use [response.send()]{@link Response#send} to send response object to client
724
   * @return {undefined}
725
   * @see [Node.createService]{@link Node#createService}
726
   * @see [Client.sendRequest]{@link Client#sendRequest}
727
   * @see {@link Client}
728
   * @see {@link Service}
729
   * @see {@link Response#send}
730
   */
731

732
  /**
733
   * Create a Service.
734
   * @param {function|string|object} typeClass - The ROS message class,
735
        OR a string representing the message class, e.g. 'std_msgs/msg/String',
736
        OR an object representing the message class, e.g. {package: 'std_msgs', type: 'msg', name: 'String'}
737
   * @param {string} serviceName - The service name to offer.
738
   * @param {object} options - The options argument used to parameterize the service.
739
   * @param {boolean} options.enableTypedArray - The request will use TypedArray if necessary, default: true.
740
   * @param {QoS} options.qos - ROS Middleware "quality of service" settings for the service, default: QoS.profileDefault.
741
   * @param {RequestCallback} callback - The callback to be called when receiving request.
742
   * @return {Service} - An instance of Service.
743
   * @see {@link RequestCallback}
744
   */
745
  createService(typeClass, serviceName, options, callback) {
746
    if (typeof typeClass === 'string' || typeof typeClass === 'object') {
10,434✔
747
      typeClass = loader.loadInterface(typeClass);
10,404✔
748
    }
749

750
    if (typeof options === 'function') {
10,413✔
751
      callback = options;
10,347✔
752
      options = undefined;
10,347✔
753
    }
754
    options = this._validateOptions(options);
10,413✔
755

756
    if (
10,383✔
757
      typeof typeClass !== 'function' ||
31,107✔
758
      typeof serviceName !== 'string' ||
759
      typeof callback !== 'function'
760
    ) {
761
      throw new TypeError('Invalid argument');
30✔
762
    }
763

764
    let service = Service.createService(
10,353✔
765
      this.handle,
766
      serviceName,
767
      typeClass,
768
      options,
769
      callback
770
    );
771
    debug('Finish creating service, service = %s.', serviceName);
10,323✔
772
    this._services.push(service);
10,323✔
773
    this.syncHandles();
10,323✔
774

775
    return service;
10,323✔
776
  }
777

778
  /**
779
   * Create a guard condition.
780
   * @param {Function} callback - The callback to be called when the guard condition is triggered.
781
   * @return {GuardCondition} - An instance of GuardCondition.
782
   */
783
  createGuardCondition(callback) {
784
    if (typeof callback !== 'function') {
9!
785
      throw new TypeError('Invalid argument');
×
786
    }
787

788
    let guard = GuardCondition.createGuardCondition(callback, this.context);
9✔
789
    debug('Finish creating guard condition');
9✔
790
    this._guards.push(guard);
9✔
791
    this.syncHandles();
9✔
792

793
    return guard;
9✔
794
  }
795

796
  /**
797
   * Destroy all resource allocated by this node, including
798
   * <code>Timer</code>s/<code>Publisher</code>s/<code>Subscription</code>s
799
   * /<code>Client</code>s/<code>Service</code>s
800
   * @return {undefined}
801
   */
802
  destroy() {
803
    if (this.spinning) {
1,788✔
804
      this.stop();
1,269✔
805
    }
806

807
    // Action servers/clients require manual destruction due to circular reference with goal handles.
808
    this._actionClients.forEach((actionClient) => actionClient.destroy());
1,788✔
809
    this._actionServers.forEach((actionServer) => actionServer.destroy());
1,788✔
810

811
    this.context.onNodeDestroyed(this);
1,788✔
812

813
    this.handle.release();
1,788✔
814
    this._clock = null;
1,788✔
815
    this._timers = [];
1,788✔
816
    this._publishers = [];
1,788✔
817
    this._subscriptions = [];
1,788✔
818
    this._clients = [];
1,788✔
819
    this._services = [];
1,788✔
820
    this._guards = [];
1,788✔
821
    this._actionClients = [];
1,788✔
822
    this._actionServers = [];
1,788✔
823

824
    if (this._rateTimerServer) {
1,788✔
825
      this._rateTimerServer.shutdown();
15✔
826
      this._rateTimerServer = null;
15✔
827
    }
828
  }
829

830
  /**
831
   * Destroy a Publisher.
832
   * @param {Publisher} publisher - The Publisher to be destroyed.
833
   * @return {undefined}
834
   */
835
  destroyPublisher(publisher) {
836
    if (!(publisher instanceof Publisher)) {
24✔
837
      throw new TypeError('Invalid argument');
6✔
838
    }
839
    this._destroyEntity(publisher, this._publishers, false);
18✔
840
  }
841

842
  /**
843
   * Destroy a Subscription.
844
   * @param {Subscription} subscription - The Subscription to be destroyed.
845
   * @return {undefined}
846
   */
847
  destroySubscription(subscription) {
848
    if (!(subscription instanceof Subscription)) {
63✔
849
      throw new TypeError('Invalid argument');
6✔
850
    }
851
    this._destroyEntity(subscription, this._subscriptions);
57✔
852
  }
853

854
  /**
855
   * Destroy a Client.
856
   * @param {Client} client - The Client to be destroyed.
857
   * @return {undefined}
858
   */
859
  destroyClient(client) {
860
    if (!(client instanceof Client)) {
24✔
861
      throw new TypeError('Invalid argument');
6✔
862
    }
863
    this._destroyEntity(client, this._clients);
18✔
864
  }
865

866
  /**
867
   * Destroy a Service.
868
   * @param {Service} service - The Service to be destroyed.
869
   * @return {undefined}
870
   */
871
  destroyService(service) {
872
    if (!(service instanceof Service)) {
24✔
873
      throw new TypeError('Invalid argument');
6✔
874
    }
875
    this._destroyEntity(service, this._services);
18✔
876
  }
877

878
  /**
879
   * Destroy a Timer.
880
   * @param {Timer} timer - The Timer to be destroyed.
881
   * @return {undefined}
882
   */
883
  destroyTimer(timer) {
884
    if (!(timer instanceof Timer)) {
24✔
885
      throw new TypeError('Invalid argument');
6✔
886
    }
887
    this._destroyEntity(timer, this._timers);
18✔
888
  }
889

890
  /**
891
   * Destroy a guard condition.
892
   * @param {GuardCondition} guard - The guard condition to be destroyed.
893
   * @return {undefined}
894
   */
895
  destroyGuardCondition(guard) {
896
    if (!(guard instanceof GuardCondition)) {
9!
897
      throw new TypeError('Invalid argument');
×
898
    }
899
    this._destroyEntity(guard, this._guards);
9✔
900
  }
901

902
  /**
903
   * Get the name of the node.
904
   * @return {string}
905
   */
906
  name() {
907
    return rclnodejs.getNodeName(this.handle);
7,059✔
908
  }
909

910
  /**
911
   * Get the namespace of the node.
912
   * @return {string}
913
   */
914
  namespace() {
915
    return rclnodejs.getNamespace(this.handle);
3,765✔
916
  }
917

918
  /**
919
   * Get the context in which this node was created.
920
   * @return {Context}
921
   */
922
  get context() {
923
    return this._context;
15,702✔
924
  }
925

926
  /**
927
   * Get the nodes logger.
928
   * @returns {Logger} - The logger for the node.
929
   */
930
  getLogger() {
931
    return this._logger;
423✔
932
  }
933

934
  /**
935
   * Get the clock used by the node.
936
   * @returns {Clock} - The nodes clock.
937
   */
938
  getClock() {
939
    return this._clock;
258✔
940
  }
941

942
  /**
943
   * Get the current time using the node's clock.
944
   * @returns {Time} - The current time.
945
   */
946
  now() {
947
    return this.getClock().now();
6✔
948
  }
949

950
  /**
951
   * Get the list of published topics discovered by the provided node for the remote node name.
952
   * @param {string} nodeName - The name of the node.
953
   * @param {string} namespace - The name of the namespace.
954
   * @param {boolean} noDemangle - If true topic names and types returned will not be demangled, default: false.
955
   * @return {Array<{name: string, types: Array<string>}>} - An array of the names and types.
956
   */
957
  getPublisherNamesAndTypesByNode(nodeName, namespace, noDemangle = false) {
×
958
    return rclnodejs.getPublisherNamesAndTypesByNode(
×
959
      this.handle,
960
      nodeName,
961
      namespace,
962
      noDemangle
963
    );
964
  }
965

966
  /**
967
   * Get the list of published topics discovered by the provided node for the remote node name.
968
   * @param {string} nodeName - The name of the node.
969
   * @param {string} namespace - The name of the namespace.
970
   * @param {boolean} noDemangle - If true topic names and types returned will not be demangled, default: false.
971
   * @return {Array<{name: string, types: Array<string>}>} - An array of the names and types.
972
   */
973
  getSubscriptionNamesAndTypesByNode(nodeName, namespace, noDemangle = false) {
×
974
    return rclnodejs.getSubscriptionNamesAndTypesByNode(
×
975
      this.handle,
976
      nodeName,
977
      namespace,
978
      noDemangle
979
    );
980
  }
981

982
  /**
983
   * Get service names and types for which a remote node has servers.
984
   * @param {string} nodeName - The name of the node.
985
   * @param {string} namespace - The name of the namespace.
986
   * @return {Array<{name: string, types: Array<string>}>} - An array of the names and types.
987
   */
988
  getServiceNamesAndTypesByNode(nodeName, namespace) {
989
    return rclnodejs.getServiceNamesAndTypesByNode(
×
990
      this.handle,
991
      nodeName,
992
      namespace
993
    );
994
  }
995

996
  /**
997
   * Get service names and types for which a remote node has clients.
998
   * @param {string} nodeName - The name of the node.
999
   * @param {string} namespace - The name of the namespace.
1000
   * @return {Array<{name: string, types: Array<string>}>} - An array of the names and types.
1001
   */
1002
  getClientNamesAndTypesByNode(nodeName, namespace) {
1003
    return rclnodejs.getClientNamesAndTypesByNode(
×
1004
      this.handle,
1005
      nodeName,
1006
      namespace
1007
    );
1008
  }
1009

1010
  /**
1011
   * Get the list of topics discovered by the provided node.
1012
   * @param {boolean} noDemangle - If true topic names and types returned will not be demangled, default: false.
1013
   * @return {Array<{name: string, types: Array<string>}>} - An array of the names and types.
1014
   */
1015
  getTopicNamesAndTypes(noDemangle = false) {
×
1016
    return rclnodejs.getTopicNamesAndTypes(this.handle, noDemangle);
×
1017
  }
1018

1019
  /**
1020
   * Get the list of services discovered by the provided node.
1021
   * @return {Array<{name: string, types: Array<string>}>} - An array of the names and types.
1022
   */
1023
  getServiceNamesAndTypes() {
1024
    return rclnodejs.getServiceNamesAndTypes(this.handle);
6✔
1025
  }
1026

1027
  /**
1028
   * Get a list of publishers on a given topic.
1029
   * @param {string} topic - the topic name to get the publishers for.
1030
   * @param {boolean} noDemangle - if `true`, `topic_name` needs to be a valid middleware topic name,
1031
   *       otherwise it should be a valid ROS topic name.
1032
   * @returns {Array} - list of publishers
1033
   */
1034
  getPublishersInfoByTopic(topic, noDemangle) {
1035
    return rclnodejs.getPublishersInfoByTopic(this.handle, topic, noDemangle);
6✔
1036
  }
1037

1038
  /**
1039
   * Get a list of subscriptions on a given topic.
1040
   * @param {string} topic - the topic name to get the subscriptions for.
1041
   * @param {boolean} noDemangle - if `true`, `topic_name` needs to be a valid middleware topic name,
1042
   *       otherwise it should be a valid ROS topic name.
1043
   * @returns {Array} - list of subscriptions
1044
   */
1045
  getSubscriptionsInfoByTopic(topic, noDemangle) {
1046
    return rclnodejs.getSubscriptionsInfoByTopic(
3✔
1047
      this.handle,
1048
      topic,
1049
      noDemangle
1050
    );
1051
  }
1052

1053
  /**
1054
   * Get the list of nodes discovered by the provided node.
1055
   * @return {Array<string>} - An array of the names.
1056
   */
1057
  getNodeNames() {
1058
    return this.getNodeNamesAndNamespaces().map((item) => item.name);
120✔
1059
  }
1060

1061
  /**
1062
   * Get the list of nodes and their namespaces discovered by the provided node.
1063
   * @return {Array<{name: string, namespace: string}>} An array of the names and namespaces.
1064
   */
1065
  getNodeNamesAndNamespaces() {
1066
    return rclnodejs.getNodeNames(this.handle, /*getEnclaves=*/ false);
51✔
1067
  }
1068

1069
  /**
1070
   * Get the list of nodes and their namespaces with enclaves discovered by the provided node.
1071
   * @return {Array<{name: string, namespace: string, enclave: string}>} An array of the names, namespaces and enclaves.
1072
   */
1073
  getNodeNamesAndNamespacesWithEnclaves() {
1074
    return rclnodejs.getNodeNames(this.handle, /*getEnclaves=*/ true);
3✔
1075
  }
1076

1077
  /**
1078
   * Return the number of publishers on a given topic.
1079
   * @param {string} topic - The name of the topic.
1080
   * @returns {number} - Number of publishers on the given topic.
1081
   */
1082
  countPublishers(topic) {
1083
    let expandedTopic = rclnodejs.expandTopicName(
18✔
1084
      topic,
1085
      this.name(),
1086
      this.namespace()
1087
    );
1088
    rclnodejs.validateTopicName(expandedTopic);
18✔
1089

1090
    return rclnodejs.countPublishers(this.handle, expandedTopic);
18✔
1091
  }
1092

1093
  /**
1094
   * Return the number of subscribers on a given topic.
1095
   * @param {string} topic - The name of the topic.
1096
   * @returns {number} - Number of subscribers on the given topic.
1097
   */
1098
  countSubscribers(topic) {
1099
    let expandedTopic = rclnodejs.expandTopicName(
18✔
1100
      topic,
1101
      this.name(),
1102
      this.namespace()
1103
    );
1104
    rclnodejs.validateTopicName(expandedTopic);
18✔
1105

1106
    return rclnodejs.countSubscribers(this.handle, expandedTopic);
18✔
1107
  }
1108

1109
  /**
1110
   * Get the number of clients on a given service name.
1111
   * @param {string} serviceName - the service name
1112
   * @returns {Number}
1113
   */
1114
  countClients(serviceName) {
1115
    if (DistroUtils.getDistroId() <= DistroUtils.getDistroId('humble')) {
6!
1116
      console.warn('countClients is not supported by this version of ROS 2');
×
1117
      return null;
×
1118
    }
1119
    return rclnodejs.countClients(this.handle, serviceName);
6✔
1120
  }
1121

1122
  /**
1123
   * Get the number of services on a given service name.
1124
   * @param {string} serviceName - the service name
1125
   * @returns {Number}
1126
   */
1127
  countServices(serviceName) {
1128
    if (DistroUtils.getDistroId() <= DistroUtils.getDistroId('humble')) {
3!
1129
      console.warn('countServices is not supported by this version of ROS 2');
×
1130
      return null;
×
1131
    }
1132
    return rclnodejs.countServices(this.handle, serviceName);
3✔
1133
  }
1134

1135
  /**
1136
   * Get the list of parameter-overrides found on the commandline and
1137
   * in the NodeOptions.parameter_overrides property.
1138
   *
1139
   * @return {Array<Parameter>} - An array of Parameters.
1140
   */
1141
  getParameterOverrides() {
1142
    return Array.from(this._parameterOverrides.values());
24✔
1143
  }
1144

1145
  /**
1146
   * Declare a parameter.
1147
   *
1148
   * Internally, register a parameter and it's descriptor.
1149
   * If a parameter-override exists, it's value will replace that of the parameter
1150
   * unless ignoreOverride is true.
1151
   * If the descriptor is undefined, then a ParameterDescriptor will be inferred
1152
   * from the parameter's state.
1153
   *
1154
   * If a parameter by the same name has already been declared then an Error is thrown.
1155
   * A parameter must be undeclared before attempting to redeclare it.
1156
   *
1157
   * @param {Parameter} parameter - Parameter to declare.
1158
   * @param {ParameterDescriptor} [descriptor] - Optional descriptor for parameter.
1159
   * @param {boolean} [ignoreOveride] - When true disregard any parameter-override that may be present.
1160
   * @return {Parameter} - The newly declared parameter.
1161
   */
1162
  declareParameter(parameter, descriptor, ignoreOveride = false) {
1,824✔
1163
    const parameters = this.declareParameters(
1,827✔
1164
      [parameter],
1165
      descriptor ? [descriptor] : [],
1,827✔
1166
      ignoreOveride
1167
    );
1168
    return parameters.length == 1 ? parameters[0] : null;
1,827!
1169
  }
1170

1171
  /**
1172
   * Declare a list of parameters.
1173
   *
1174
   * Internally register parameters with their corresponding descriptor one by one
1175
   * in the order they are provided. This is an atomic operation. If an error
1176
   * occurs the process halts and no further parameters are declared.
1177
   * Parameters that have already been processed are undeclared.
1178
   *
1179
   * While descriptors is an optional parameter, when provided there must be
1180
   * a descriptor for each parameter; otherwise an Error is thrown.
1181
   * If descriptors is not provided then a descriptor will be inferred
1182
   * from each parameter's state.
1183
   *
1184
   * When a parameter-override is available, the parameter's value
1185
   * will be replaced with that of the parameter-override unless ignoreOverrides
1186
   * is true.
1187
   *
1188
   * If a parameter by the same name has already been declared then an Error is thrown.
1189
   * A parameter must be undeclared before attempting to redeclare it.
1190
   *
1191
   * Prior to declaring the parameters each SetParameterEventCallback registered
1192
   * using setOnParameterEventCallback() is called in succession with the parameters
1193
   * list. Any SetParameterEventCallback that retuns does not return a successful
1194
   * result will cause the entire operation to terminate with no changes to the
1195
   * parameters. When all SetParameterEventCallbacks return successful then the
1196
   * list of parameters is updated.
1197
   *
1198
   * @param {Parameter[]} parameters - The parameters to declare.
1199
   * @param {ParameterDescriptor[]} [descriptors] - Optional descriptors,
1200
   *    a 1-1 correspondence with parameters.
1201
   * @param {boolean} ignoreOverrides - When true, parameter-overrides are
1202
   *    not considered, i.e.,ignored.
1203
   * @return {Parameter[]} - The declared parameters.
1204
   */
1205
  declareParameters(parameters, descriptors = [], ignoreOverrides = false) {
×
1206
    if (!Array.isArray(parameters)) {
1,827!
1207
      throw new TypeError('Invalid parameter: expected array of Parameter');
×
1208
    }
1209
    if (!Array.isArray(descriptors)) {
1,827!
1210
      throw new TypeError(
×
1211
        'Invalid parameters: expected array of ParameterDescriptor'
1212
      );
1213
    }
1214
    if (descriptors.length > 0 && parameters.length !== descriptors.length) {
1,827!
1215
      throw new TypeError(
×
1216
        'Each parameter must have a cooresponding ParameterDescriptor'
1217
      );
1218
    }
1219

1220
    const declaredDescriptors = [];
1,827✔
1221
    const declaredParameters = [];
1,827✔
1222
    const declaredParameterCollisions = [];
1,827✔
1223
    for (let i = 0; i < parameters.length; i++) {
1,827✔
1224
      let parameter =
1225
        !ignoreOverrides && this._parameterOverrides.has(parameters[i].name)
1,827✔
1226
          ? this._parameterOverrides.get(parameters[i].name)
1227
          : parameters[i];
1228

1229
      // stop processing parameters that have already been declared
1230
      if (this._parameters.has(parameter.name)) {
1,827!
1231
        declaredParameterCollisions.push(parameter);
×
1232
        continue;
×
1233
      }
1234

1235
      // create descriptor for parameter if not provided
1236
      let descriptor =
1237
        descriptors.length > 0
1,827✔
1238
          ? descriptors[i]
1239
          : ParameterDescriptor.fromParameter(parameter);
1240

1241
      descriptor.validate();
1,827✔
1242

1243
      declaredDescriptors.push(descriptor);
1,827✔
1244
      declaredParameters.push(parameter);
1,827✔
1245
    }
1246

1247
    if (declaredParameterCollisions.length > 0) {
1,827!
1248
      const errorMsg =
1249
        declaredParameterCollisions.length == 1
×
1250
          ? `Parameter(${declaredParameterCollisions[0]}) already declared.`
1251
          : `Multiple parameters already declared, e.g., Parameter(${declaredParameterCollisions[0]}).`;
1252
      throw new Error(errorMsg);
×
1253
    }
1254

1255
    // register descriptor
1256
    for (const descriptor of declaredDescriptors) {
1,827✔
1257
      this._parameterDescriptors.set(descriptor.name, descriptor);
1,827✔
1258
    }
1259

1260
    const result = this._setParametersAtomically(declaredParameters, true);
1,827✔
1261
    if (!result.successful) {
1,827!
1262
      // unregister descriptors
1263
      for (const descriptor of declaredDescriptors) {
×
1264
        this._parameterDescriptors.delete(descriptor.name);
×
1265
      }
1266

1267
      throw new Error(result.reason);
×
1268
    }
1269

1270
    return this.getParameters(declaredParameters.map((param) => param.name));
1,827✔
1271
  }
1272

1273
  /**
1274
   * Undeclare a parameter.
1275
   *
1276
   * Readonly parameters can not be undeclared or updated.
1277
   * @param {string} name - Name of parameter to undeclare.
1278
   * @return {undefined} -
1279
   */
1280
  undeclareParameter(name) {
1281
    if (!this.hasParameter(name)) return;
3!
1282

1283
    const descriptor = this.getParameterDescriptor(name);
3✔
1284
    if (descriptor.readOnly) {
3!
1285
      throw new Error(
×
1286
        `${name} parameter is read-only and can not be undeclared`
1287
      );
1288
    }
1289

1290
    this._parameters.delete(name);
3✔
1291
    this._parameterDescriptors.delete(name);
3✔
1292
  }
1293

1294
  /**
1295
   * Determine if a parameter has been declared.
1296
   * @param {string} name - name of parameter
1297
   * @returns {boolean} - Return true if parameter is declared; false otherwise.
1298
   */
1299
  hasParameter(name) {
1300
    return this._parameters.has(name);
5,340✔
1301
  }
1302

1303
  /**
1304
   * Get a declared parameter by name.
1305
   *
1306
   * If unable to locate a declared parameter then a
1307
   * parameter with type == PARAMETER_NOT_SET is returned.
1308
   *
1309
   * @param {string} name - The name of the parameter.
1310
   * @return {Parameter} - The parameter.
1311
   */
1312
  getParameter(name) {
1313
    return this.getParameters([name])[0];
1,758✔
1314
  }
1315

1316
  /**
1317
   * Get a list of parameters.
1318
   *
1319
   * Find and return the declared parameters.
1320
   * If no names are provided return all declared parameters.
1321
   *
1322
   * If unable to locate a declared parameter then a
1323
   * parameter with type == PARAMETER_NOT_SET is returned in
1324
   * it's place.
1325
   *
1326
   * @param {string[]} [names] - The names of the declared parameters
1327
   *    to find or null indicating to return all declared parameters.
1328
   * @return {Parameter[]} - The parameters.
1329
   */
1330
  getParameters(names = []) {
24✔
1331
    let params = [];
3,612✔
1332

1333
    if (names.length == 0) {
3,612✔
1334
      // get all parameters
1335
      params = [...this._parameters.values()];
24✔
1336
      return params;
24✔
1337
    }
1338

1339
    for (const name of names) {
3,588✔
1340
      const param = this.hasParameter(name)
3,591!
1341
        ? this._parameters.get(name)
1342
        : new Parameter(name, ParameterType.PARAMETER_NOT_SET);
1343

1344
      params.push(param);
3,591✔
1345
    }
1346

1347
    return params;
3,588✔
1348
  }
1349

1350
  /**
1351
   * Get the types of given parameters.
1352
   *
1353
   * Return the types of given parameters.
1354
   *
1355
   * @param {string[]} [names] - The names of the declared parameters.
1356
   * @return {Uint8Array} - The types.
1357
   */
1358
  getParameterTypes(names = []) {
×
1359
    let types = [];
3✔
1360

1361
    for (const name of names) {
3✔
1362
      const descriptor = this._parameterDescriptors.get(name);
9✔
1363
      if (descriptor) {
9!
1364
        types.push(descriptor.type);
9✔
1365
      }
1366
    }
1367
    return types;
3✔
1368
  }
1369

1370
  /**
1371
   * Get the names of all declared parameters.
1372
   *
1373
   * @return {Array<string>} - The declared parameter names or empty array if
1374
   *    no parameters have been declared.
1375
   */
1376
  getParameterNames() {
1377
    return this.getParameters().map((param) => param.name);
45✔
1378
  }
1379

1380
  /**
1381
   * Determine if a parameter descriptor exists.
1382
   *
1383
   * @param {string} name - The name of a descriptor to for.
1384
   * @return {boolean} - True if a descriptor has been declared; otherwise false.
1385
   */
1386
  hasParameterDescriptor(name) {
1387
    return !!this.getParameterDescriptor(name);
1,845✔
1388
  }
1389

1390
  /**
1391
   * Get a declared parameter descriptor by name.
1392
   *
1393
   * If unable to locate a declared parameter descriptor then a
1394
   * descriptor with type == PARAMETER_NOT_SET is returned.
1395
   *
1396
   * @param {string} name - The name of the parameter descriptor to find.
1397
   * @return {ParameterDescriptor} - The parameter descriptor.
1398
   */
1399
  getParameterDescriptor(name) {
1400
    return this.getParameterDescriptors([name])[0];
3,693✔
1401
  }
1402

1403
  /**
1404
   * Find a list of declared ParameterDescriptors.
1405
   *
1406
   * If no names are provided return all declared descriptors.
1407
   *
1408
   * If unable to locate a declared descriptor then a
1409
   * descriptor with type == PARAMETER_NOT_SET is returned in
1410
   * it's place.
1411
   *
1412
   * @param {string[]} [names] - The names of the declared parameter
1413
   *    descriptors to find or null indicating to return all declared descriptors.
1414
   * @return {ParameterDescriptor[]} - The parameter descriptors.
1415
   */
1416
  getParameterDescriptors(names = []) {
×
1417
    let descriptors = [];
3,696✔
1418

1419
    if (names.length == 0) {
3,696!
1420
      // get all parameters
1421
      descriptors = [...this._parameterDescriptors.values()];
×
1422
      return descriptors;
×
1423
    }
1424

1425
    for (const name of names) {
3,696✔
1426
      let descriptor = this._parameterDescriptors.get(name);
3,699✔
1427
      if (!descriptor) {
3,699!
1428
        descriptor = new ParameterDescriptor(
×
1429
          name,
1430
          ParameterType.PARAMETER_NOT_SET
1431
        );
1432
      }
1433
      descriptors.push(descriptor);
3,699✔
1434
    }
1435

1436
    return descriptors;
3,696✔
1437
  }
1438

1439
  /**
1440
   * Replace a declared parameter.
1441
   *
1442
   * The parameter being replaced must be a declared parameter who's descriptor
1443
   * is not readOnly; otherwise an Error is thrown.
1444
   *
1445
   * @param {Parameter} parameter - The new parameter.
1446
   * @return {rclnodejs.rcl_interfaces.msg.SetParameterResult} - The result of the operation.
1447
   */
1448
  setParameter(parameter) {
1449
    const results = this.setParameters([parameter]);
12✔
1450
    return results[0];
12✔
1451
  }
1452

1453
  /**
1454
   * Replace a list of declared parameters.
1455
   *
1456
   * Declared parameters are replaced in the order they are provided and
1457
   * a ParameterEvent is published for each individual parameter change.
1458
   *
1459
   * Prior to setting the parameters each SetParameterEventCallback registered
1460
   * using setOnParameterEventCallback() is called in succession with the parameters
1461
   * list. Any SetParameterEventCallback that retuns does not return a successful
1462
   * result will cause the entire operation to terminate with no changes to the
1463
   * parameters. When all SetParameterEventCallbacks return successful then the
1464
   * list of parameters is updated.
1465
   *
1466
   * If an error occurs, the process is stopped and returned. Parameters
1467
   * set before an error remain unchanged.
1468
   *
1469
   * @param {Parameter[]} parameters - The parameters to set.
1470
   * @return {rclnodejs.rcl_interfaces.msg.SetParameterResult[]} - A list of SetParameterResult, one for each parameter that was set.
1471
   */
1472
  setParameters(parameters = []) {
×
1473
    return parameters.map((parameter) =>
15✔
1474
      this.setParametersAtomically([parameter])
15✔
1475
    );
1476
  }
1477

1478
  /**
1479
   * Repalce a list of declared parameters atomically.
1480
   *
1481
   * Declared parameters are replaced in the order they are provided.
1482
   * A single ParameterEvent is published collectively for all changed
1483
   * parameters.
1484
   *
1485
   * Prior to setting the parameters each SetParameterEventCallback registered
1486
   * using setOnParameterEventCallback() is called in succession with the parameters
1487
   * list. Any SetParameterEventCallback that retuns does not return a successful
1488
   * result will cause the entire operation to terminate with no changes to the
1489
   * parameters. When all SetParameterEventCallbacks return successful then the
1490
   * list of parameters is updated.d
1491
   *
1492
   * If an error occurs, the process stops immediately. All parameters updated to
1493
   * the point of the error are reverted to their previous state.
1494
   *
1495
   * @param {Parameter[]} parameters - The parameters to set.
1496
   * @return {rclnodejs.rcl_interfaces.msg.SetParameterResult} - describes the result of setting 1 or more parameters.
1497
   */
1498
  setParametersAtomically(parameters = []) {
×
1499
    return this._setParametersAtomically(parameters);
18✔
1500
  }
1501

1502
  /**
1503
   * Internal method for updating parameters atomically.
1504
   *
1505
   * Prior to setting the parameters each SetParameterEventCallback registered
1506
   * using setOnParameterEventCallback() is called in succession with the parameters
1507
   * list. Any SetParameterEventCallback that retuns does not return a successful
1508
   * result will cause the entire operation to terminate with no changes to the
1509
   * parameters. When all SetParameterEventCallbacks return successful then the
1510
   * list of parameters is updated.
1511
   *
1512
   * @param {Paramerter[]} parameters - The parameters to update.
1513
   * @param {boolean} declareParameterMode - When true parameters are being declared;
1514
   *    otherwise they are being changed.
1515
   * @return {SetParameterResult} - A single collective result.
1516
   */
1517
  _setParametersAtomically(parameters = [], declareParameterMode = false) {
18!
1518
    let result = this._validateParameters(parameters, declareParameterMode);
1,845✔
1519
    if (!result.successful) {
1,845!
1520
      return result;
×
1521
    }
1522

1523
    // give all SetParametersCallbacks a chance to veto this change
1524
    for (const callback of this._setParametersCallbacks) {
1,845✔
1525
      result = callback(parameters);
141✔
1526
      if (!result.successful) {
141✔
1527
        // a callback has vetoed a parameter change
1528
        return result;
3✔
1529
      }
1530
    }
1531

1532
    // collectively track updates to parameters for use
1533
    // when publishing a ParameterEvent
1534
    const newParameters = [];
1,842✔
1535
    const changedParameters = [];
1,842✔
1536
    const deletedParameters = [];
1,842✔
1537

1538
    for (const parameter of parameters) {
1,842✔
1539
      if (parameter.type == ParameterType.PARAMETER_NOT_SET) {
1,842✔
1540
        this.undeclareParameter(parameter.name);
3✔
1541
        deletedParameters.push(parameter);
3✔
1542
      } else {
1543
        this._parameters.set(parameter.name, parameter);
1,839✔
1544
        if (declareParameterMode) {
1,839✔
1545
          newParameters.push(parameter);
1,827✔
1546
        } else {
1547
          changedParameters.push(parameter);
12✔
1548
        }
1549
      }
1550
    }
1551

1552
    // create ParameterEvent
1553
    const parameterEvent = new (loader.loadInterface(
1,842✔
1554
      PARAMETER_EVENT_MSG_TYPE
1555
    ))();
1556

1557
    const { seconds, nanoseconds } = this._clock.now().secondsAndNanoseconds;
1,842✔
1558
    parameterEvent.stamp = {
1,842✔
1559
      sec: Number(seconds),
1560
      nanosec: Number(nanoseconds),
1561
    };
1562

1563
    parameterEvent.node =
1,842✔
1564
      this.namespace() === '/'
1,842✔
1565
        ? this.namespace() + this.name()
1566
        : this.namespace() + '/' + this.name();
1567

1568
    if (newParameters.length > 0) {
1,842✔
1569
      parameterEvent['new_parameters'] = newParameters.map((parameter) =>
1,827✔
1570
        parameter.toParameterMessage()
1,827✔
1571
      );
1572
    }
1573
    if (changedParameters.length > 0) {
1,842✔
1574
      parameterEvent['changed_parameters'] = changedParameters.map(
12✔
1575
        (parameter) => parameter.toParameterMessage()
12✔
1576
      );
1577
    }
1578
    if (deletedParameters.length > 0) {
1,842✔
1579
      parameterEvent['deleted_parameters'] = deletedParameters.map(
3✔
1580
        (parameter) => parameter.toParameterMessage()
3✔
1581
      );
1582
    }
1583

1584
    // Publish ParameterEvent.
1585
    this._parameterEventPublisher.publish(parameterEvent);
1,842✔
1586

1587
    return {
1,842✔
1588
      successful: true,
1589
      reason: '',
1590
    };
1591
  }
1592

1593
  /**
1594
   * This callback is called when declaring a parameter or setting a parameter.
1595
   * The callback is provided a list of parameters and returns a SetParameterResult
1596
   * to indicate approval or veto of the operation.
1597
   *
1598
   * @callback SetParametersCallback
1599
   * @param {Parameter[]} parameters - The message published
1600
   * @returns {rcl_interfaces.msg.SetParameterResult} -
1601
   *
1602
   * @see [Node.addOnSetParametersCallback]{@link Node#addOnSetParametersCallback}
1603
   * @see [Node.removeOnSetParametersCallback]{@link Node#removeOnSetParametersCallback}
1604
   */
1605

1606
  /**
1607
   * Add a callback to the front of the list of callbacks invoked for parameter declaration
1608
   * and setting. No checks are made for duplicate callbacks.
1609
   *
1610
   * @param {SetParametersCallback} callback - The callback to add.
1611
   * @returns {undefined}
1612
   */
1613
  addOnSetParametersCallback(callback) {
1614
    this._setParametersCallbacks.unshift(callback);
1,746✔
1615
  }
1616

1617
  /**
1618
   * Remove a callback from the list of SetParametersCallbacks.
1619
   * If the callback is not found the process is a nop.
1620
   *
1621
   * @param {SetParametersCallback} callback - The callback to be removed
1622
   * @returns {undefined}
1623
   */
1624
  removeOnSetParametersCallback(callback) {
1625
    const idx = this._setParametersCallbacks.indexOf(callback);
6✔
1626
    if (idx > -1) {
6!
1627
      this._setParametersCallbacks.splice(idx, 1);
6✔
1628
    }
1629
  }
1630

1631
  /**
1632
   * Get the fully qualified name of the node.
1633
   *
1634
   * @returns {string} - String containing the fully qualified name of the node.
1635
   */
1636
  getFullyQualifiedName() {
1637
    return rclnodejs.getFullyQualifiedName(this.handle);
3✔
1638
  }
1639

1640
  // returns on 1st error or result {successful, reason}
1641
  _validateParameters(parameters = [], declareParameterMode = false) {
×
1642
    for (const parameter of parameters) {
1,845✔
1643
      // detect invalid parameter
1644
      try {
1,845✔
1645
        parameter.validate();
1,845✔
1646
      } catch {
1647
        return {
×
1648
          successful: false,
1649
          reason: `Invalid ${parameter.name}`,
1650
        };
1651
      }
1652

1653
      // detect undeclared parameter
1654
      if (!this.hasParameterDescriptor(parameter.name)) {
1,845!
1655
        return {
×
1656
          successful: false,
1657
          reason: `Parameter ${parameter.name} has not been declared`,
1658
        };
1659
      }
1660

1661
      // detect readonly parameter that can not be updated
1662
      const descriptor = this.getParameterDescriptor(parameter.name);
1,845✔
1663
      if (!declareParameterMode && descriptor.readOnly) {
1,845!
1664
        return {
×
1665
          successful: false,
1666
          reason: `Parameter ${parameter.name} is readonly`,
1667
        };
1668
      }
1669

1670
      // validate parameter against descriptor if not an undeclare action
1671
      if (parameter.type != ParameterType.PARAMETER_NOT_SET) {
1,845✔
1672
        try {
1,842✔
1673
          descriptor.validateParameter(parameter);
1,842✔
1674
        } catch {
1675
          return {
×
1676
            successful: false,
1677
            reason: `Parameter ${parameter.name} does not  readonly`,
1678
          };
1679
        }
1680
      }
1681
    }
1682

1683
    return {
1,845✔
1684
      successful: true,
1685
      reason: null,
1686
    };
1687
  }
1688

1689
  // Get a Map(nodeName->Parameter[]) of CLI parameter args that
1690
  // apply to 'this' node, .e.g., -p mynode:foo:=bar -p hello:=world
1691
  _getNativeParameterOverrides() {
1692
    const overrides = new Map();
1,722✔
1693

1694
    // Get native parameters from rcl context->global_arguments.
1695
    // rclnodejs returns an array of objects, 1 for each node e.g., -p my_node:foo:=bar,
1696
    // and a node named '/**' for global parameter rules,
1697
    // i.e., does not include a node identifier, e.g., -p color:=red
1698
    // {
1699
    //   name: string // node name
1700
    //   parameters[] = {
1701
    //     name: string
1702
    //     type: uint
1703
    //     value: object
1704
    // }
1705
    const cliParamOverrideData = rclnodejs.getParameterOverrides(
1,722✔
1706
      this.context.handle
1707
    );
1708

1709
    // convert native CLI parameterOverrides to Map<nodeName,Array<ParameterOverride>>
1710
    const cliParamOverrides = new Map();
1,722✔
1711
    if (cliParamOverrideData) {
1,722✔
1712
      for (let nodeParamData of cliParamOverrideData) {
24✔
1713
        const nodeName = nodeParamData.name;
36✔
1714
        const nodeParamOverrides = [];
36✔
1715
        for (let paramData of nodeParamData.parameters) {
36✔
1716
          const paramOverride = new Parameter(
51✔
1717
            paramData.name,
1718
            paramData.type,
1719
            paramData.value
1720
          );
1721
          nodeParamOverrides.push(paramOverride);
51✔
1722
        }
1723
        cliParamOverrides.set(nodeName, nodeParamOverrides);
36✔
1724
      }
1725
    }
1726

1727
    // collect global CLI global parameters, name == /**
1728
    let paramOverrides = cliParamOverrides.get('/**'); // array of ParameterOverrides
1,722✔
1729
    if (paramOverrides) {
1,722✔
1730
      for (const parameter of paramOverrides) {
15✔
1731
        overrides.set(parameter.name, parameter);
18✔
1732
      }
1733
    }
1734

1735
    // merge CLI node parameterOverrides with global parameterOverrides, replace existing
1736
    paramOverrides = cliParamOverrides.get(this.name()); // array of ParameterOverrides
1,722✔
1737
    if (paramOverrides) {
1,722✔
1738
      for (const parameter of paramOverrides) {
15✔
1739
        overrides.set(parameter.name, parameter);
21✔
1740
      }
1741
    }
1742

1743
    return overrides;
1,722✔
1744
  }
1745

1746
  /**
1747
   * Invokes the callback with a raw message of the given type. After the callback completes
1748
   * the message will be destroyed.
1749
   * @param {function} Type - Message type to create.
1750
   * @param {function} callback - Callback to invoke. First parameter will be the raw message,
1751
   * and the second is a function to retrieve the deserialized message.
1752
   * @returns {undefined}
1753
   */
1754
  _runWithMessageType(Type, callback) {
1755
    let message = new Type();
2,116✔
1756

1757
    callback(message.toRawROS(), () => {
2,116✔
1758
      let result = new Type();
1,666✔
1759
      result.deserialize(message.refObject);
1,666✔
1760

1761
      return result;
1,666✔
1762
    });
1763

1764
    Type.destoryRawROS(message);
2,116✔
1765
  }
1766

1767
  _addActionClient(actionClient) {
1768
    this._actionClients.push(actionClient);
123✔
1769
    this.syncHandles();
123✔
1770
  }
1771

1772
  _addActionServer(actionServer) {
1773
    this._actionServers.push(actionServer);
123✔
1774
    this.syncHandles();
123✔
1775
  }
1776
}
1777

1778
/**
1779
 * Create an Options instance initialized with default values.
1780
 * @returns {Options} - The new initialized instance.
1781
 * @static
1782
 * @example
1783
 * {
1784
 *   enableTypedArray: true,
1785
 *   isRaw: false,
1786
 *   qos: QoS.profileDefault,
1787
 *   contentFilter: undefined,
1788
 * }
1789
 */
1790
Node.getDefaultOptions = function () {
78✔
1791
  return {
16,383✔
1792
    enableTypedArray: true,
1793
    isRaw: false,
1794
    qos: QoS.profileDefault,
1795
    contentFilter: undefined,
1796
  };
1797
};
1798

1799
module.exports = Node;
78✔
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

© 2025 Coveralls, Inc