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

RobotWebTools / rclnodejs / 15815975751

23 Jun 2025 05:24AM UTC coverage: 84.579% (-0.02%) from 84.603%
15815975751

Pull #1177

github

web-flow
Merge a1f4f4035 into ba7cdc01f
Pull Request #1177: Validate and remap the topic name for publishers/subscriptions

775 of 1007 branches covered (76.96%)

Branch coverage included in aggregate %.

6 of 7 new or added lines in 1 file covered. (85.71%)

25 existing lines in 1 file now uncovered.

1907 of 2164 relevant lines covered (88.12%)

3201.1 hits per line

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

88.55
/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');
208✔
18

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

48
// Parameter event publisher constants
49
const PARAMETER_EVENT_MSG_TYPE = 'rcl_interfaces/msg/ParameterEvent';
208✔
50
const PARAMETER_EVENT_TOPIC = 'parameter_events';
208✔
51

52
/**
53
 * @class - Class representing a Node in ROS
54
 */
55

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

76
    if (typeof nodeName !== 'string' || typeof namespace !== 'string') {
4,910✔
77
      throw new TypeError('Invalid argument.');
176✔
78
    }
79

80
    this._init(nodeName, namespace, options, context, args, useGlobalArguments);
4,734✔
81
    debug(
4,662✔
82
      'Finish initializing node, name = %s and namespace = %s.',
83
      nodeName,
84
      namespace
85
    );
86
  }
87

88
  _init(name, namespace, options, context, args, useGlobalArguments) {
89
    this.handle = rclnodejs.createNode(
4,734✔
90
      name,
91
      namespace,
92
      context.handle,
93
      args,
94
      useGlobalArguments
95
    );
96
    Object.defineProperty(this, 'handle', {
4,662✔
97
      configurable: false,
98
      writable: false,
99
    }); // make read-only
100

101
    this._context = context;
4,662✔
102
    this.context.onNodeCreated(this);
4,662✔
103

104
    this._publishers = [];
4,662✔
105
    this._subscriptions = [];
4,662✔
106
    this._clients = [];
4,662✔
107
    this._services = [];
4,662✔
108
    this._timers = [];
4,662✔
109
    this._guards = [];
4,662✔
110
    this._events = [];
4,662✔
111
    this._actionClients = [];
4,662✔
112
    this._actionServers = [];
4,662✔
113
    this._rateTimerServer = null;
4,662✔
114
    this._parameterDescriptors = new Map();
4,662✔
115
    this._parameters = new Map();
4,662✔
116
    this._parameterService = null;
4,662✔
117
    this._typeDescriptionService = null;
4,662✔
118
    this._parameterEventPublisher = null;
4,662✔
119
    this._setParametersCallbacks = [];
4,662✔
120
    this._logger = new Logging(rclnodejs.getNodeLoggerName(this.handle));
4,662✔
121
    this._spinning = false;
4,662✔
122

123
    this._parameterEventPublisher = this.createPublisher(
4,662✔
124
      PARAMETER_EVENT_MSG_TYPE,
125
      PARAMETER_EVENT_TOPIC
126
    );
127

128
    // initialize _parameterOverrides from parameters defined on the commandline
129
    this._parameterOverrides = this._getNativeParameterOverrides();
4,662✔
130

131
    // override cli parameterOverrides with those specified in options
132
    if (options.parameterOverrides.length > 0) {
4,662✔
133
      for (const parameter of options.parameterOverrides) {
64✔
134
        if ((!parameter) instanceof Parameter) {
94!
135
          throw new TypeError(
×
136
            'Parameter-override must be an instance of Parameter.'
137
          );
138
        }
139
        this._parameterOverrides.set(parameter.name, parameter);
94✔
140
      }
141
    }
142

143
    // initialize _parameters from parameterOverrides
144
    if (options.automaticallyDeclareParametersFromOverrides) {
4,662✔
145
      for (const parameter of this._parameterOverrides.values()) {
40✔
146
        parameter.validate();
70✔
147
        const descriptor = ParameterDescriptor.fromParameter(parameter);
70✔
148
        this._parameters.set(parameter.name, parameter);
70✔
149
        this._parameterDescriptors.set(parameter.name, descriptor);
70✔
150
      }
151
    }
152

153
    // Clock that has support for ROS time.
154
    // Note: parameter overrides and parameter event publisher need to be ready at this point
155
    // to be able to declare 'use_sim_time' if it was not declared yet.
156
    this._clock = new Clock.ROSClock();
4,662✔
157
    this._timeSource = new TimeSource(this);
4,662✔
158
    this._timeSource.attachClock(this._clock);
4,662✔
159

160
    if (options.startParameterServices) {
4,662✔
161
      this._parameterService = new ParameterService(this);
4,614✔
162
      this._parameterService.start();
4,614✔
163
    }
164

165
    if (
4,662✔
166
      DistroUtils.getDistroId() >= DistroUtils.getDistroId('jazzy') &&
8,184✔
167
      options.startTypeDescriptionService
168
    ) {
169
      this._typeDescriptionService = new TypeDescriptionService(this);
3,522✔
170
      this._typeDescriptionService.start();
3,522✔
171
    }
172
  }
173

174
  execute(handles) {
175
    let timersReady = this._timers.filter((timer) =>
36,337✔
176
      handles.includes(timer.handle)
30,992✔
177
    );
178
    let guardsReady = this._guards.filter((guard) =>
36,337✔
179
      handles.includes(guard.handle)
24✔
180
    );
181
    let subscriptionsReady = this._subscriptions.filter((subscription) =>
36,337✔
182
      handles.includes(subscription.handle)
3,635✔
183
    );
184
    let clientsReady = this._clients.filter((client) =>
36,337✔
185
      handles.includes(client.handle)
744✔
186
    );
187
    let servicesReady = this._services.filter((service) =>
36,337✔
188
      handles.includes(service.handle)
103,778✔
189
    );
190
    let actionClientsReady = this._actionClients.filter((actionClient) =>
36,337✔
191
      handles.includes(actionClient.handle)
1,252✔
192
    );
193
    let actionServersReady = this._actionServers.filter((actionServer) =>
36,337✔
194
      handles.includes(actionServer.handle)
1,252✔
195
    );
196
    let eventsReady = this._events.filter((event) =>
36,337✔
197
      handles.includes(event.handle)
24✔
198
    );
199

200
    timersReady.forEach((timer) => {
36,337✔
201
      if (timer.isReady()) {
30,912✔
202
        rclnodejs.callTimer(timer.handle);
30,614✔
203
        timer.callback();
30,614✔
204
      }
205
    });
206

207
    eventsReady.forEach((event) => {
36,337✔
208
      event.takeData();
24✔
209
    });
210

211
    for (const subscription of subscriptionsReady) {
36,337✔
212
      if (subscription.isDestroyed()) continue;
3,351✔
213
      if (subscription.isRaw) {
3,270✔
214
        let rawMessage = rclnodejs.rclTakeRaw(subscription.handle);
8✔
215
        if (rawMessage) {
8!
216
          subscription.processResponse(rawMessage);
8✔
217
        }
218
        continue;
8✔
219
      }
220

221
      this._runWithMessageType(
3,262✔
222
        subscription.typeClass,
223
        (message, deserialize) => {
224
          let success = rclnodejs.rclTake(subscription.handle, message);
3,262✔
225
          if (success) {
3,262✔
226
            subscription.processResponse(deserialize());
2,825✔
227
          }
228
        }
229
      );
230
    }
231

232
    for (const guard of guardsReady) {
36,337✔
233
      if (guard.isDestroyed()) continue;
24!
234

235
      guard.callback();
24✔
236
    }
237

238
    for (const client of clientsReady) {
36,337✔
239
      if (client.isDestroyed()) continue;
382!
240
      this._runWithMessageType(
382✔
241
        client.typeClass.Response,
242
        (message, deserialize) => {
243
          let sequenceNumber = rclnodejs.rclTakeResponse(
382✔
244
            client.handle,
245
            message
246
          );
247
          if (sequenceNumber !== undefined) {
382✔
248
            client.processResponse(sequenceNumber, deserialize());
232✔
249
          }
250
        }
251
      );
252
    }
253

254
    for (const service of servicesReady) {
36,337✔
255
      if (service.isDestroyed()) continue;
492!
256
      this._runWithMessageType(
492✔
257
        service.typeClass.Request,
258
        (message, deserialize) => {
259
          let header = rclnodejs.rclTakeRequest(
492✔
260
            service.handle,
261
            this.handle,
262
            message
263
          );
264
          if (header) {
492✔
265
            service.processRequest(header, deserialize());
254✔
266
          }
267
        }
268
      );
269
    }
270

271
    for (const actionClient of actionClientsReady) {
36,337✔
272
      if (actionClient.isDestroyed()) continue;
592!
273

274
      const properties = actionClient.handle.properties;
592✔
275

276
      if (properties.isGoalResponseReady) {
592✔
277
        this._runWithMessageType(
282✔
278
          actionClient.typeClass.impl.SendGoalService.Response,
279
          (message, deserialize) => {
280
            let sequence = rclnodejs.actionTakeGoalResponse(
282✔
281
              actionClient.handle,
282
              message
283
            );
284
            if (sequence != undefined) {
282✔
285
              actionClient.processGoalResponse(sequence, deserialize());
240✔
286
            }
287
          }
288
        );
289
      }
290

291
      if (properties.isCancelResponseReady) {
592✔
292
        this._runWithMessageType(
51✔
293
          actionClient.typeClass.impl.CancelGoal.Response,
294
          (message, deserialize) => {
295
            let sequence = rclnodejs.actionTakeCancelResponse(
51✔
296
              actionClient.handle,
297
              message
298
            );
299
            if (sequence != undefined) {
51✔
300
              actionClient.processCancelResponse(sequence, deserialize());
32✔
301
            }
302
          }
303
        );
304
      }
305

306
      if (properties.isResultResponseReady) {
592✔
307
        this._runWithMessageType(
123✔
308
          actionClient.typeClass.impl.GetResultService.Response,
309
          (message, deserialize) => {
310
            let sequence = rclnodejs.actionTakeResultResponse(
123✔
311
              actionClient.handle,
312
              message
313
            );
314
            if (sequence != undefined) {
123✔
315
              actionClient.processResultResponse(sequence, deserialize());
120✔
316
            }
317
          }
318
        );
319
      }
320

321
      if (properties.isFeedbackReady) {
592✔
322
        this._runWithMessageType(
61✔
323
          actionClient.typeClass.impl.FeedbackMessage,
324
          (message, deserialize) => {
325
            let success = rclnodejs.actionTakeFeedback(
61✔
326
              actionClient.handle,
327
              message
328
            );
329
            if (success) {
61✔
330
              actionClient.processFeedbackMessage(deserialize());
40✔
331
            }
332
          }
333
        );
334
      }
335

336
      if (properties.isStatusReady) {
592✔
337
        this._runWithMessageType(
311✔
338
          actionClient.typeClass.impl.GoalStatusArray,
339
          (message, deserialize) => {
340
            let success = rclnodejs.actionTakeStatus(
311✔
341
              actionClient.handle,
342
              message
343
            );
344
            if (success) {
311✔
345
              actionClient.processStatusMessage(deserialize());
267✔
346
            }
347
          }
348
        );
349
      }
350
    }
351

352
    for (const actionServer of actionServersReady) {
36,337✔
353
      if (actionServer.isDestroyed()) continue;
735!
354

355
      const properties = actionServer.handle.properties;
735✔
356

357
      if (properties.isGoalRequestReady) {
735✔
358
        this._runWithMessageType(
254✔
359
          actionServer.typeClass.impl.SendGoalService.Request,
360
          (message, deserialize) => {
361
            const result = rclnodejs.actionTakeGoalRequest(
254✔
362
              actionServer.handle,
363
              message
364
            );
365
            if (result) {
254✔
366
              actionServer.processGoalRequest(result, deserialize());
240✔
367
            }
368
          }
369
        );
370
      }
371

372
      if (properties.isCancelRequestReady) {
735✔
373
        this._runWithMessageType(
57✔
374
          actionServer.typeClass.impl.CancelGoal.Request,
375
          (message, deserialize) => {
376
            const result = rclnodejs.actionTakeCancelRequest(
57✔
377
              actionServer.handle,
378
              message
379
            );
380
            if (result) {
57✔
381
              actionServer.processCancelRequest(result, deserialize());
32✔
382
            }
383
          }
384
        );
385
      }
386

387
      if (properties.isResultRequestReady) {
735✔
388
        this._runWithMessageType(
178✔
389
          actionServer.typeClass.impl.GetResultService.Request,
390
          (message, deserialize) => {
391
            const result = rclnodejs.actionTakeResultRequest(
178✔
392
              actionServer.handle,
393
              message
394
            );
395
            if (result) {
178✔
396
              actionServer.processResultRequest(result, deserialize());
120✔
397
            }
398
          }
399
        );
400
      }
401

402
      if (properties.isGoalExpired) {
735✔
403
        let GoalInfoArray = ActionInterfaces.GoalInfo.ArrayType;
32✔
404
        let message = new GoalInfoArray(actionServer._goalHandles.size);
32✔
405
        let count = rclnodejs.actionExpireGoals(
32✔
406
          actionServer.handle,
407
          actionServer._goalHandles.size,
408
          message._refArray.buffer
409
        );
410
        if (count > 0) {
32✔
411
          actionServer.processGoalExpired(message, count);
24✔
412
        }
413
        GoalInfoArray.freeArray(message);
32✔
414
      }
415
    }
416

417
    // At this point it is safe to clear the cache of any
418
    // destroyed entity references
419
    Entity._gcHandles();
36,337✔
420
  }
421

422
  /**
423
   * Determine if this node is spinning.
424
   * @returns {boolean} - true when spinning; otherwise returns false.
425
   */
426
  get spinning() {
427
    return this._spinning;
32,352✔
428
  }
429

430
  /**
431
   * Trigger the event loop to continuously check for and route.
432
   * incoming events.
433
   * @param {Node} node - The node to be spun up.
434
   * @param {number} [timeout=10] - Timeout to wait in milliseconds. Block forever if negative. Don't wait if 0.
435
   * @throws {Error} If the node is already spinning.
436
   * @return {undefined}
437
   */
438
  spin(timeout = 10) {
106✔
439
    if (this.spinning) {
3,426!
440
      throw new Error('The node is already spinning.');
×
441
    }
442
    this.start(this.context.handle, timeout);
3,426✔
443
    this._spinning = true;
3,426✔
444
  }
445

446
  /**
447
   * Use spin().
448
   * @deprecated, since 0.18.0
449
   */
450
  startSpinning(timeout) {
451
    this.spin(timeout);
×
452
  }
453

454
  /**
455
   * Terminate spinning - no further events will be received.
456
   * @returns {undefined}
457
   */
458
  stop() {
459
    super.stop();
3,426✔
460
    this._spinning = false;
3,426✔
461
  }
462

463
  /**
464
   * Terminate spinning - no further events will be received.
465
   * @returns {undefined}
466
   * @deprecated since 0.18.0, Use stop().
467
   */
468
  stopSpinning() {
469
    super.stop();
×
470
    this._spinning = false;
×
471
  }
472

473
  /**
474
   * Spin the node and trigger the event loop to check for one incoming event. Thereafter the node
475
   * will not received additional events until running additional calls to spin() or spinOnce().
476
   * @param {Node} node - The node to be spun.
477
   * @param {number} [timeout=10] - Timeout to wait in milliseconds. Block forever if negative. Don't wait if 0.
478
   * @throws {Error} If the node is already spinning.
479
   * @return {undefined}
480
   */
481
  spinOnce(timeout = 10) {
16✔
482
    if (this.spinning) {
24,072✔
483
      throw new Error('The node is already spinning.');
16✔
484
    }
485
    super.spinOnce(this.context.handle, timeout);
24,056✔
486
  }
487

488
  _removeEntityFromArray(entity, array) {
489
    let index = array.indexOf(entity);
1,057✔
490
    if (index > -1) {
1,057✔
491
      array.splice(index, 1);
1,041✔
492
    }
493
  }
494

495
  _destroyEntity(entity, array, syncHandles = true) {
1,067✔
496
    if (entity['isDestroyed'] && entity.isDestroyed()) return;
1,121✔
497

498
    this._removeEntityFromArray(entity, array);
1,057✔
499
    if (syncHandles) {
1,057✔
500
      this.syncHandles();
1,019✔
501
    }
502

503
    if (entity['_destroy']) {
1,057✔
504
      entity._destroy();
1,009✔
505
    } else {
506
      // guards and timers
507
      entity.handle.release();
48✔
508
    }
509
  }
510

511
  _validateOptions(options) {
512
    if (
43,329✔
513
      options !== undefined &&
43,713✔
514
      (options === null || typeof options !== 'object')
515
    ) {
516
      throw new TypeError('Invalid argument of options');
160✔
517
    }
518

519
    if (options === undefined) {
43,169✔
520
      return Node.getDefaultOptions();
43,057✔
521
    }
522

523
    if (options.enableTypedArray === undefined) {
112✔
524
      options = Object.assign(options, { enableTypedArray: true });
16✔
525
    }
526

527
    if (options.qos === undefined) {
112✔
528
      options = Object.assign(options, { qos: QoS.profileDefault });
32✔
529
    }
530

531
    if (options.isRaw === undefined) {
112✔
532
      options = Object.assign(options, { isRaw: false });
32✔
533
    }
534

535
    return options;
112✔
536
  }
537

538
  /**
539
   * Create a Timer.
540
   * @param {bigint} period - The number representing period in nanoseconds.
541
   * @param {function} callback - The callback to be called when timeout.
542
   * @param {Clock} [clock] - The clock which the timer gets time from.
543
   * @return {Timer} - An instance of Timer.
544
   */
545
  createTimer(period, callback, clock = null) {
462✔
546
    if (arguments.length === 3 && !(arguments[2] instanceof Clock)) {
462!
547
      clock = null;
×
548
    } else if (arguments.length === 4) {
462!
549
      clock = arguments[3];
×
550
    }
551

552
    if (typeof period !== 'bigint' || typeof callback !== 'function') {
462✔
553
      throw new TypeError('Invalid argument');
16✔
554
    }
555

556
    const timerClock = clock || this._clock;
446✔
557
    let timerHandle = rclnodejs.createTimer(
446✔
558
      timerClock.handle,
559
      this.context.handle,
560
      period
561
    );
562
    let timer = new Timer(timerHandle, period, callback);
446✔
563
    debug('Finish creating timer, period = %d.', period);
446✔
564
    this._timers.push(timer);
446✔
565
    this.syncHandles();
446✔
566

567
    return timer;
446✔
568
  }
569

570
  /**
571
   * Create a Rate.
572
   *
573
   * @param {number} hz - The frequency of the rate timer; default is 1 hz.
574
   * @returns {Promise<Rate>} - Promise resolving to new instance of Rate.
575
   */
576
  async createRate(hz = 1) {
32✔
577
    if (typeof hz !== 'number') {
72!
578
      throw new TypeError('Invalid argument');
×
579
    }
580

581
    const MAX_RATE_HZ_IN_MILLISECOND = 1000.0;
72✔
582
    if (hz <= 0.0 || hz > MAX_RATE_HZ_IN_MILLISECOND) {
72✔
583
      throw new RangeError(
16✔
584
        `Hz must be between 0.0 and ${MAX_RATE_HZ_IN_MILLISECOND}`
585
      );
586
    }
587

588
    // lazy initialize rateTimerServer
589
    if (!this._rateTimerServer) {
56✔
590
      this._rateTimerServer = new Rates.RateTimerServer(this);
40✔
591
      await this._rateTimerServer.init();
40✔
592
    }
593

594
    const period = Math.round(1000 / hz);
56✔
595
    const timer = this._rateTimerServer.createTimer(BigInt(period) * 1000000n);
56✔
596
    const rate = new Rates.Rate(hz, timer);
56✔
597

598
    return rate;
56✔
599
  }
600

601
  /**
602
   * Create a Publisher.
603
   * @param {function|string|object} typeClass - The ROS message class,
604
        OR a string representing the message class, e.g. 'std_msgs/msg/String',
605
        OR an object representing the message class, e.g. {package: 'std_msgs', type: 'msg', name: 'String'}
606
   * @param {string} topic - The name of the topic.
607
   * @param {object} options - The options argument used to parameterize the publisher.
608
   * @param {boolean} options.enableTypedArray - The topic will use TypedArray if necessary, default: true.
609
   * @param {QoS} options.qos - ROS Middleware "quality of service" settings for the publisher, default: QoS.profileDefault.
610
   * @param {PublisherEventCallbacks} eventCallbacks - The event callbacks for the publisher.
611
   * @return {Publisher} - An instance of Publisher.
612
   */
613
  createPublisher(typeClass, topic, options, eventCallbacks) {
614
    return this._createPublisher(
7,534✔
615
      typeClass,
616
      topic,
617
      options,
618
      Publisher,
619
      eventCallbacks
620
    );
621
  }
622

623
  _createPublisher(typeClass, topic, options, publisherClass, eventCallbacks) {
624
    if (typeof typeClass === 'string' || typeof typeClass === 'object') {
7,558✔
625
      typeClass = loader.loadInterface(typeClass);
7,366✔
626
    }
627
    options = this._validateOptions(options);
7,502✔
628

629
    if (
7,502✔
630
      typeof typeClass !== 'function' ||
22,294✔
631
      typeof topic !== 'string' ||
632
      (eventCallbacks && !(eventCallbacks instanceof PublisherEventCallbacks))
633
    ) {
634
      throw new TypeError('Invalid argument');
160✔
635
    }
636

637
    let publisher = publisherClass.createPublisher(
7,342✔
638
      this,
639
      typeClass,
640
      topic,
641
      options,
642
      eventCallbacks
643
    );
644
    debug('Finish creating publisher, topic = %s.', topic);
7,262✔
645
    this._publishers.push(publisher);
7,262✔
646
    return publisher;
7,262✔
647
  }
648

649
  /**
650
   * This callback is called when a message is published
651
   * @callback SubscriptionCallback
652
   * @param {Object} message - The message published
653
   * @see [Node.createSubscription]{@link Node#createSubscription}
654
   * @see [Node.createPublisher]{@link Node#createPublisher}
655
   * @see {@link Publisher}
656
   * @see {@link Subscription}
657
   */
658

659
  /**
660
   * Create a Subscription with optional content-filtering.
661
   * @param {function|string|object} typeClass - The ROS message class,
662
        OR a string representing the message class, e.g. 'std_msgs/msg/String',
663
        OR an object representing the message class, e.g. {package: 'std_msgs', type: 'msg', name: 'String'}
664
   * @param {string} topic - The name of the topic.
665
   * @param {object} options - The options argument used to parameterize the subscription.
666
   * @param {boolean} options.enableTypedArray - The topic will use TypedArray if necessary, default: true.
667
   * @param {QoS} options.qos - ROS Middleware "quality of service" settings for the subscription, default: QoS.profileDefault.
668
   * @param {boolean} options.isRaw - The topic is serialized when true, default: false.
669
   * @param {object} [options.contentFilter=undefined] - The content-filter, default: undefined.
670
   *  Confirm that your RMW supports content-filtered topics before use. 
671
   * @param {string} options.contentFilter.expression - Specifies the criteria to select the data samples of
672
   *  interest. It is similar to the WHERE part of an SQL clause.
673
   * @param {string[]} [options.contentFilter.parameters=undefined] - Array of strings that give values to
674
   *  the ‘parameters’ (i.e., "%n" tokens) in the filter_expression. The number of supplied parameters must
675
   *  fit with the requested values in the filter_expression (i.e., the number of %n tokens). default: undefined.
676
   * @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.
677
   * @param {SubscriptionEventCallbacks} eventCallbacks - The event callbacks for the subscription.
678
   * @return {Subscription} - An instance of Subscription.
679
   * @throws {ERROR} - May throw an RMW error if content-filter is malformed. 
680
   * @see {@link SubscriptionCallback}
681
   * @see {@link https://www.omg.org/spec/DDS/1.4/PDF|Content-filter details at DDS 1.4 specification, Annex B}
682
   */
683
  createSubscription(typeClass, topic, options, callback, eventCallbacks) {
684
    if (typeof typeClass === 'string' || typeof typeClass === 'object') {
3,017✔
685
      typeClass = loader.loadInterface(typeClass);
2,945✔
686
    }
687

688
    if (typeof options === 'function') {
2,961✔
689
      callback = options;
2,683✔
690
      options = undefined;
2,683✔
691
    }
692
    options = this._validateOptions(options);
2,961✔
693

694
    if (
2,881✔
695
      typeof typeClass !== 'function' ||
11,356✔
696
      typeof topic !== 'string' ||
697
      typeof callback !== 'function' ||
698
      (eventCallbacks &&
699
        !(eventCallbacks instanceof SubscriptionEventCallbacks))
700
    ) {
701
      throw new TypeError('Invalid argument');
80✔
702
    }
703

704
    let subscription = Subscription.createSubscription(
2,801✔
705
      this,
706
      typeClass,
707
      topic,
708
      options,
709
      callback,
710
      eventCallbacks
711
    );
712
    debug('Finish creating subscription, topic = %s.', topic);
2,713✔
713
    this._subscriptions.push(subscription);
2,713✔
714
    this.syncHandles();
2,713✔
715

716
    return subscription;
2,713✔
717
  }
718

719
  /**
720
   * Create a Client.
721
   * @param {function|string|object} typeClass - The ROS message class,
722
        OR a string representing the message class, e.g. 'std_msgs/msg/String',
723
        OR an object representing the message class, e.g. {package: 'std_msgs', type: 'msg', name: 'String'}
724
   * @param {string} serviceName - The service name to request.
725
   * @param {object} options - The options argument used to parameterize the client.
726
   * @param {boolean} options.enableTypedArray - The response will use TypedArray if necessary, default: true.
727
   * @param {QoS} options.qos - ROS Middleware "quality of service" settings for the client, default: QoS.profileDefault.
728
   * @return {Client} - An instance of Client.
729
   */
730
  createClient(typeClass, serviceName, options) {
731
    if (typeof typeClass === 'string' || typeof typeClass === 'object') {
652✔
732
      typeClass = loader.loadInterface(typeClass);
572✔
733
    }
734
    options = this._validateOptions(options);
596✔
735

736
    if (typeof typeClass !== 'function' || typeof serviceName !== 'string') {
596✔
737
      throw new TypeError('Invalid argument');
160✔
738
    }
739

740
    let client = Client.createClient(
436✔
741
      this.handle,
742
      serviceName,
743
      typeClass,
744
      options
745
    );
746
    debug('Finish creating client, service = %s.', serviceName);
356✔
747
    this._clients.push(client);
356✔
748
    this.syncHandles();
356✔
749

750
    return client;
356✔
751
  }
752

753
  /**
754
   * This callback is called when a request is sent to service
755
   * @callback RequestCallback
756
   * @param {Object} request - The request sent to the service
757
   * @param {Response} response - The response to client.
758
        Use [response.send()]{@link Response#send} to send response object to client
759
   * @return {undefined}
760
   * @see [Node.createService]{@link Node#createService}
761
   * @see [Client.sendRequest]{@link Client#sendRequest}
762
   * @see {@link Client}
763
   * @see {@link Service}
764
   * @see {@link Response#send}
765
   */
766

767
  /**
768
   * Create a Service.
769
   * @param {function|string|object} typeClass - The ROS message class,
770
        OR a string representing the message class, e.g. 'std_msgs/msg/String',
771
        OR an object representing the message class, e.g. {package: 'std_msgs', type: 'msg', name: 'String'}
772
   * @param {string} serviceName - The service name to offer.
773
   * @param {object} options - The options argument used to parameterize the service.
774
   * @param {boolean} options.enableTypedArray - The request will use TypedArray if necessary, default: true.
775
   * @param {QoS} options.qos - ROS Middleware "quality of service" settings for the service, default: QoS.profileDefault.
776
   * @param {RequestCallback} callback - The callback to be called when receiving request.
777
   * @return {Service} - An instance of Service.
778
   * @see {@link RequestCallback}
779
   */
780
  createService(typeClass, serviceName, options, callback) {
781
    if (typeof typeClass === 'string' || typeof typeClass === 'object') {
28,228✔
782
      typeClass = loader.loadInterface(typeClass);
28,148✔
783
    }
784

785
    if (typeof options === 'function') {
28,172✔
786
      callback = options;
27,996✔
787
      options = undefined;
27,996✔
788
    }
789
    options = this._validateOptions(options);
28,172✔
790

791
    if (
28,092✔
792
      typeof typeClass !== 'function' ||
84,164✔
793
      typeof serviceName !== 'string' ||
794
      typeof callback !== 'function'
795
    ) {
796
      throw new TypeError('Invalid argument');
80✔
797
    }
798

799
    let service = Service.createService(
28,012✔
800
      this.handle,
801
      serviceName,
802
      typeClass,
803
      options,
804
      callback
805
    );
806
    debug('Finish creating service, service = %s.', serviceName);
27,932✔
807
    this._services.push(service);
27,932✔
808
    this.syncHandles();
27,932✔
809

810
    return service;
27,932✔
811
  }
812

813
  /**
814
   * Create a guard condition.
815
   * @param {Function} callback - The callback to be called when the guard condition is triggered.
816
   * @return {GuardCondition} - An instance of GuardCondition.
817
   */
818
  createGuardCondition(callback) {
819
    if (typeof callback !== 'function') {
24!
820
      throw new TypeError('Invalid argument');
×
821
    }
822

823
    let guard = GuardCondition.createGuardCondition(callback, this.context);
24✔
824
    debug('Finish creating guard condition');
24✔
825
    this._guards.push(guard);
24✔
826
    this.syncHandles();
24✔
827

828
    return guard;
24✔
829
  }
830

831
  /**
832
   * Destroy all resource allocated by this node, including
833
   * <code>Timer</code>s/<code>Publisher</code>s/<code>Subscription</code>s
834
   * /<code>Client</code>s/<code>Service</code>s
835
   * @return {undefined}
836
   */
837
  destroy() {
838
    if (this.spinning) {
4,838✔
839
      this.stop();
3,406✔
840
    }
841

842
    // Action servers/clients require manual destruction due to circular reference with goal handles.
843
    this._actionClients.forEach((actionClient) => actionClient.destroy());
4,838✔
844
    this._actionServers.forEach((actionServer) => actionServer.destroy());
4,838✔
845

846
    this.context.onNodeDestroyed(this);
4,838✔
847

848
    this.handle.release();
4,838✔
849
    this._clock = null;
4,838✔
850
    this._timers = [];
4,838✔
851
    this._publishers = [];
4,838✔
852
    this._subscriptions = [];
4,838✔
853
    this._clients = [];
4,838✔
854
    this._services = [];
4,838✔
855
    this._guards = [];
4,838✔
856
    this._actionClients = [];
4,838✔
857
    this._actionServers = [];
4,838✔
858

859
    if (this._rateTimerServer) {
4,838✔
860
      this._rateTimerServer.shutdown();
40✔
861
      this._rateTimerServer = null;
40✔
862
    }
863
  }
864

865
  /**
866
   * Destroy a Publisher.
867
   * @param {Publisher} publisher - The Publisher to be destroyed.
868
   * @return {undefined}
869
   */
870
  destroyPublisher(publisher) {
871
    if (!(publisher instanceof Publisher)) {
70✔
872
      throw new TypeError('Invalid argument');
16✔
873
    }
874
    if (publisher.events) {
54!
875
      publisher.events.forEach((event) => {
×
876
        this._destroyEntity(event, this._events);
×
877
      });
878
      publisher.events = [];
×
879
    }
880
    this._destroyEntity(publisher, this._publishers, false);
54✔
881
  }
882

883
  /**
884
   * Destroy a Subscription.
885
   * @param {Subscription} subscription - The Subscription to be destroyed.
886
   * @return {undefined}
887
   */
888
  destroySubscription(subscription) {
889
    if (!(subscription instanceof Subscription)) {
309✔
890
      throw new TypeError('Invalid argument');
16✔
891
    }
892
    if (subscription.events) {
293✔
893
      subscription.events.forEach((event) => {
6✔
894
        this._destroyEntity(event, this._events);
6✔
895
      });
896
      subscription.events = [];
6✔
897
    }
898

899
    this._destroyEntity(subscription, this._subscriptions);
293✔
900
  }
901

902
  /**
903
   * Destroy a Client.
904
   * @param {Client} client - The Client to be destroyed.
905
   * @return {undefined}
906
   */
907
  destroyClient(client) {
908
    if (!(client instanceof Client)) {
64✔
909
      throw new TypeError('Invalid argument');
16✔
910
    }
911
    this._destroyEntity(client, this._clients);
48✔
912
  }
913

914
  /**
915
   * Destroy a Service.
916
   * @param {Service} service - The Service to be destroyed.
917
   * @return {undefined}
918
   */
919
  destroyService(service) {
920
    if (!(service instanceof Service)) {
64✔
921
      throw new TypeError('Invalid argument');
16✔
922
    }
923
    this._destroyEntity(service, this._services);
48✔
924
  }
925

926
  /**
927
   * Destroy a Timer.
928
   * @param {Timer} timer - The Timer to be destroyed.
929
   * @return {undefined}
930
   */
931
  destroyTimer(timer) {
932
    if (!(timer instanceof Timer)) {
64✔
933
      throw new TypeError('Invalid argument');
16✔
934
    }
935
    this._destroyEntity(timer, this._timers);
48✔
936
  }
937

938
  /**
939
   * Destroy a guard condition.
940
   * @param {GuardCondition} guard - The guard condition to be destroyed.
941
   * @return {undefined}
942
   */
943
  destroyGuardCondition(guard) {
944
    if (!(guard instanceof GuardCondition)) {
24!
945
      throw new TypeError('Invalid argument');
×
946
    }
947
    this._destroyEntity(guard, this._guards);
24✔
948
  }
949

950
  /**
951
   * Get the name of the node.
952
   * @return {string}
953
   */
954
  name() {
955
    return rclnodejs.getNodeName(this.handle);
21,510✔
956
  }
957

958
  /**
959
   * Get the namespace of the node.
960
   * @return {string}
961
   */
962
  namespace() {
963
    return rclnodejs.getNamespace(this.handle);
17,210✔
964
  }
965

966
  /**
967
   * Get the context in which this node was created.
968
   * @return {Context}
969
   */
970
  get context() {
971
    return this._context;
42,114✔
972
  }
973

974
  /**
975
   * Get the nodes logger.
976
   * @returns {Logger} - The logger for the node.
977
   */
978
  getLogger() {
979
    return this._logger;
1,128✔
980
  }
981

982
  /**
983
   * Get the clock used by the node.
984
   * @returns {Clock} - The nodes clock.
985
   */
986
  getClock() {
987
    return this._clock;
656✔
988
  }
989

990
  /**
991
   * Get the current time using the node's clock.
992
   * @returns {Time} - The current time.
993
   */
994
  now() {
995
    return this.getClock().now();
16✔
996
  }
997

998
  /**
999
   * Get the list of published topics discovered by the provided node for the remote node name.
1000
   * @param {string} nodeName - The name of the node.
1001
   * @param {string} namespace - The name of the namespace.
1002
   * @param {boolean} noDemangle - If true topic names and types returned will not be demangled, default: false.
1003
   * @return {Array<{name: string, types: Array<string>}>} - An array of the names and types.
1004
   */
1005
  getPublisherNamesAndTypesByNode(nodeName, namespace, noDemangle = false) {
×
1006
    return rclnodejs.getPublisherNamesAndTypesByNode(
×
1007
      this.handle,
1008
      nodeName,
1009
      namespace,
1010
      noDemangle
1011
    );
1012
  }
1013

1014
  /**
1015
   * Get the list of published topics discovered by the provided node for the remote node name.
1016
   * @param {string} nodeName - The name of the node.
1017
   * @param {string} namespace - The name of the namespace.
1018
   * @param {boolean} noDemangle - If true topic names and types returned will not be demangled, default: false.
1019
   * @return {Array<{name: string, types: Array<string>}>} - An array of the names and types.
1020
   */
1021
  getSubscriptionNamesAndTypesByNode(nodeName, namespace, noDemangle = false) {
×
1022
    return rclnodejs.getSubscriptionNamesAndTypesByNode(
×
1023
      this.handle,
1024
      nodeName,
1025
      namespace,
1026
      noDemangle
1027
    );
1028
  }
1029

1030
  /**
1031
   * Get service names and types for which a remote node has servers.
1032
   * @param {string} nodeName - The name of the node.
1033
   * @param {string} namespace - The name of the namespace.
1034
   * @return {Array<{name: string, types: Array<string>}>} - An array of the names and types.
1035
   */
1036
  getServiceNamesAndTypesByNode(nodeName, namespace) {
1037
    return rclnodejs.getServiceNamesAndTypesByNode(
×
1038
      this.handle,
1039
      nodeName,
1040
      namespace
1041
    );
1042
  }
1043

1044
  /**
1045
   * Get service names and types for which a remote node has clients.
1046
   * @param {string} nodeName - The name of the node.
1047
   * @param {string} namespace - The name of the namespace.
1048
   * @return {Array<{name: string, types: Array<string>}>} - An array of the names and types.
1049
   */
1050
  getClientNamesAndTypesByNode(nodeName, namespace) {
1051
    return rclnodejs.getClientNamesAndTypesByNode(
×
1052
      this.handle,
1053
      nodeName,
1054
      namespace
1055
    );
1056
  }
1057

1058
  /**
1059
   * Get the list of topics discovered by the provided node.
1060
   * @param {boolean} noDemangle - If true topic names and types returned will not be demangled, default: false.
1061
   * @return {Array<{name: string, types: Array<string>}>} - An array of the names and types.
1062
   */
1063
  getTopicNamesAndTypes(noDemangle = false) {
×
1064
    return rclnodejs.getTopicNamesAndTypes(this.handle, noDemangle);
×
1065
  }
1066

1067
  /**
1068
   * Get the list of services discovered by the provided node.
1069
   * @return {Array<{name: string, types: Array<string>}>} - An array of the names and types.
1070
   */
1071
  getServiceNamesAndTypes() {
1072
    return rclnodejs.getServiceNamesAndTypes(this.handle);
16✔
1073
  }
1074

1075
  /**
1076
   * Return a list of publishers on a given topic.
1077
   *
1078
   * The returned parameter is a list of TopicEndpointInfo objects, where each will contain
1079
   * the node name, node namespace, topic type, topic endpoint's GID, and its QoS profile.
1080
   *
1081
   * When the `no_mangle` parameter is `true`, the provided `topic` should be a valid
1082
   * topic name for the middleware (useful when combining ROS with native middleware (e.g. DDS)
1083
   * apps).  When the `no_mangle` parameter is `False`, the provided `topic` should
1084
   * follow ROS topic name conventions.
1085
   *
1086
   * `topic` may be a relative, private, or fully qualified topic name.
1087
   *  A relative or private topic will be expanded using this node's namespace and name.
1088
   *  The queried `topic` is not remapped.
1089
   * @param {string} topic - The topic on which to find the publishers.
1090
   * @param {boolean} noDemangle - If `true`, `topic` needs to be a valid middleware topic
1091
   *                               name, otherwise it should be a valid ROS topic name. Defaults to `false`.
1092
   * @returns {Array} - list of publishers
1093
   */
1094
  getPublishersInfoByTopic(topic, noDemangle) {
1095
    return rclnodejs.getPublishersInfoByTopic(
22✔
1096
      this.handle,
1097
      this._getValidatedTopic(topic, noDemangle),
1098
      noDemangle
1099
    );
1100
  }
1101

1102
  /**
1103
   * Return a list of subscriptions on a given topic.
1104
   *
1105
   * The returned parameter is a list of TopicEndpointInfo objects, where each will contain
1106
   * the node name, node namespace, topic type, topic endpoint's GID, and its QoS profile.
1107
   *
1108
   * When the `no_mangle` parameter is `true`, the provided `topic` should be a valid
1109
   * topic name for the middleware (useful when combining ROS with native middleware (e.g. DDS)
1110
   * apps).  When the `no_mangle` parameter is `False`, the provided `topic` should
1111
   * follow ROS topic name conventions.
1112
   *
1113
   * `topic` may be a relative, private, or fully qualified topic name.
1114
   *  A relative or private topic will be expanded using this node's namespace and name.
1115
   *  The queried `topic` is not remapped.
1116
   * @param {string} topic - The topic on which to find the subscriptions.
1117
   * @param {boolean} noDemangle -  If `True`, `topic` needs to be a valid middleware topic
1118
                                    name, otherwise it should be a valid ROS topic name. Defaults to `False`.
1119
   * @returns {Array} - list of subscriptions
1120
   */
1121
  getSubscriptionsInfoByTopic(topic, noDemangle) {
1122
    return rclnodejs.getSubscriptionsInfoByTopic(
16✔
1123
      this.handle,
1124
      this._getValidatedTopic(topic, noDemangle),
1125
      noDemangle
1126
    );
1127
  }
1128

1129
  /**
1130
   * Get the list of nodes discovered by the provided node.
1131
   * @return {Array<string>} - An array of the names.
1132
   */
1133
  getNodeNames() {
1134
    return this.getNodeNamesAndNamespaces().map((item) => item.name);
320✔
1135
  }
1136

1137
  /**
1138
   * Get the list of nodes and their namespaces discovered by the provided node.
1139
   * @return {Array<{name: string, namespace: string}>} An array of the names and namespaces.
1140
   */
1141
  getNodeNamesAndNamespaces() {
1142
    return rclnodejs.getNodeNames(this.handle, /*getEnclaves=*/ false);
136✔
1143
  }
1144

1145
  /**
1146
   * Get the list of nodes and their namespaces with enclaves discovered by the provided node.
1147
   * @return {Array<{name: string, namespace: string, enclave: string}>} An array of the names, namespaces and enclaves.
1148
   */
1149
  getNodeNamesAndNamespacesWithEnclaves() {
1150
    return rclnodejs.getNodeNames(this.handle, /*getEnclaves=*/ true);
8✔
1151
  }
1152

1153
  /**
1154
   * Return the number of publishers on a given topic.
1155
   * @param {string} topic - The name of the topic.
1156
   * @returns {number} - Number of publishers on the given topic.
1157
   */
1158
  countPublishers(topic) {
1159
    let expandedTopic = rclnodejs.expandTopicName(
48✔
1160
      topic,
1161
      this.name(),
1162
      this.namespace()
1163
    );
1164
    rclnodejs.validateTopicName(expandedTopic);
48✔
1165

1166
    return rclnodejs.countPublishers(this.handle, expandedTopic);
48✔
1167
  }
1168

1169
  /**
1170
   * Return the number of subscribers on a given topic.
1171
   * @param {string} topic - The name of the topic.
1172
   * @returns {number} - Number of subscribers on the given topic.
1173
   */
1174
  countSubscribers(topic) {
1175
    let expandedTopic = rclnodejs.expandTopicName(
48✔
1176
      topic,
1177
      this.name(),
1178
      this.namespace()
1179
    );
1180
    rclnodejs.validateTopicName(expandedTopic);
48✔
1181

1182
    return rclnodejs.countSubscribers(this.handle, expandedTopic);
48✔
1183
  }
1184

1185
  /**
1186
   * Get the number of clients on a given service name.
1187
   * @param {string} serviceName - the service name
1188
   * @returns {Number}
1189
   */
1190
  countClients(serviceName) {
1191
    if (DistroUtils.getDistroId() <= DistroUtils.getDistroId('humble')) {
12!
UNCOV
1192
      console.warn('countClients is not supported by this version of ROS 2');
×
UNCOV
1193
      return null;
×
1194
    }
1195
    return rclnodejs.countClients(this.handle, serviceName);
12✔
1196
  }
1197

1198
  /**
1199
   * Get the number of services on a given service name.
1200
   * @param {string} serviceName - the service name
1201
   * @returns {Number}
1202
   */
1203
  countServices(serviceName) {
1204
    if (DistroUtils.getDistroId() <= DistroUtils.getDistroId('humble')) {
6!
UNCOV
1205
      console.warn('countServices is not supported by this version of ROS 2');
×
UNCOV
1206
      return null;
×
1207
    }
1208
    return rclnodejs.countServices(this.handle, serviceName);
6✔
1209
  }
1210

1211
  /**
1212
   * Get the list of parameter-overrides found on the commandline and
1213
   * in the NodeOptions.parameter_overrides property.
1214
   *
1215
   * @return {Array<Parameter>} - An array of Parameters.
1216
   */
1217
  getParameterOverrides() {
1218
    return Array.from(this._parameterOverrides.values());
64✔
1219
  }
1220

1221
  /**
1222
   * Declare a parameter.
1223
   *
1224
   * Internally, register a parameter and it's descriptor.
1225
   * If a parameter-override exists, it's value will replace that of the parameter
1226
   * unless ignoreOverride is true.
1227
   * If the descriptor is undefined, then a ParameterDescriptor will be inferred
1228
   * from the parameter's state.
1229
   *
1230
   * If a parameter by the same name has already been declared then an Error is thrown.
1231
   * A parameter must be undeclared before attempting to redeclare it.
1232
   *
1233
   * @param {Parameter} parameter - Parameter to declare.
1234
   * @param {ParameterDescriptor} [descriptor] - Optional descriptor for parameter.
1235
   * @param {boolean} [ignoreOveride] - When true disregard any parameter-override that may be present.
1236
   * @return {Parameter} - The newly declared parameter.
1237
   */
1238
  declareParameter(parameter, descriptor, ignoreOveride = false) {
8,426✔
1239
    const parameters = this.declareParameters(
8,434✔
1240
      [parameter],
1241
      descriptor ? [descriptor] : [],
8,434✔
1242
      ignoreOveride
1243
    );
1244
    return parameters.length == 1 ? parameters[0] : null;
8,434!
1245
  }
1246

1247
  /**
1248
   * Declare a list of parameters.
1249
   *
1250
   * Internally register parameters with their corresponding descriptor one by one
1251
   * in the order they are provided. This is an atomic operation. If an error
1252
   * occurs the process halts and no further parameters are declared.
1253
   * Parameters that have already been processed are undeclared.
1254
   *
1255
   * While descriptors is an optional parameter, when provided there must be
1256
   * a descriptor for each parameter; otherwise an Error is thrown.
1257
   * If descriptors is not provided then a descriptor will be inferred
1258
   * from each parameter's state.
1259
   *
1260
   * When a parameter-override is available, the parameter's value
1261
   * will be replaced with that of the parameter-override unless ignoreOverrides
1262
   * is true.
1263
   *
1264
   * If a parameter by the same name has already been declared then an Error is thrown.
1265
   * A parameter must be undeclared before attempting to redeclare it.
1266
   *
1267
   * Prior to declaring the parameters each SetParameterEventCallback registered
1268
   * using setOnParameterEventCallback() is called in succession with the parameters
1269
   * list. Any SetParameterEventCallback that retuns does not return a successful
1270
   * result will cause the entire operation to terminate with no changes to the
1271
   * parameters. When all SetParameterEventCallbacks return successful then the
1272
   * list of parameters is updated.
1273
   *
1274
   * @param {Parameter[]} parameters - The parameters to declare.
1275
   * @param {ParameterDescriptor[]} [descriptors] - Optional descriptors,
1276
   *    a 1-1 correspondence with parameters.
1277
   * @param {boolean} ignoreOverrides - When true, parameter-overrides are
1278
   *    not considered, i.e.,ignored.
1279
   * @return {Parameter[]} - The declared parameters.
1280
   */
1281
  declareParameters(parameters, descriptors = [], ignoreOverrides = false) {
×
1282
    if (!Array.isArray(parameters)) {
8,434!
UNCOV
1283
      throw new TypeError('Invalid parameter: expected array of Parameter');
×
1284
    }
1285
    if (!Array.isArray(descriptors)) {
8,434!
UNCOV
1286
      throw new TypeError(
×
1287
        'Invalid parameters: expected array of ParameterDescriptor'
1288
      );
1289
    }
1290
    if (descriptors.length > 0 && parameters.length !== descriptors.length) {
8,434!
UNCOV
1291
      throw new TypeError(
×
1292
        'Each parameter must have a cooresponding ParameterDescriptor'
1293
      );
1294
    }
1295

1296
    const declaredDescriptors = [];
8,434✔
1297
    const declaredParameters = [];
8,434✔
1298
    const declaredParameterCollisions = [];
8,434✔
1299
    for (let i = 0; i < parameters.length; i++) {
8,434✔
1300
      let parameter =
1301
        !ignoreOverrides && this._parameterOverrides.has(parameters[i].name)
8,434✔
1302
          ? this._parameterOverrides.get(parameters[i].name)
1303
          : parameters[i];
1304

1305
      // stop processing parameters that have already been declared
1306
      if (this._parameters.has(parameter.name)) {
8,434!
UNCOV
1307
        declaredParameterCollisions.push(parameter);
×
UNCOV
1308
        continue;
×
1309
      }
1310

1311
      // create descriptor for parameter if not provided
1312
      let descriptor =
1313
        descriptors.length > 0
8,434✔
1314
          ? descriptors[i]
1315
          : ParameterDescriptor.fromParameter(parameter);
1316

1317
      descriptor.validate();
8,434✔
1318

1319
      declaredDescriptors.push(descriptor);
8,434✔
1320
      declaredParameters.push(parameter);
8,434✔
1321
    }
1322

1323
    if (declaredParameterCollisions.length > 0) {
8,434!
1324
      const errorMsg =
UNCOV
1325
        declaredParameterCollisions.length == 1
×
1326
          ? `Parameter(${declaredParameterCollisions[0]}) already declared.`
1327
          : `Multiple parameters already declared, e.g., Parameter(${declaredParameterCollisions[0]}).`;
UNCOV
1328
      throw new Error(errorMsg);
×
1329
    }
1330

1331
    // register descriptor
1332
    for (const descriptor of declaredDescriptors) {
8,434✔
1333
      this._parameterDescriptors.set(descriptor.name, descriptor);
8,434✔
1334
    }
1335

1336
    const result = this._setParametersAtomically(declaredParameters, true);
8,434✔
1337
    if (!result.successful) {
8,434!
1338
      // unregister descriptors
UNCOV
1339
      for (const descriptor of declaredDescriptors) {
×
UNCOV
1340
        this._parameterDescriptors.delete(descriptor.name);
×
1341
      }
1342

UNCOV
1343
      throw new Error(result.reason);
×
1344
    }
1345

1346
    return this.getParameters(declaredParameters.map((param) => param.name));
8,434✔
1347
  }
1348

1349
  /**
1350
   * Undeclare a parameter.
1351
   *
1352
   * Readonly parameters can not be undeclared or updated.
1353
   * @param {string} name - Name of parameter to undeclare.
1354
   * @return {undefined} -
1355
   */
1356
  undeclareParameter(name) {
1357
    if (!this.hasParameter(name)) return;
8!
1358

1359
    const descriptor = this.getParameterDescriptor(name);
8✔
1360
    if (descriptor.readOnly) {
8!
UNCOV
1361
      throw new Error(
×
1362
        `${name} parameter is read-only and can not be undeclared`
1363
      );
1364
    }
1365

1366
    this._parameters.delete(name);
8✔
1367
    this._parameterDescriptors.delete(name);
8✔
1368
  }
1369

1370
  /**
1371
   * Determine if a parameter has been declared.
1372
   * @param {string} name - name of parameter
1373
   * @returns {boolean} - Return true if parameter is declared; false otherwise.
1374
   */
1375
  hasParameter(name) {
1376
    return this._parameters.has(name);
24,992✔
1377
  }
1378

1379
  /**
1380
   * Get a declared parameter by name.
1381
   *
1382
   * If unable to locate a declared parameter then a
1383
   * parameter with type == PARAMETER_NOT_SET is returned.
1384
   *
1385
   * @param {string} name - The name of the parameter.
1386
   * @return {Parameter} - The parameter.
1387
   */
1388
  getParameter(name) {
1389
    return this.getParameters([name])[0];
8,280✔
1390
  }
1391

1392
  /**
1393
   * Get a list of parameters.
1394
   *
1395
   * Find and return the declared parameters.
1396
   * If no names are provided return all declared parameters.
1397
   *
1398
   * If unable to locate a declared parameter then a
1399
   * parameter with type == PARAMETER_NOT_SET is returned in
1400
   * it's place.
1401
   *
1402
   * @param {string[]} [names] - The names of the declared parameters
1403
   *    to find or null indicating to return all declared parameters.
1404
   * @return {Parameter[]} - The parameters.
1405
   */
1406
  getParameters(names = []) {
70✔
1407
    let params = [];
16,798✔
1408

1409
    if (names.length == 0) {
16,798✔
1410
      // get all parameters
1411
      params = [...this._parameters.values()];
70✔
1412
      return params;
70✔
1413
    }
1414

1415
    for (const name of names) {
16,728✔
1416
      const param = this.hasParameter(name)
16,736!
1417
        ? this._parameters.get(name)
1418
        : new Parameter(name, ParameterType.PARAMETER_NOT_SET);
1419

1420
      params.push(param);
16,736✔
1421
    }
1422

1423
    return params;
16,728✔
1424
  }
1425

1426
  /**
1427
   * Get the types of given parameters.
1428
   *
1429
   * Return the types of given parameters.
1430
   *
1431
   * @param {string[]} [names] - The names of the declared parameters.
1432
   * @return {Uint8Array} - The types.
1433
   */
1434
  getParameterTypes(names = []) {
×
1435
    let types = [];
8✔
1436

1437
    for (const name of names) {
8✔
1438
      const descriptor = this._parameterDescriptors.get(name);
24✔
1439
      if (descriptor) {
24!
1440
        types.push(descriptor.type);
24✔
1441
      }
1442
    }
1443
    return types;
8✔
1444
  }
1445

1446
  /**
1447
   * Get the names of all declared parameters.
1448
   *
1449
   * @return {Array<string>} - The declared parameter names or empty array if
1450
   *    no parameters have been declared.
1451
   */
1452
  getParameterNames() {
1453
    return this.getParameters().map((param) => param.name);
150✔
1454
  }
1455

1456
  /**
1457
   * Determine if a parameter descriptor exists.
1458
   *
1459
   * @param {string} name - The name of a descriptor to for.
1460
   * @return {boolean} - True if a descriptor has been declared; otherwise false.
1461
   */
1462
  hasParameterDescriptor(name) {
1463
    return !!this.getParameterDescriptor(name);
8,482✔
1464
  }
1465

1466
  /**
1467
   * Get a declared parameter descriptor by name.
1468
   *
1469
   * If unable to locate a declared parameter descriptor then a
1470
   * descriptor with type == PARAMETER_NOT_SET is returned.
1471
   *
1472
   * @param {string} name - The name of the parameter descriptor to find.
1473
   * @return {ParameterDescriptor} - The parameter descriptor.
1474
   */
1475
  getParameterDescriptor(name) {
1476
    return this.getParameterDescriptors([name])[0];
16,972✔
1477
  }
1478

1479
  /**
1480
   * Find a list of declared ParameterDescriptors.
1481
   *
1482
   * If no names are provided return all declared descriptors.
1483
   *
1484
   * If unable to locate a declared descriptor then a
1485
   * descriptor with type == PARAMETER_NOT_SET is returned in
1486
   * it's place.
1487
   *
1488
   * @param {string[]} [names] - The names of the declared parameter
1489
   *    descriptors to find or null indicating to return all declared descriptors.
1490
   * @return {ParameterDescriptor[]} - The parameter descriptors.
1491
   */
1492
  getParameterDescriptors(names = []) {
×
1493
    let descriptors = [];
16,980✔
1494

1495
    if (names.length == 0) {
16,980!
1496
      // get all parameters
UNCOV
1497
      descriptors = [...this._parameterDescriptors.values()];
×
UNCOV
1498
      return descriptors;
×
1499
    }
1500

1501
    for (const name of names) {
16,980✔
1502
      let descriptor = this._parameterDescriptors.get(name);
16,988✔
1503
      if (!descriptor) {
16,988!
UNCOV
1504
        descriptor = new ParameterDescriptor(
×
1505
          name,
1506
          ParameterType.PARAMETER_NOT_SET
1507
        );
1508
      }
1509
      descriptors.push(descriptor);
16,988✔
1510
    }
1511

1512
    return descriptors;
16,980✔
1513
  }
1514

1515
  /**
1516
   * Replace a declared parameter.
1517
   *
1518
   * The parameter being replaced must be a declared parameter who's descriptor
1519
   * is not readOnly; otherwise an Error is thrown.
1520
   *
1521
   * @param {Parameter} parameter - The new parameter.
1522
   * @return {rclnodejs.rcl_interfaces.msg.SetParameterResult} - The result of the operation.
1523
   */
1524
  setParameter(parameter) {
1525
    const results = this.setParameters([parameter]);
32✔
1526
    return results[0];
32✔
1527
  }
1528

1529
  /**
1530
   * Replace a list of declared parameters.
1531
   *
1532
   * Declared parameters are replaced in the order they are provided and
1533
   * a ParameterEvent is published for each individual parameter change.
1534
   *
1535
   * Prior to setting the parameters each SetParameterEventCallback registered
1536
   * using setOnParameterEventCallback() is called in succession with the parameters
1537
   * list. Any SetParameterEventCallback that retuns does not return a successful
1538
   * result will cause the entire operation to terminate with no changes to the
1539
   * parameters. When all SetParameterEventCallbacks return successful then the
1540
   * list of parameters is updated.
1541
   *
1542
   * If an error occurs, the process is stopped and returned. Parameters
1543
   * set before an error remain unchanged.
1544
   *
1545
   * @param {Parameter[]} parameters - The parameters to set.
1546
   * @return {rclnodejs.rcl_interfaces.msg.SetParameterResult[]} - A list of SetParameterResult, one for each parameter that was set.
1547
   */
1548
  setParameters(parameters = []) {
×
1549
    return parameters.map((parameter) =>
40✔
1550
      this.setParametersAtomically([parameter])
40✔
1551
    );
1552
  }
1553

1554
  /**
1555
   * Repalce a list of declared parameters atomically.
1556
   *
1557
   * Declared parameters are replaced in the order they are provided.
1558
   * A single ParameterEvent is published collectively for all changed
1559
   * parameters.
1560
   *
1561
   * Prior to setting the parameters each SetParameterEventCallback registered
1562
   * using setOnParameterEventCallback() is called in succession with the parameters
1563
   * list. Any SetParameterEventCallback that retuns does not return a successful
1564
   * result will cause the entire operation to terminate with no changes to the
1565
   * parameters. When all SetParameterEventCallbacks return successful then the
1566
   * list of parameters is updated.d
1567
   *
1568
   * If an error occurs, the process stops immediately. All parameters updated to
1569
   * the point of the error are reverted to their previous state.
1570
   *
1571
   * @param {Parameter[]} parameters - The parameters to set.
1572
   * @return {rclnodejs.rcl_interfaces.msg.SetParameterResult} - describes the result of setting 1 or more parameters.
1573
   */
1574
  setParametersAtomically(parameters = []) {
×
1575
    return this._setParametersAtomically(parameters);
48✔
1576
  }
1577

1578
  /**
1579
   * Internal method for updating parameters atomically.
1580
   *
1581
   * Prior to setting the parameters each SetParameterEventCallback registered
1582
   * using setOnParameterEventCallback() is called in succession with the parameters
1583
   * list. Any SetParameterEventCallback that retuns does not return a successful
1584
   * result will cause the entire operation to terminate with no changes to the
1585
   * parameters. When all SetParameterEventCallbacks return successful then the
1586
   * list of parameters is updated.
1587
   *
1588
   * @param {Paramerter[]} parameters - The parameters to update.
1589
   * @param {boolean} declareParameterMode - When true parameters are being declared;
1590
   *    otherwise they are being changed.
1591
   * @return {SetParameterResult} - A single collective result.
1592
   */
1593
  _setParametersAtomically(parameters = [], declareParameterMode = false) {
48!
1594
    let result = this._validateParameters(parameters, declareParameterMode);
8,482✔
1595
    if (!result.successful) {
8,482!
UNCOV
1596
      return result;
×
1597
    }
1598

1599
    // give all SetParametersCallbacks a chance to veto this change
1600
    for (const callback of this._setParametersCallbacks) {
8,482✔
1601
      result = callback(parameters);
3,868✔
1602
      if (!result.successful) {
3,868✔
1603
        // a callback has vetoed a parameter change
1604
        return result;
8✔
1605
      }
1606
    }
1607

1608
    // collectively track updates to parameters for use
1609
    // when publishing a ParameterEvent
1610
    const newParameters = [];
8,474✔
1611
    const changedParameters = [];
8,474✔
1612
    const deletedParameters = [];
8,474✔
1613

1614
    for (const parameter of parameters) {
8,474✔
1615
      if (parameter.type == ParameterType.PARAMETER_NOT_SET) {
8,474✔
1616
        this.undeclareParameter(parameter.name);
8✔
1617
        deletedParameters.push(parameter);
8✔
1618
      } else {
1619
        this._parameters.set(parameter.name, parameter);
8,466✔
1620
        if (declareParameterMode) {
8,466✔
1621
          newParameters.push(parameter);
8,434✔
1622
        } else {
1623
          changedParameters.push(parameter);
32✔
1624
        }
1625
      }
1626
    }
1627

1628
    // create ParameterEvent
1629
    const parameterEvent = new (loader.loadInterface(
8,474✔
1630
      PARAMETER_EVENT_MSG_TYPE
1631
    ))();
1632

1633
    const { seconds, nanoseconds } = this._clock.now().secondsAndNanoseconds;
8,474✔
1634
    parameterEvent.stamp = {
8,474✔
1635
      sec: Number(seconds),
1636
      nanosec: Number(nanoseconds),
1637
    };
1638

1639
    parameterEvent.node =
8,474✔
1640
      this.namespace() === '/'
8,474✔
1641
        ? this.namespace() + this.name()
1642
        : this.namespace() + '/' + this.name();
1643

1644
    if (newParameters.length > 0) {
8,474✔
1645
      parameterEvent['new_parameters'] = newParameters.map((parameter) =>
8,434✔
1646
        parameter.toParameterMessage()
8,434✔
1647
      );
1648
    }
1649
    if (changedParameters.length > 0) {
8,474✔
1650
      parameterEvent['changed_parameters'] = changedParameters.map(
32✔
1651
        (parameter) => parameter.toParameterMessage()
32✔
1652
      );
1653
    }
1654
    if (deletedParameters.length > 0) {
8,474✔
1655
      parameterEvent['deleted_parameters'] = deletedParameters.map(
8✔
1656
        (parameter) => parameter.toParameterMessage()
8✔
1657
      );
1658
    }
1659

1660
    // Publish ParameterEvent.
1661
    this._parameterEventPublisher.publish(parameterEvent);
8,474✔
1662

1663
    return {
8,474✔
1664
      successful: true,
1665
      reason: '',
1666
    };
1667
  }
1668

1669
  /**
1670
   * This callback is called when declaring a parameter or setting a parameter.
1671
   * The callback is provided a list of parameters and returns a SetParameterResult
1672
   * to indicate approval or veto of the operation.
1673
   *
1674
   * @callback SetParametersCallback
1675
   * @param {Parameter[]} parameters - The message published
1676
   * @returns {rcl_interfaces.msg.SetParameterResult} -
1677
   *
1678
   * @see [Node.addOnSetParametersCallback]{@link Node#addOnSetParametersCallback}
1679
   * @see [Node.removeOnSetParametersCallback]{@link Node#removeOnSetParametersCallback}
1680
   */
1681

1682
  /**
1683
   * Add a callback to the front of the list of callbacks invoked for parameter declaration
1684
   * and setting. No checks are made for duplicate callbacks.
1685
   *
1686
   * @param {SetParametersCallback} callback - The callback to add.
1687
   * @returns {undefined}
1688
   */
1689
  addOnSetParametersCallback(callback) {
1690
    this._setParametersCallbacks.unshift(callback);
4,726✔
1691
  }
1692

1693
  /**
1694
   * Remove a callback from the list of SetParametersCallbacks.
1695
   * If the callback is not found the process is a nop.
1696
   *
1697
   * @param {SetParametersCallback} callback - The callback to be removed
1698
   * @returns {undefined}
1699
   */
1700
  removeOnSetParametersCallback(callback) {
1701
    const idx = this._setParametersCallbacks.indexOf(callback);
16✔
1702
    if (idx > -1) {
16!
1703
      this._setParametersCallbacks.splice(idx, 1);
16✔
1704
    }
1705
  }
1706

1707
  /**
1708
   * Get the fully qualified name of the node.
1709
   *
1710
   * @returns {string} - String containing the fully qualified name of the node.
1711
   */
1712
  getFullyQualifiedName() {
1713
    return rclnodejs.getFullyQualifiedName(this.handle);
8✔
1714
  }
1715

1716
  /**
1717
   * Get the RMW implementation identifier
1718
   * @returns {string} - The RMW implementation identifier.
1719
   */
1720
  getRMWImplementationIdentifier() {
1721
    return rclnodejs.getRMWImplementationIdentifier();
8✔
1722
  }
1723

1724
  /**
1725
   * Return a topic name expanded and remapped.
1726
   * @param {string} topicName - Topic name to be expanded and remapped.
1727
   * @param {boolean} [onlyExpand=false] - If `true`, remapping rules won't be applied.
1728
   * @returns {string} - A fully qualified topic name.
1729
   */
1730
  resolveTopicName(topicName, onlyExpand = false) {
16✔
1731
    if (typeof topicName !== 'string') {
24!
UNCOV
1732
      throw new TypeError('Invalid argument: expected string');
×
1733
    }
1734
    return rclnodejs.resolveName(
24✔
1735
      this.handle,
1736
      topicName,
1737
      onlyExpand,
1738
      /*isService=*/ false
1739
    );
1740
  }
1741

1742
  /**
1743
   * Return a service name expanded and remapped.
1744
   * @param {string} service - Service name to be expanded and remapped.
1745
   * @param {boolean} [onlyExpand=false] - If `true`, remapping rules won't be applied.
1746
   * @returns {string} - A fully qualified service name.
1747
   */
1748
  resolveServiceName(service, onlyExpand = false) {
16✔
1749
    if (typeof service !== 'string') {
24!
UNCOV
1750
      throw new TypeError('Invalid argument: expected string');
×
1751
    }
1752
    return rclnodejs.resolveName(
24✔
1753
      this.handle,
1754
      service,
1755
      onlyExpand,
1756
      /*isService=*/ true
1757
    );
1758
  }
1759

1760
  // returns on 1st error or result {successful, reason}
1761
  _validateParameters(parameters = [], declareParameterMode = false) {
×
1762
    for (const parameter of parameters) {
8,482✔
1763
      // detect invalid parameter
1764
      try {
8,482✔
1765
        parameter.validate();
8,482✔
1766
      } catch {
UNCOV
1767
        return {
×
1768
          successful: false,
1769
          reason: `Invalid ${parameter.name}`,
1770
        };
1771
      }
1772

1773
      // detect undeclared parameter
1774
      if (!this.hasParameterDescriptor(parameter.name)) {
8,482!
UNCOV
1775
        return {
×
1776
          successful: false,
1777
          reason: `Parameter ${parameter.name} has not been declared`,
1778
        };
1779
      }
1780

1781
      // detect readonly parameter that can not be updated
1782
      const descriptor = this.getParameterDescriptor(parameter.name);
8,482✔
1783
      if (!declareParameterMode && descriptor.readOnly) {
8,482!
UNCOV
1784
        return {
×
1785
          successful: false,
1786
          reason: `Parameter ${parameter.name} is readonly`,
1787
        };
1788
      }
1789

1790
      // validate parameter against descriptor if not an undeclare action
1791
      if (parameter.type != ParameterType.PARAMETER_NOT_SET) {
8,482✔
1792
        try {
8,474✔
1793
          descriptor.validateParameter(parameter);
8,474✔
1794
        } catch {
UNCOV
1795
          return {
×
1796
            successful: false,
1797
            reason: `Parameter ${parameter.name} does not  readonly`,
1798
          };
1799
        }
1800
      }
1801
    }
1802

1803
    return {
8,482✔
1804
      successful: true,
1805
      reason: null,
1806
    };
1807
  }
1808

1809
  // Get a Map(nodeName->Parameter[]) of CLI parameter args that
1810
  // apply to 'this' node, .e.g., -p mynode:foo:=bar -p hello:=world
1811
  _getNativeParameterOverrides() {
1812
    const overrides = new Map();
4,662✔
1813

1814
    // Get native parameters from rcl context->global_arguments.
1815
    // rclnodejs returns an array of objects, 1 for each node e.g., -p my_node:foo:=bar,
1816
    // and a node named '/**' for global parameter rules,
1817
    // i.e., does not include a node identifier, e.g., -p color:=red
1818
    // {
1819
    //   name: string // node name
1820
    //   parameters[] = {
1821
    //     name: string
1822
    //     type: uint
1823
    //     value: object
1824
    // }
1825
    const cliParamOverrideData = rclnodejs.getParameterOverrides(
4,662✔
1826
      this.context.handle
1827
    );
1828

1829
    // convert native CLI parameterOverrides to Map<nodeName,Array<ParameterOverride>>
1830
    const cliParamOverrides = new Map();
4,662✔
1831
    if (cliParamOverrideData) {
4,662✔
1832
      for (let nodeParamData of cliParamOverrideData) {
64✔
1833
        const nodeName = nodeParamData.name;
96✔
1834
        const nodeParamOverrides = [];
96✔
1835
        for (let paramData of nodeParamData.parameters) {
96✔
1836
          const paramOverride = new Parameter(
136✔
1837
            paramData.name,
1838
            paramData.type,
1839
            paramData.value
1840
          );
1841
          nodeParamOverrides.push(paramOverride);
136✔
1842
        }
1843
        cliParamOverrides.set(nodeName, nodeParamOverrides);
96✔
1844
      }
1845
    }
1846

1847
    // collect global CLI global parameters, name == /**
1848
    let paramOverrides = cliParamOverrides.get('/**'); // array of ParameterOverrides
4,662✔
1849
    if (paramOverrides) {
4,662✔
1850
      for (const parameter of paramOverrides) {
40✔
1851
        overrides.set(parameter.name, parameter);
48✔
1852
      }
1853
    }
1854

1855
    // merge CLI node parameterOverrides with global parameterOverrides, replace existing
1856
    paramOverrides = cliParamOverrides.get(this.name()); // array of ParameterOverrides
4,662✔
1857
    if (paramOverrides) {
4,662✔
1858
      for (const parameter of paramOverrides) {
40✔
1859
        overrides.set(parameter.name, parameter);
56✔
1860
      }
1861
    }
1862

1863
    return overrides;
4,662✔
1864
  }
1865

1866
  /**
1867
   * Invokes the callback with a raw message of the given type. After the callback completes
1868
   * the message will be destroyed.
1869
   * @param {function} Type - Message type to create.
1870
   * @param {function} callback - Callback to invoke. First parameter will be the raw message,
1871
   * and the second is a function to retrieve the deserialized message.
1872
   * @returns {undefined}
1873
   */
1874
  _runWithMessageType(Type, callback) {
1875
    let message = new Type();
5,453✔
1876

1877
    callback(message.toRawROS(), () => {
5,453✔
1878
      let result = new Type();
4,402✔
1879
      result.deserialize(message.refObject);
4,402✔
1880

1881
      return result;
4,402✔
1882
    });
1883

1884
    Type.destroyRawROS(message);
5,453✔
1885
  }
1886

1887
  _addActionClient(actionClient) {
1888
    this._actionClients.push(actionClient);
324✔
1889
    this.syncHandles();
324✔
1890
  }
1891

1892
  _addActionServer(actionServer) {
1893
    this._actionServers.push(actionServer);
324✔
1894
    this.syncHandles();
324✔
1895
  }
1896

1897
  _getValidatedTopic(topicName, noDemangle) {
1898
    if (noDemangle) {
38!
NEW
1899
      return topicName;
×
1900
    }
1901
    const fqTopicName = rclnodejs.expandTopicName(
38✔
1902
      topicName,
1903
      this.name(),
1904
      this.namespace()
1905
    );
1906
    validateFullTopicName(fqTopicName);
38✔
1907
    return rclnodejs.remapTopicName(this.handle, fqTopicName);
38✔
1908
  }
1909
}
1910

1911
/**
1912
 * Create an Options instance initialized with default values.
1913
 * @returns {Options} - The new initialized instance.
1914
 * @static
1915
 * @example
1916
 * {
1917
 *   enableTypedArray: true,
1918
 *   isRaw: false,
1919
 *   qos: QoS.profileDefault,
1920
 *   contentFilter: undefined,
1921
 * }
1922
 */
1923
Node.getDefaultOptions = function () {
208✔
1924
  return {
43,137✔
1925
    enableTypedArray: true,
1926
    isRaw: false,
1927
    qos: QoS.profileDefault,
1928
    contentFilter: undefined,
1929
  };
1930
};
1931

1932
module.exports = Node;
208✔
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