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

RobotWebTools / rclnodejs / 40639761865

28 Oct 2026 05:44AM UTC coverage: 91.176% (+0.01%) from 91.164%
40639761865

Pull #1666

github

web-flow
Merge 9043ba26d into 4c9ff990f
Pull Request #1666: Harden Callback Lifetimes and Exception-Path Cleanup

2357 of 2762 branches covered (85.34%)

Branch coverage included in aggregate %.

256 of 257 new or added lines in 1 file covered. (99.61%)

50 existing lines in 1 file now uncovered.

18122 of 19699 relevant lines covered (91.99%)

271.44 hits per line

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

90.25
/lib/node.js
1
// Copyright (c) 2017 Intel Corporation. All rights reserved.
88 ✔
2
//
88 ✔
3
// Licensed under the Apache License, Version 2.0 (the "License");
88 ✔
4
// you may not use this file except in compliance with the License.
88 ✔
5
// You may obtain a copy of the License at
88 ✔
6
//
88 ✔
7
//     http://www.apache.org/licenses/LICENSE-2.0
88 ✔
8
//
88 ✔
9
// Unless required by applicable law or agreed to in writing, software
88 ✔
10
// distributed under the License is distributed on an "AS IS" BASIS,
88 ✔
11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
88 ✔
12
// See the License for the specific language governing permissions and
88 ✔
13
// limitations under the License.
88 ✔
14

88 ✔
15
import rclnodejs from './native_loader.js';
88 ✔
16

88 ✔
17
import ActionInterfaces from './action/interfaces.js';
88 ✔
18
import Client from './client.js';
88 ✔
19
import * as Clock from './clock.js';
88 ✔
20
import Context from './context.js';
88 ✔
21
import createDebug from 'debug';
88 ✔
22
const debug = createDebug('rclnodejs:node');
88 ✔
23
import DistroUtils from './distro.js';
88 ✔
24
import GuardCondition from './guard_condition.js';
88 ✔
25
import loader from './interface_loader.js';
88 ✔
26
import Logging from './logging.js';
88 ✔
27
import LoggingService from './logging_service.js';
88 ✔
28
import NodeOptions from './node_options.js';
88 ✔
29
import { ParameterType, Parameter, ParameterDescriptor } from './parameter.js';
88 ✔
30
import { isValidSerializationMode } from './message_serialization.js';
88 ✔
31
import {
88 ✔
32
  TypeValidationError,
88 ✔
33
  RangeValidationError,
88 ✔
34
  ValidationError,
88 ✔
35
} from './errors.js';
88 ✔
36
import ParameterService from './parameter_service.js';
88 ✔
37
import ParameterClient from './parameter_client.js';
88 ✔
38
import ParameterWatcher from './parameter_watcher.js';
88 ✔
39
import ParameterEventHandler from './parameter_event_handler.js';
88 ✔
40
import Publisher from './publisher.js';
88 ✔
41
import QoS from './qos.js';
88 ✔
42
import * as Rates from './rate.js';
88 ✔
43
import Service from './service.js';
88 ✔
44
import Subscription from './subscription.js';
88 ✔
45
import ObservableSubscription from './observable_subscription.js';
88 ✔
46
import MessageInfo from './message_info.js';
88 ✔
47
import ActionEndpointInfo from './action/endpoint_info.js';
88 ✔
48
import { declareQosParameters, _resolveQoS } from './qos_overriding_options.js';
88 ✔
49
import TimeSource from './time_source.js';
88 ✔
50
import Timer from './timer.js';
88 ✔
51
import TypeDescriptionService from './type_description_service.js';
88 ✔
52
import Entity from './entity.js';
88 ✔
53
import { SubscriptionEventCallbacks } from '../lib/event_handler.js';
88 ✔
54
import { PublisherEventCallbacks } from '../lib/event_handler.js';
88 ✔
55
import { validateFullTopicName } from './validator.js';
88 ✔
56

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

88 ✔
61
/**
88 ✔
62
 * @class - Class representing a Node in ROS
88 ✔
63
 */
88 ✔
64

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

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

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

88 ✔
100
  static _normalizeOptions(options) {
88 ✔
101
    if (options instanceof NodeOptions) {
1,125 ✔
102
      return options;
1,122 ✔
103
    }
1,122 ✔
104
    const defaults = NodeOptions.defaultOptions;
3 ✔
105
    return {
3 ✔
106
      startParameterServices:
3 ✔
107
        options.startParameterServices ?? defaults.startParameterServices,
1,125 ✔
108
      parameterOverrides:
1,125 ✔
109
        options.parameterOverrides ?? defaults.parameterOverrides,
1,125 ✔
110
      automaticallyDeclareParametersFromOverrides:
1,125 ✔
111
        options.automaticallyDeclareParametersFromOverrides ??
1,125 ✔
112
        defaults.automaticallyDeclareParametersFromOverrides,
1,125 ✔
113
      startTypeDescriptionService:
1,125 ✔
114
        options.startTypeDescriptionService ??
1,125 ✔
115
        defaults.startTypeDescriptionService,
1,125 ✔
116
      enableRosout: options.enableRosout ?? defaults.enableRosout,
1,125 ✔
117
      rosoutQos: options.rosoutQos ?? defaults.rosoutQos,
1,125 ✔
118
      enableLoggerService:
1,125 ✔
119
        options.enableLoggerService ?? defaults.enableLoggerService,
1,125 ✔
120
    };
1,125 ✔
121
  }
1,125 ✔
122

88 ✔
123
  _init(name, namespace, options, context, args, useGlobalArguments) {
88 ✔
124
    options = Node._normalizeOptions(options);
1,125 ✔
125

1,125 ✔
126
    this.handle = rclnodejs.createNode(
1,125 ✔
127
      name,
1,125 ✔
128
      namespace,
1,125 ✔
129
      context.handle,
1,125 ✔
130
      args,
1,125 ✔
131
      useGlobalArguments,
1,125 ✔
132
      options.rosoutQos
1,125 ✔
133
    );
1,125 ✔
134
    Object.defineProperty(this, 'handle', {
1,125 ✔
135
      configurable: false,
1,125 ✔
136
      writable: false,
1,125 ✔
137
    }); // make read-only
1,125 ✔
138

1,125 ✔
139
    this._context = context;
1,125 ✔
140
    this.context.onNodeCreated(this);
1,125 ✔
141

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

1,125 ✔
169
    if (this._enableRosout) {
1,125 ✔
170
      rclnodejs.initRosoutPublisherForNode(this.handle);
1,066 ✔
171
    }
1,066 ✔
172

1,115 ✔
173
    this._parameterEventPublisher = this.createPublisher(
1,115 ✔
174
      PARAMETER_EVENT_MSG_TYPE,
1,115 ✔
175
      PARAMETER_EVENT_TOPIC
1,115 ✔
176
    );
1,115 ✔
177

1,115 ✔
178
    // initialize _parameterOverrides from parameters defined on the commandline
1,115 ✔
179
    this._parameterOverrides = this._getNativeParameterOverrides();
1,115 ✔
180

1,115 ✔
181
    // override cli parameterOverrides with those specified in options
1,115 ✔
182
    if (options.parameterOverrides.length > 0) {
1,125 ✔
183
      for (const parameter of options.parameterOverrides) {
12 ✔
184
        if (!(parameter instanceof Parameter)) {
17 ✔
185
          throw new TypeValidationError(
1 ✔
186
            'parameterOverride',
1 ✔
187
            parameter,
1 ✔
188
            'Parameter instance',
1 ✔
189
            {
1 ✔
190
              nodeName: name,
1 ✔
191
            }
1 ✔
192
          );
1 ✔
193
        }
1 ✔
194
        this._parameterOverrides.set(parameter.name, parameter);
16 ✔
195
      }
16 ✔
196
    }
11 ✔
197

1,114 ✔
198
    // initialize _parameters from parameterOverrides
1,114 ✔
199
    if (options.automaticallyDeclareParametersFromOverrides) {
1,125 ✔
200
      for (const parameter of this._parameterOverrides.values()) {
5 ✔
201
        parameter.validate();
10 ✔
202
        const descriptor = ParameterDescriptor.fromParameter(parameter);
10 ✔
203
        this._parameters.set(parameter.name, parameter);
10 ✔
204
        this._parameterDescriptors.set(parameter.name, descriptor);
10 ✔
205
      }
10 ✔
206
    }
5 ✔
207

1,114 ✔
208
    // Clock that has support for ROS time.
1,114 ✔
209
    // Note: parameter overrides and parameter event publisher need to be ready at this point
1,114 ✔
210
    // to be able to declare 'use_sim_time' if it was not declared yet.
1,114 ✔
211
    this._clock = new Clock.ROSClock();
1,114 ✔
212
    this._timeSource = new TimeSource(this);
1,114 ✔
213
    this._timeSource.attachClock(this._clock);
1,114 ✔
214

1,114 ✔
215
    if (options.startParameterServices) {
1,125 ✔
216
      this._parameterService = new ParameterService(this);
1,060 ✔
217
      this._parameterService.start();
1,060 ✔
218
    }
1,060 ✔
219

1,114 ✔
220
    if (options.enableLoggerService) {
1,125 !
UNCOV
221
      this._loggerService = new LoggingService(this);
×
222
      this._loggerService.start();
×
223
    }
×
224

1,114 ✔
225
    if (
1,114 ✔
226
      DistroUtils.getDistroId() >= DistroUtils.getDistroId('jazzy') &&
1,114 ✔
227
      options.startTypeDescriptionService
1,114 ✔
228
    ) {
1,125 ✔
229
      this._typeDescriptionService = new TypeDescriptionService(this);
1,067 ✔
230
      this._typeDescriptionService.start();
1,067 ✔
231
    }
1,067 ✔
232
  }
1,125 ✔
233

88 ✔
234
  execute(handles) {
88 ✔
235
    const dispatchGeneration = this._dispatchGeneration;
5,697 ✔
236
    try {
5,697 ✔
237
      let timersReady = this._timers.filter((timer) =>
5,697 ✔
238
        handles.includes(timer.handle)
4,606 ✔
239
      );
5,697 ✔
240
      let guardsReady = this._guards.filter((guard) =>
5,697 ✔
241
        handles.includes(guard.handle)
127 ✔
242
      );
5,697 ✔
243
      let subscriptionsReady = this._subscriptions.filter((subscription) =>
5,697 ✔
244
        handles.includes(subscription.handle)
469 ✔
245
      );
5,697 ✔
246
      let clientsReady = this._clients.filter((client) =>
5,697 ✔
247
        handles.includes(client.handle)
201 ✔
248
      );
5,697 ✔
249
      let servicesReady = this._services.filter((service) =>
5,697 ✔
250
        handles.includes(service.handle)
21,207 ✔
251
      );
5,697 ✔
252
      let actionClientsReady = this._actionClients.filter((actionClient) =>
5,697 ✔
253
        handles.includes(actionClient.handle)
369 ✔
254
      );
5,697 ✔
255
      let actionServersReady = this._actionServers.filter((actionServer) =>
5,697 ✔
256
        handles.includes(actionServer.handle)
385 ✔
257
      );
5,697 ✔
258
      let eventsReady = this._events.filter((event) =>
5,697 ✔
259
        handles.includes(event.handle)
24 ✔
260
      );
5,697 ✔
261

5,697 ✔
262
      for (const timer of timersReady) {
5,697 ✔
263
        if (this._dispatchGeneration !== dispatchGeneration) return;
4,592 ✔
264
        // Earlier callbacks may destroy timers or the node after this snapshot.
4,588 ✔
265
        if (this._timers.includes(timer) && timer.isReady()) {
4,592 ✔
266
          let timerInfo;
4,582 ✔
267
          if (typeof rclnodejs.callTimerWithInfo === 'function') {
4,582 ✔
268
            timerInfo = rclnodejs.callTimerWithInfo(timer.handle);
4,581 ✔
269
            timer.callback(timerInfo);
4,581 ✔
270
          } else {
4,582 ✔
271
            rclnodejs.callTimer(timer.handle);
1 ✔
272
            timer.callback();
1 ✔
273
          }
1 ✔
274
        }
4,582 ✔
275
      }
4,592 ✔
276

5,684 ✔
277
      for (const event of eventsReady) {
5,686 ✔
278
        if (this._dispatchGeneration !== dispatchGeneration) return;
16 !
279
        if (!event.isDestroyed()) event.takeData();
16 ✔
280
      }
16 ✔
281

5,682 ✔
282
      for (const subscription of subscriptionsReady) {
5,686 ✔
283
        if (this._dispatchGeneration !== dispatchGeneration) return;
434 !
284
        if (subscription.isDestroyed()) continue;
434 ✔
285
        if (subscription.isRaw) {
434 ✔
286
          if (subscription.wantsMessageInfo) {
5 ✔
287
            let taken = rclnodejs.rclTakeRawWithInfo(subscription.handle);
3 ✔
288
            if (taken) {
3 ✔
289
              subscription.processResponse(
3 ✔
290
                taken.buffer,
3 ✔
291
                new MessageInfo(taken.info)
3 ✔
292
              );
3 ✔
293
            }
3 ✔
294
          } else {
5 ✔
295
            let rawMessage = rclnodejs.rclTakeRaw(subscription.handle);
2 ✔
296
            if (rawMessage) {
2 ✔
297
              subscription.processResponse(rawMessage);
2 ✔
298
            }
2 ✔
299
          }
2 ✔
300
          continue;
5 ✔
301
        }
5 ✔
302

417 ✔
303
        this._runWithMessageType(
417 ✔
304
          subscription.typeClass,
417 ✔
305
          (message, deserialize) => {
417 ✔
306
            if (subscription.wantsMessageInfo) {
417 ✔
307
              let rawInfo = rclnodejs.rclTakeWithInfo(
4 ✔
308
                subscription.handle,
4 ✔
309
                message
4 ✔
310
              );
4 ✔
311
              if (rawInfo) {
4 ✔
312
                subscription.processResponse(
4 ✔
313
                  deserialize(),
4 ✔
314
                  new MessageInfo(rawInfo)
4 ✔
315
                );
4 ✔
316
              }
4 ✔
317
            } else {
417 ✔
318
              let success = rclnodejs.rclTake(subscription.handle, message);
413 ✔
319
              if (success) {
413 ✔
320
                subscription.processResponse(deserialize());
406 ✔
321
              }
406 ✔
322
            }
413 ✔
323
          }
417 ✔
324
        );
417 ✔
325
      }
417 ✔
326

5,682 ✔
327
      for (const guard of guardsReady) {
5,686 ✔
328
        if (this._dispatchGeneration !== dispatchGeneration) return;
108 ✔
329
        if (guard.isDestroyed()) continue;
104 !
330

103 ✔
331
        guard.callback();
103 ✔
332
      }
103 ✔
333

5,654 ✔
334
      for (const client of clientsReady) {
5,666 ✔
335
        if (this._dispatchGeneration !== dispatchGeneration) return;
104 !
336
        if (client.isDestroyed()) continue;
104 !
337
        this._runWithMessageType(
104 ✔
338
          client.typeClass.Response,
104 ✔
339
          (message, deserialize) => {
104 ✔
340
            let sequenceNumber = rclnodejs.rclTakeResponse(
104 ✔
341
              client.handle,
104 ✔
342
              message
104 ✔
343
            );
104 ✔
344
            if (sequenceNumber !== undefined) {
104 ✔
345
              client.processResponse(sequenceNumber, deserialize());
99 ✔
346
            }
99 ✔
347
          }
104 ✔
348
        );
104 ✔
349
      }
104 ✔
350

5,654 ✔
351
      for (const service of servicesReady) {
5,666 ✔
352
        if (this._dispatchGeneration !== dispatchGeneration) return;
114 !
353
        if (service.isDestroyed()) continue;
114 !
354
        this._runWithMessageType(
114 ✔
355
          service.typeClass.Request,
114 ✔
356
          (message, deserialize) => {
114 ✔
357
            let header = rclnodejs.rclTakeRequest(
114 ✔
358
              service.handle,
114 ✔
359
              this.handle,
114 ✔
360
              message
114 ✔
361
            );
114 ✔
362
            if (header) {
114 ✔
363
              service.processRequest(header, deserialize());
104 ✔
364
            }
104 ✔
365
          }
114 ✔
366
        );
114 ✔
367
      }
114 ✔
368

5,654 ✔
369
      for (const actionClient of actionClientsReady) {
5,666 ✔
370
        if (this._dispatchGeneration !== dispatchGeneration) return;
225 !
371
        if (actionClient.isDestroyed()) continue;
225 !
372

225 ✔
373
        const properties = actionClient.handle.properties;
225 ✔
374

225 ✔
375
        if (properties.isGoalResponseReady) {
225 ✔
376
          this._runWithMessageType(
98 ✔
377
            actionClient.typeClass.impl.SendGoalService.Response,
98 ✔
378
            (message, deserialize) => {
98 ✔
379
              let sequence = rclnodejs.actionTakeGoalResponse(
98 ✔
380
                actionClient.handle,
98 ✔
381
                message
98 ✔
382
              );
98 ✔
383
              if (sequence != undefined) {
98 ✔
384
                actionClient.processGoalResponse(sequence, deserialize());
87 ✔
385
              }
87 ✔
386
            }
98 ✔
387
          );
98 ✔
388
        }
98 ✔
389

222 ✔
390
        if (this._dispatchGeneration !== dispatchGeneration) return;
225 ✔
391
        if (properties.isCancelResponseReady && !actionClient.isDestroyed()) {
225 ✔
392
          this._runWithMessageType(
11 ✔
393
            actionClient.typeClass.impl.CancelGoal.Response,
11 ✔
394
            (message, deserialize) => {
11 ✔
395
              let sequence = rclnodejs.actionTakeCancelResponse(
11 ✔
396
                actionClient.handle,
11 ✔
397
                message
11 ✔
398
              );
11 ✔
399
              if (sequence != undefined) {
11 ✔
400
                actionClient.processCancelResponse(sequence, deserialize());
11 ✔
401
              }
11 ✔
402
            }
11 ✔
403
          );
11 ✔
404
        }
11 ✔
405

221 ✔
406
        if (this._dispatchGeneration !== dispatchGeneration) return;
225 !
407
        if (properties.isResultResponseReady && !actionClient.isDestroyed()) {
225 ✔
408
          this._runWithMessageType(
57 ✔
409
            actionClient.typeClass.impl.GetResultService.Response,
57 ✔
410
            (message, deserialize) => {
57 ✔
411
              let sequence = rclnodejs.actionTakeResultResponse(
57 ✔
412
                actionClient.handle,
57 ✔
413
                message
57 ✔
414
              );
57 ✔
415
              if (sequence != undefined) {
57 ✔
416
                actionClient.processResultResponse(sequence, deserialize());
55 ✔
417
              }
55 ✔
418
            }
57 ✔
419
          );
57 ✔
420
        }
57 ✔
421

221 ✔
422
        if (this._dispatchGeneration !== dispatchGeneration) return;
225 !
423
        if (properties.isFeedbackReady && !actionClient.isDestroyed()) {
225 ✔
424
          this._runWithMessageType(
32 ✔
425
            actionClient.typeClass.impl.FeedbackMessage,
32 ✔
426
            (message, deserialize) => {
32 ✔
427
              let success = rclnodejs.actionTakeFeedback(
32 ✔
428
                actionClient.handle,
32 ✔
429
                message
32 ✔
430
              );
32 ✔
431
              if (success) {
32 ✔
432
                actionClient.processFeedbackMessage(deserialize());
32 ✔
433
              }
32 ✔
434
            }
32 ✔
435
          );
32 ✔
436
        }
32 ✔
437

221 ✔
438
        if (this._dispatchGeneration !== dispatchGeneration) return;
225 !
439
        if (properties.isStatusReady && !actionClient.isDestroyed()) {
225 ✔
440
          this._runWithMessageType(
145 ✔
441
            actionClient.typeClass.impl.GoalStatusArray,
145 ✔
442
            (message, deserialize) => {
145 ✔
443
              let success = rclnodejs.actionTakeStatus(
145 ✔
444
                actionClient.handle,
145 ✔
445
                message
145 ✔
446
              );
145 ✔
447
              if (success) {
145 ✔
448
                actionClient.processStatusMessage(deserialize());
145 ✔
449
              }
145 ✔
450
            }
145 ✔
451
          );
145 ✔
452
        }
145 ✔
453
      }
225 ✔
454

5,650 ✔
455
      for (const actionServer of actionServersReady) {
5,666 ✔
456
        if (this._dispatchGeneration !== dispatchGeneration) return;
190 !
457
        if (actionServer.isDestroyed()) continue;
190 !
458

190 ✔
459
        const properties = actionServer.handle.properties;
190 ✔
460

190 ✔
461
        if (properties.isGoalRequestReady) {
190 ✔
462
          this._runWithMessageType(
105 ✔
463
            actionServer.typeClass.impl.SendGoalService.Request,
105 ✔
464
            (message, deserialize) => {
105 ✔
465
              const result = rclnodejs.actionTakeGoalRequest(
105 ✔
466
                actionServer.handle,
105 ✔
467
                message
105 ✔
468
              );
105 ✔
469
              if (result) {
105 ✔
470
                actionServer.processGoalRequest(result, deserialize());
87 ✔
471
              }
87 ✔
472
            }
105 ✔
473
          );
105 ✔
474
        }
105 ✔
475

187 ✔
476
        if (this._dispatchGeneration !== dispatchGeneration) return;
190 ✔
477
        if (properties.isCancelRequestReady && !actionServer.isDestroyed()) {
190 ✔
478
          this._runWithMessageType(
16 ✔
479
            actionServer.typeClass.impl.CancelGoal.Request,
16 ✔
480
            (message, deserialize) => {
16 ✔
481
              const result = rclnodejs.actionTakeCancelRequest(
16 ✔
482
                actionServer.handle,
16 ✔
483
                message
16 ✔
484
              );
16 ✔
485
              if (result) {
16 ✔
486
                actionServer.processCancelRequest(result, deserialize());
11 ✔
487
              }
11 ✔
488
            }
16 ✔
489
          );
16 ✔
490
        }
16 ✔
491

186 ✔
492
        if (this._dispatchGeneration !== dispatchGeneration) return;
190 !
493
        if (properties.isResultRequestReady && !actionServer.isDestroyed()) {
190 ✔
494
          this._runWithMessageType(
71 ✔
495
            actionServer.typeClass.impl.GetResultService.Request,
71 ✔
496
            (message, deserialize) => {
71 ✔
497
              const result = rclnodejs.actionTakeResultRequest(
71 ✔
498
                actionServer.handle,
71 ✔
499
                message
71 ✔
500
              );
71 ✔
501
              if (result) {
71 ✔
502
                actionServer.processResultRequest(result, deserialize());
56 ✔
503
              }
56 ✔
504
            }
71 ✔
505
          );
71 ✔
506
        }
71 ✔
507

186 ✔
508
        if (this._dispatchGeneration !== dispatchGeneration) return;
190 !
509
        if (properties.isGoalExpired && !actionServer.isDestroyed()) {
190 ✔
510
          let numGoals = actionServer._goalHandles.size;
4 ✔
511
          if (numGoals > 0) {
4 ✔
512
            let GoalInfoArray = ActionInterfaces.GoalInfo.ArrayType;
4 ✔
513
            let message = new GoalInfoArray(numGoals);
4 ✔
514
            try {
4 ✔
515
              let count = rclnodejs.actionExpireGoals(
4 ✔
516
                actionServer.handle,
4 ✔
517
                numGoals,
4 ✔
518
                message._refArray.buffer
4 ✔
519
              );
4 ✔
520
              if (count > 0) {
4 ✔
521
                actionServer.processGoalExpired(message, count);
4 ✔
522
              }
4 ✔
523
            } finally {
4 ✔
524
              GoalInfoArray.freeArray(message);
4 ✔
525
            }
4 ✔
526
          }
4 ✔
527
        }
4 ✔
528
      }
190 ✔
529
    } finally {
5,697 ✔
530
      Entity._gcHandles();
5,697 ✔
531
    }
5,697 ✔
532
  }
5,697 ✔
533

88 ✔
534
  /**
88 ✔
535
   * Determine if this node is spinning.
88 ✔
536
   * @returns {boolean} - true when spinning; otherwise returns false.
88 ✔
537
   */
88 ✔
538
  get spinning() {
88 ✔
539
    return this._spinning;
5,345 ✔
540
  }
5,345 ✔
541

88 ✔
542
  /**
88 ✔
543
   * Trigger the event loop to continuously check for and route.
88 ✔
544
   * incoming events.
88 ✔
545
   * A synchronous callback exception stops the current ready batch and is
88 ✔
546
   * reported through Node.js uncaught exception handling.
88 ✔
547
   * @param {Node} node - The node to be spun up.
88 ✔
548
   * @param {number} [timeout=10] - Timeout to wait in milliseconds. Block forever if negative. Don't wait if 0.
88 ✔
549
   * @throws {Error} If the node is already spinning or native executor cleanup is still in progress.
88 ✔
550
   * @return {undefined}
88 ✔
551
   */
88 ✔
552
  spin(timeout = 10) {
88 ✔
553
    if (this.spinning) {
1,086 !
UNCOV
554
      throw new Error('The node is already spinning.');
×
555
    }
×
556
    this.start(this.context.handle, timeout);
1,086 ✔
557
    this._spinning = true;
1,086 ✔
558
  }
1,086 ✔
559

88 ✔
560
  /**
88 ✔
561
   * Use spin().
88 ✔
562
   * @deprecated, since 0.18.0
88 ✔
563
   */
88 ✔
564
  startSpinning(timeout) {
88 ✔
UNCOV
565
    this.spin(timeout);
×
566
  }
×
567

88 ✔
568
  /**
88 ✔
569
   * Terminate spinning - no further events will be received.
88 ✔
570
   * Interrupts the native background wait, including an infinite spin timeout.
88 ✔
571
   * Does not interrupt a JavaScript callback that is already executing.
88 ✔
572
   * Reentrant calls during cleanup do not repeat teardown; starting a new spin
88 ✔
573
   * must wait until cleanup has finished.
88 ✔
574
   * @throws {Error} If the worker or native cleanup fails.
88 ✔
575
   * @returns {undefined}
88 ✔
576
   */
88 ✔
577
  stop() {
88 ✔
578
    this._dispatchGeneration++;
1,102 ✔
579
    try {
1,102 ✔
580
      super.stop();
1,102 ✔
581
    } finally {
1,102 ✔
582
      this._spinning = !super._isExecutorStopped();
1,102 ✔
583
    }
1,102 ✔
584
  }
1,102 ✔
585

88 ✔
586
  /**
88 ✔
587
   * Terminate spinning - no further events will be received.
88 ✔
588
   * @returns {undefined}
88 ✔
589
   * @deprecated since 0.18.0, Use stop().
88 ✔
590
   */
88 ✔
591
  stopSpinning() {
88 ✔
NEW
UNCOV
592
    this._dispatchGeneration++;
×
593
    try {
×
594
      super.stop();
×
595
    } finally {
×
596
      this._spinning = !super._isExecutorStopped();
×
597
    }
×
598
  }
×
599

88 ✔
600
  /**
88 ✔
601
   * Spin the node and trigger the event loop to check for one incoming event. Thereafter the node
88 ✔
602
   * will not received additional events until running additional calls to spin() or spinOnce().
88 ✔
603
   * A synchronous callback exception stops the current ready batch and is thrown to the caller.
88 ✔
604
   * @param {Node} node - The node to be spun.
88 ✔
605
   * @param {number} [timeout=10] - Timeout to wait in milliseconds. Block forever if negative. Don't wait if 0.
88 ✔
606
   * @throws {Error} If the node is already spinning or native executor cleanup is still in progress.
88 ✔
607
   * @return {undefined}
88 ✔
608
   */
88 ✔
609
  spinOnce(timeout = 10) {
88 ✔
610
    if (this.spinning) {
3,039 ✔
611
      throw new Error('The node is already spinning.');
2 ✔
612
    }
2 ✔
613
    super.spinOnce(this.context.handle, timeout);
3,037 ✔
614
  }
3,039 ✔
615

88 ✔
616
  _removeEntityFromArray(entity, array) {
88 ✔
617
    let index = array.indexOf(entity);
959 ✔
618
    if (index > -1) {
959 ✔
619
      array.splice(index, 1);
955 ✔
620
    }
955 ✔
621
  }
959 ✔
622

88 ✔
623
  _destroyEntity(entity, array, syncHandles = true) {
88 ✔
624
    if (entity['isDestroyed'] && entity.isDestroyed()) return;
911 ✔
625

903 ✔
626
    this._removeEntityFromArray(entity, array);
903 ✔
627
    if (syncHandles) {
911 ✔
628
      this.syncHandles();
872 ✔
629
    }
872 ✔
630

903 ✔
631
    if (entity['_destroy']) {
911 ✔
632
      entity._destroy();
787 ✔
633
    } else {
811 ✔
634
      // guards and timers
116 ✔
635
      entity.handle.release();
116 ✔
636
    }
116 ✔
637
  }
911 ✔
638

88 ✔
639
  _validateOptions(options) {
88 ✔
640
    if (
9,915 ✔
641
      options !== undefined &&
9,915 ✔
642
      (options === null || typeof options !== 'object')
66 ✔
643
    ) {
9,915 ✔
644
      throw new TypeValidationError('options', options, 'object', {
20 ✔
645
        nodeName: this.name(),
20 ✔
646
      });
20 ✔
647
    }
20 ✔
648

9,895 ✔
649
    if (options === undefined) {
9,915 ✔
650
      return Node.getDefaultOptions();
9,849 ✔
651
    }
9,849 ✔
652

46 ✔
653
    if (options.enableTypedArray === undefined) {
9,434 ✔
654
      options = Object.assign(options, { enableTypedArray: true });
32 ✔
655
    }
32 ✔
656

46 ✔
657
    if (options.qos === undefined) {
9,434 ✔
658
      options = Object.assign(options, { qos: QoS.profileDefault });
24 ✔
659
    }
24 ✔
660

46 ✔
661
    if (options.isRaw === undefined) {
9,434 ✔
662
      options = Object.assign(options, { isRaw: false });
31 ✔
663
    }
31 ✔
664

46 ✔
665
    if (options.serializationMode === undefined) {
9,434 ✔
666
      options = Object.assign(options, { serializationMode: 'default' });
29 ✔
667
    } else if (!isValidSerializationMode(options.serializationMode)) {
9,434 ✔
668
      throw new ValidationError(
1 ✔
669
        `Invalid serializationMode: ${options.serializationMode}. Valid modes are: 'default', 'plain', 'json'`,
1 ✔
670
        {
1 ✔
671
          code: 'INVALID_SERIALIZATION_MODE',
1 ✔
672
          argumentName: 'serializationMode',
1 ✔
673
          providedValue: options.serializationMode,
1 ✔
674
          expectedType: "'default' | 'plain' | 'json'",
1 ✔
675
          nodeName: this.name(),
1 ✔
676
        }
1 ✔
677
      );
1 ✔
678
    }
1 ✔
679

45 ✔
680
    return options;
45 ✔
681
  }
9,915 ✔
682

88 ✔
683
  /**
88 ✔
684
   * Create a Timer.
88 ✔
685
   * @param {bigint} period - The number representing period in nanoseconds.
88 ✔
686
   * @param {function} callback - The callback to be called when the timer fires.
88 ✔
687
   *   On distros with native support, the callback receives a `TimerInfo` object
88 ✔
688
   *   describing the expected and actual call time.
88 ✔
689
   * @param {object|Clock} [optionsOrClock] - Timer options or the clock which the timer gets time from.
88 ✔
690
   *   Supported options: `{ autostart?: boolean }`.
88 ✔
691
   * @param {Clock} [clock] - The clock which the timer gets time from when options are provided.
88 ✔
692
   * @return {Timer} - An instance of Timer.
88 ✔
693
   */
88 ✔
694
  createTimer(period, callback, optionsOrClock = null, clock = null) {
88 ✔
695
    let options = {};
241 ✔
696

241 ✔
697
    if (optionsOrClock instanceof Clock.Clock) {
241 ✔
698
      clock = optionsOrClock;
1 ✔
699
    } else if (optionsOrClock === null || optionsOrClock === undefined) {
241 ✔
700
      // Keep the 4th argument as the clock when the 3rd argument is omitted or explicitly null.
237 ✔
701
    } else {
240 ✔
702
      if (typeof optionsOrClock !== 'object' || Array.isArray(optionsOrClock)) {
3 !
UNCOV
703
        throw new TypeValidationError(
×
704
          'options',
×
705
          optionsOrClock,
×
706
          'object or Clock',
×
707
          {
×
708
            nodeName: this.name(),
×
709
          }
×
710
        );
×
711
      }
×
712
      options = optionsOrClock;
3 ✔
713
    }
3 ✔
714

241 ✔
715
    if (
241 ✔
716
      arguments.length === 4 &&
241 ✔
717
      clock !== null &&
241 ✔
718
      !(clock instanceof Clock.Clock)
1 ✔
719
    ) {
241 !
UNCOV
720
      throw new TypeValidationError('clock', clock, 'Clock', {
×
721
        nodeName: this.name(),
×
722
      });
×
723
    }
×
724

241 ✔
725
    if (typeof period !== 'bigint') {
241 ✔
726
      throw new TypeValidationError('period', period, 'bigint', {
1 ✔
727
        nodeName: this.name(),
1 ✔
728
      });
1 ✔
729
    }
1 ✔
730
    if (typeof callback !== 'function') {
241 ✔
731
      throw new TypeValidationError('callback', callback, 'function', {
1 ✔
732
        nodeName: this.name(),
1 ✔
733
      });
1 ✔
734
    }
1 ✔
735
    if (
239 ✔
736
      options.autostart !== undefined &&
241 ✔
737
      typeof options.autostart !== 'boolean'
3 ✔
738
    ) {
241 ✔
739
      throw new TypeValidationError(
1 ✔
740
        'options.autostart',
1 ✔
741
        options.autostart,
1 ✔
742
        'boolean',
1 ✔
743
        {
1 ✔
744
          nodeName: this.name(),
1 ✔
745
        }
1 ✔
746
      );
1 ✔
747
    }
1 ✔
748

238 ✔
749
    const timerClock = clock || this._clock;
241 ✔
750
    const autostart = options.autostart ?? true;
241 ✔
751
    let timerHandle = rclnodejs.createTimer(
241 ✔
752
      timerClock.handle,
241 ✔
753
      this.context.handle,
241 ✔
754
      period,
241 ✔
755
      autostart
241 ✔
756
    );
241 ✔
757
    let timer = new Timer(timerHandle, period, callback, timerClock);
241 ✔
758
    debug('Finish creating timer, period = %d.', period);
241 ✔
759
    this._timers.push(timer);
241 ✔
760
    this.syncHandles();
241 ✔
761

241 ✔
762
    return timer;
241 ✔
763
  }
241 ✔
764

88 ✔
765
  /**
88 ✔
766
   * Create a Rate.
88 ✔
767
   *
88 ✔
768
   * @param {number} hz - The frequency of the rate timer; default is 1 hz.
88 ✔
769
   * @returns {Promise<Rate>} - Promise resolving to new instance of Rate.
88 ✔
770
   */
88 ✔
771
  async createRate(hz = 1) {
88 ✔
772
    if (typeof hz !== 'number') {
9 !
UNCOV
773
      throw new TypeValidationError('hz', hz, 'number', {
×
774
        nodeName: this.name(),
×
775
      });
×
776
    }
×
777

9 ✔
778
    const MAX_RATE_HZ_IN_MILLISECOND = 1000.0;
9 ✔
779
    if (hz <= 0.0 || hz > MAX_RATE_HZ_IN_MILLISECOND) {
9 ✔
780
      throw new RangeValidationError(
2 ✔
781
        'hz',
2 ✔
782
        hz,
2 ✔
783
        `0.0 < hz <= ${MAX_RATE_HZ_IN_MILLISECOND}`,
2 ✔
784
        {
2 ✔
785
          nodeName: this.name(),
2 ✔
786
        }
2 ✔
787
      );
2 ✔
788
    }
2 ✔
789

7 ✔
790
    // lazy initialize rateTimerServer
7 ✔
791
    if (!this._rateTimerServer) {
9 ✔
792
      this._rateTimerServer = new Rates.RateTimerServer(this);
5 ✔
793
      await this._rateTimerServer.init();
5 ✔
794
    }
5 ✔
795

7 ✔
796
    const period = Math.round(1000 / hz);
7 ✔
797
    const timer = this._rateTimerServer.createTimer(BigInt(period) * 1000000n);
7 ✔
798
    const rate = new Rates.Rate(hz, timer);
7 ✔
799

7 ✔
800
    return rate;
7 ✔
801
  }
9 ✔
802

88 ✔
803
  /**
88 ✔
804
   * Create a Publisher.
88 ✔
805
   * @param {function|string|object} typeClass - The ROS message class,
88 ✔
806
        OR a string representing the message class, e.g. 'std_msgs/msg/String',
88 ✔
807
        OR an object representing the message class, e.g. {package: 'std_msgs', type: 'msg', name: 'String'}
88 ✔
808
   * @param {string} topic - The name of the topic.
88 ✔
809
   * @param {object} options - The options argument used to parameterize the publisher.
88 ✔
810
   * @param {boolean} options.enableTypedArray - The topic will use TypedArray if necessary, default: true.
88 ✔
811
   * @param {QoS} options.qos - ROS Middleware "quality of service" settings for the publisher, default: QoS.profileDefault.
88 ✔
812
   * @param {QoSOverridingOptions} [options.qosOverridingOptions] - If provided, declares read-only ROS parameters
88 ✔
813
   *  for the specified QoS policies (e.g. `qos_overrides./topic.publisher.depth`). These can be overridden at
88 ✔
814
   *  startup via `--ros-args -p` or `--params-file`. If qos is a profile string, it will be resolved to a
88 ✔
815
   *  mutable QoS object before overrides are applied.
88 ✔
816
   * @param {PublisherEventCallbacks} eventCallbacks - The event callbacks for the publisher.
88 ✔
817
   * @return {Publisher} - An instance of Publisher.
88 ✔
818
   */
88 ✔
819
  createPublisher(typeClass, topic, options, eventCallbacks) {
88 ✔
820
    return this._createPublisher(
1,536 ✔
821
      typeClass,
1,536 ✔
822
      topic,
1,536 ✔
823
      options,
1,536 ✔
824
      Publisher,
1,536 ✔
825
      eventCallbacks
1,536 ✔
826
    );
1,536 ✔
827
  }
1,536 ✔
828

88 ✔
829
  _createPublisher(typeClass, topic, options, publisherClass, eventCallbacks) {
88 ✔
830
    if (typeof typeClass === 'string' || typeof typeClass === 'object') {
1,539 ✔
831
      typeClass = loader.loadInterface(typeClass);
1,515 ✔
832
    }
1,515 ✔
833
    options = this._validateOptions(options);
1,532 ✔
834

1,532 ✔
835
    if (typeof typeClass !== 'function') {
1,539 ✔
836
      throw new TypeValidationError('typeClass', typeClass, 'function', {
8 ✔
837
        nodeName: this.name(),
8 ✔
838
        entityType: 'publisher',
8 ✔
839
      });
8 ✔
840
    }
8 ✔
841
    if (typeof topic !== 'string') {
1,539 ✔
842
      throw new TypeValidationError('topic', topic, 'string', {
12 ✔
843
        nodeName: this.name(),
12 ✔
844
        entityType: 'publisher',
12 ✔
845
      });
12 ✔
846
    }
12 ✔
847
    if (
1,512 ✔
848
      eventCallbacks &&
1,539 ✔
849
      !(eventCallbacks instanceof PublisherEventCallbacks)
12 ✔
850
    ) {
1,539 !
UNCOV
851
      throw new TypeValidationError(
×
852
        'eventCallbacks',
×
853
        eventCallbacks,
×
854
        'PublisherEventCallbacks',
×
855
        {
×
856
          nodeName: this.name(),
×
857
          entityType: 'publisher',
×
858
          entityName: topic,
×
859
        }
×
860
      );
×
861
    }
×
862

1,512 ✔
863
    // Apply QoS overriding options if provided
1,512 ✔
864
    if (options.qosOverridingOptions) {
1,539 ✔
865
      const resolvedTopic = this.resolveTopicName(topic);
5 ✔
866
      if (typeof options.qos === 'string' || !(options.qos instanceof QoS)) {
5 ✔
867
        options.qos = _resolveQoS(options.qos);
2 ✔
868
      }
2 ✔
869
      declareQosParameters(
5 ✔
870
        'publisher',
5 ✔
871
        this,
5 ✔
872
        resolvedTopic,
5 ✔
873
        options.qos,
5 ✔
874
        options.qosOverridingOptions
5 ✔
875
      );
5 ✔
876
    }
5 ✔
877

1,511 ✔
878
    let publisher = publisherClass.createPublisher(
1,511 ✔
879
      this,
1,511 ✔
880
      typeClass,
1,511 ✔
881
      topic,
1,511 ✔
882
      options,
1,511 ✔
883
      eventCallbacks
1,511 ✔
884
    );
1,511 ✔
885
    debug('Finish creating publisher, topic = %s.', topic);
1,511 ✔
886
    this._publishers.push(publisher);
1,511 ✔
887
    return publisher;
1,511 ✔
888
  }
1,539 ✔
889

88 ✔
890
  /**
88 ✔
891
   * This callback is called when a message is published
88 ✔
892
   * @callback SubscriptionCallback
88 ✔
893
   * @param {Object} message - The message published
88 ✔
894
   * @see [Node.createSubscription]{@link Node#createSubscription}
88 ✔
895
   * @see [Node.createPublisher]{@link Node#createPublisher}
88 ✔
896
   * @see {@link Publisher}
88 ✔
897
   * @see {@link Subscription}
88 ✔
898
   */
88 ✔
899

88 ✔
900
  /**
88 ✔
901
   * Create a Subscription with optional content-filtering.
88 ✔
902
   * @param {function|string|object} typeClass - The ROS message class,
88 ✔
903
        OR a string representing the message class, e.g. 'std_msgs/msg/String',
88 ✔
904
        OR an object representing the message class, e.g. {package: 'std_msgs', type: 'msg', name: 'String'}
88 ✔
905
   * @param {string} topic - The name of the topic.
88 ✔
906
   * @param {object} options - The options argument used to parameterize the subscription.
88 ✔
907
   * @param {boolean} options.enableTypedArray - The topic will use TypedArray if necessary, default: true.
88 ✔
908
   * @param {QoS} options.qos - ROS Middleware "quality of service" settings for the subscription, default: QoS.profileDefault.
88 ✔
909
   * @param {boolean} options.isRaw - The topic is serialized when true, default: false.
88 ✔
910
   * @param {string} [options.serializationMode='default'] - Controls message serialization format:
88 ✔
911
   *  'default': Use native rclnodejs behavior (respects enableTypedArray setting),
88 ✔
912
   *  'plain': Convert TypedArrays to regular arrays,
88 ✔
913
   *  'json': Fully JSON-safe (handles TypedArrays, BigInt, etc.).
88 ✔
914
   * @param {object} [options.contentFilter=undefined] - The content-filter, default: undefined.
88 ✔
915
   *  Confirm that your RMW supports content-filtered topics before use. 
88 ✔
916
   * @param {string} options.contentFilter.expression - Specifies the criteria to select the data samples of
88 ✔
917
   *  interest. It is similar to the WHERE part of an SQL clause.
88 ✔
918
   * @param {string[]} [options.contentFilter.parameters=undefined] - Array of strings that give values to
88 ✔
919
   *  the ‘parameters’ (i.e., "%n" tokens) in the filter_expression. The number of supplied parameters must
88 ✔
920
   *  fit with the requested values in the filter_expression (i.e., the number of %n tokens). default: undefined.
88 ✔
921
   * @param {QoSOverridingOptions} [options.qosOverridingOptions] - If provided, declares read-only ROS parameters
88 ✔
922
   *  for the specified QoS policies (e.g. `qos_overrides./topic.subscription.depth`). These can be overridden at
88 ✔
923
   *  startup via `--ros-args -p` or `--params-file`. If qos is a profile string, it will be resolved to a
88 ✔
924
   *  mutable QoS object before overrides are applied.
88 ✔
925
   * @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.
88 ✔
926
   * @param {SubscriptionEventCallbacks} eventCallbacks - The event callbacks for the subscription.
88 ✔
927
   * @return {Subscription} - An instance of Subscription.
88 ✔
928
   * @throws {ERROR} - May throw an RMW error if content-filter is malformed. 
88 ✔
929
   * @see {@link SubscriptionCallback}
88 ✔
930
   * @see {@link https://www.omg.org/spec/DDS/1.4/PDF|Content-filter details at DDS 1.4 specification, Annex B}
88 ✔
931
   */
88 ✔
932
  createSubscription(typeClass, topic, options, callback, eventCallbacks) {
88 ✔
933
    if (typeof typeClass === 'string' || typeof typeClass === 'object') {
609 ✔
934
      typeClass = loader.loadInterface(typeClass);
500 ✔
935
    }
500 ✔
936

602 ✔
937
    if (typeof options === 'function') {
609 ✔
938
      callback = options;
471 ✔
939
      options = undefined;
471 ✔
940
    }
471 ✔
941
    options = this._validateOptions(options);
602 ✔
942

602 ✔
943
    if (typeof typeClass !== 'function') {
609 ✔
944
      throw new TypeValidationError('typeClass', typeClass, 'function', {
4 ✔
945
        nodeName: this.name(),
4 ✔
946
        entityType: 'subscription',
4 ✔
947
      });
4 ✔
948
    }
4 ✔
949
    if (typeof topic !== 'string') {
609 ✔
950
      throw new TypeValidationError('topic', topic, 'string', {
6 ✔
951
        nodeName: this.name(),
6 ✔
952
        entityType: 'subscription',
6 ✔
953
      });
6 ✔
954
    }
6 ✔
955
    if (typeof callback !== 'function') {
609 !
UNCOV
956
      throw new TypeValidationError('callback', callback, 'function', {
×
957
        nodeName: this.name(),
×
958
        entityType: 'subscription',
×
959
        entityName: topic,
×
960
      });
×
961
    }
×
962
    if (
582 ✔
963
      eventCallbacks &&
609 ✔
964
      !(eventCallbacks instanceof SubscriptionEventCallbacks)
14 ✔
965
    ) {
609 !
UNCOV
966
      throw new TypeValidationError(
×
967
        'eventCallbacks',
×
968
        eventCallbacks,
×
969
        'SubscriptionEventCallbacks',
×
970
        {
×
971
          nodeName: this.name(),
×
972
          entityType: 'subscription',
×
973
          entityName: topic,
×
974
        }
×
975
      );
×
976
    }
×
977

582 ✔
978
    // Apply QoS overriding options if provided
582 ✔
979
    if (options.qosOverridingOptions) {
609 ✔
980
      const resolvedTopic = this.resolveTopicName(topic);
2 ✔
981
      if (typeof options.qos === 'string' || !(options.qos instanceof QoS)) {
2 !
UNCOV
982
        options.qos = _resolveQoS(options.qos);
×
983
      }
×
984
      declareQosParameters(
2 ✔
985
        'subscription',
2 ✔
986
        this,
2 ✔
987
        resolvedTopic,
2 ✔
988
        options.qos,
2 ✔
989
        options.qosOverridingOptions
2 ✔
990
      );
2 ✔
991
    }
2 ✔
992

582 ✔
993
    let subscription = Subscription.createSubscription(
582 ✔
994
      this,
582 ✔
995
      typeClass,
582 ✔
996
      topic,
582 ✔
997
      options,
582 ✔
998
      callback,
582 ✔
999
      eventCallbacks
582 ✔
1000
    );
582 ✔
1001
    debug('Finish creating subscription, topic = %s.', topic);
582 ✔
1002
    this._subscriptions.push(subscription);
582 ✔
1003
    this.syncHandles();
582 ✔
1004

582 ✔
1005
    return subscription;
582 ✔
1006
  }
609 ✔
1007

88 ✔
1008
  /**
88 ✔
1009
   * Create a Subscription that returns an RxJS Observable.
88 ✔
1010
   * This allows using reactive programming patterns with ROS 2 messages.
88 ✔
1011
   *
88 ✔
1012
   * @param {function|string|object} typeClass - The ROS message class,
88 ✔
1013
   *      OR a string representing the message class, e.g. 'std_msgs/msg/String',
88 ✔
1014
   *      OR an object representing the message class, e.g. {package: 'std_msgs', type: 'msg', name: 'String'}
88 ✔
1015
   * @param {string} topic - The name of the topic.
88 ✔
1016
   * @param {object} [options] - The options argument used to parameterize the subscription.
88 ✔
1017
   * @param {boolean} [options.enableTypedArray=true] - The topic will use TypedArray if necessary.
88 ✔
1018
   * @param {QoS} [options.qos=QoS.profileDefault] - ROS Middleware "quality of service" settings.
88 ✔
1019
   * @param {boolean} [options.isRaw=false] - The topic is serialized when true.
88 ✔
1020
   * @param {string} [options.serializationMode='default'] - Controls message serialization format.
88 ✔
1021
   * @param {object} [options.contentFilter] - The content-filter (if supported by RMW).
88 ✔
1022
   * @param {SubscriptionEventCallbacks} [eventCallbacks] - The event callbacks for the subscription.
88 ✔
1023
   * @return {ObservableSubscription} - An ObservableSubscription with an RxJS Observable.
88 ✔
1024
   */
88 ✔
1025
  createObservableSubscription(typeClass, topic, options, eventCallbacks) {
88 ✔
1026
    let observableSubscription = null;
7 ✔
1027

7 ✔
1028
    const subscription = this.createSubscription(
7 ✔
1029
      typeClass,
7 ✔
1030
      topic,
7 ✔
1031
      options,
7 ✔
1032
      (message) => {
7 ✔
1033
        if (observableSubscription) {
8 ✔
1034
          observableSubscription._emit(message);
8 ✔
1035
        }
8 ✔
1036
      },
7 ✔
1037
      eventCallbacks
7 ✔
1038
    );
7 ✔
1039

7 ✔
1040
    observableSubscription = new ObservableSubscription(subscription);
7 ✔
1041
    return observableSubscription;
7 ✔
1042
  }
7 ✔
1043

88 ✔
1044
  /**
88 ✔
1045
   * Create a Client.
88 ✔
1046
   * @param {function|string|object} typeClass - The ROS message class,
88 ✔
1047
        OR a string representing the message class, e.g. 'std_msgs/msg/String',
88 ✔
1048
        OR an object representing the message class, e.g. {package: 'std_msgs', type: 'msg', name: 'String'}
88 ✔
1049
   * @param {string} serviceName - The service name to request.
88 ✔
1050
   * @param {object} options - The options argument used to parameterize the client.
88 ✔
1051
   * @param {boolean} options.enableTypedArray - The response will use TypedArray if necessary, default: true.
88 ✔
1052
   * @param {QoS} options.qos - ROS Middleware "quality of service" settings for the client, default: QoS.profileDefault.
88 ✔
1053
   * @return {Client} - An instance of Client.
88 ✔
1054
   */
88 ✔
1055
  createClient(typeClass, serviceName, options) {
88 ✔
1056
    if (typeof typeClass === 'string' || typeof typeClass === 'object') {
211 ✔
1057
      typeClass = loader.loadInterface(typeClass);
200 ✔
1058
    }
200 ✔
1059
    options = this._validateOptions(options);
204 ✔
1060

204 ✔
1061
    if (typeof typeClass !== 'function') {
211 ✔
1062
      throw new TypeValidationError('typeClass', typeClass, 'function', {
8 ✔
1063
        nodeName: this.name(),
8 ✔
1064
        entityType: 'client',
8 ✔
1065
      });
8 ✔
1066
    }
8 ✔
1067
    if (typeof serviceName !== 'string') {
211 ✔
1068
      throw new TypeValidationError('serviceName', serviceName, 'string', {
12 ✔
1069
        nodeName: this.name(),
12 ✔
1070
        entityType: 'client',
12 ✔
1071
      });
12 ✔
1072
    }
12 ✔
1073

184 ✔
1074
    let client = Client.createClient(
184 ✔
1075
      this.handle,
184 ✔
1076
      serviceName,
184 ✔
1077
      typeClass,
184 ✔
1078
      options
184 ✔
1079
    );
184 ✔
1080
    debug('Finish creating client, service = %s.', serviceName);
184 ✔
1081
    this._clients.push(client);
184 ✔
1082
    this.syncHandles();
184 ✔
1083

184 ✔
1084
    return client;
184 ✔
1085
  }
211 ✔
1086

88 ✔
1087
  /**
88 ✔
1088
   * This callback is called when a request is sent to service
88 ✔
1089
   * @callback RequestCallback
88 ✔
1090
   * @param {Object} request - The request sent to the service
88 ✔
1091
   * @param {Response} response - The response to client.
88 ✔
1092
        Use [response.send()]{@link Response#send} to send response object to client
88 ✔
1093
   * @return {undefined}
88 ✔
1094
   * @see [Node.createService]{@link Node#createService}
88 ✔
1095
   * @see [Client.sendRequest]{@link Client#sendRequest}
88 ✔
1096
   * @see {@link Client}
88 ✔
1097
   * @see {@link Service}
88 ✔
1098
   * @see {@link Response#send}
88 ✔
1099
   */
88 ✔
1100

88 ✔
1101
  /**
88 ✔
1102
   * Create a Service.
88 ✔
1103
   * @param {function|string|object} typeClass - The ROS message class,
88 ✔
1104
        OR a string representing the message class, e.g. 'std_msgs/msg/String',
88 ✔
1105
        OR an object representing the message class, e.g. {package: 'std_msgs', type: 'msg', name: 'String'}
88 ✔
1106
   * @param {string} serviceName - The service name to offer.
88 ✔
1107
   * @param {object} options - The options argument used to parameterize the service.
88 ✔
1108
   * @param {boolean} options.enableTypedArray - The request will use TypedArray if necessary, default: true.
88 ✔
1109
   * @param {QoS} options.qos - ROS Middleware "quality of service" settings for the service, default: QoS.profileDefault.
88 ✔
1110
   * @param {RequestCallback} callback - The callback to be called when receiving request.
88 ✔
1111
   * @return {Service} - An instance of Service.
88 ✔
1112
   * @see {@link RequestCallback}
88 ✔
1113
   */
88 ✔
1114
  createService(typeClass, serviceName, options, callback) {
88 ✔
1115
    if (typeof typeClass === 'string' || typeof typeClass === 'object') {
6,441 ✔
1116
      typeClass = loader.loadInterface(typeClass);
6,430 ✔
1117
    }
6,430 ✔
1118

6,434 ✔
1119
    if (typeof options === 'function') {
6,441 ✔
1120
      callback = options;
6,411 ✔
1121
      options = undefined;
6,411 ✔
1122
    }
6,411 ✔
1123
    options = this._validateOptions(options);
6,434 ✔
1124

6,434 ✔
1125
    if (typeof typeClass !== 'function') {
6,441 ✔
1126
      throw new TypeValidationError('typeClass', typeClass, 'function', {
4 ✔
1127
        nodeName: this.name(),
4 ✔
1128
        entityType: 'service',
4 ✔
1129
      });
4 ✔
1130
    }
4 ✔
1131
    if (typeof serviceName !== 'string') {
6,441 ✔
1132
      throw new TypeValidationError('serviceName', serviceName, 'string', {
6 ✔
1133
        nodeName: this.name(),
6 ✔
1134
        entityType: 'service',
6 ✔
1135
      });
6 ✔
1136
    }
6 ✔
1137
    if (typeof callback !== 'function') {
6,441 !
UNCOV
1138
      throw new TypeValidationError('callback', callback, 'function', {
×
1139
        nodeName: this.name(),
×
1140
        entityType: 'service',
×
1141
        entityName: serviceName,
×
1142
      });
×
1143
    }
×
1144

6,414 ✔
1145
    let service = Service.createService(
6,414 ✔
1146
      this.handle,
6,414 ✔
1147
      serviceName,
6,414 ✔
1148
      typeClass,
6,414 ✔
1149
      options,
6,414 ✔
1150
      callback
6,414 ✔
1151
    );
6,414 ✔
1152
    debug('Finish creating service, service = %s.', serviceName);
6,414 ✔
1153
    this._services.push(service);
6,414 ✔
1154
    this.syncHandles();
6,414 ✔
1155

6,414 ✔
1156
    return service;
6,414 ✔
1157
  }
6,441 ✔
1158

88 ✔
1159
  /**
88 ✔
1160
   * Create a ParameterClient for accessing parameters on a remote node.
88 ✔
1161
   * @param {string} remoteNodeName - The name of the remote node whose parameters to access.
88 ✔
1162
   * @param {object} [options] - Options for parameter client.
88 ✔
1163
   * @param {number} [options.timeout=5000] - Default timeout in milliseconds for service calls.
88 ✔
1164
   * @return {ParameterClient} - An instance of ParameterClient.
88 ✔
1165
   */
88 ✔
1166
  createParameterClient(remoteNodeName, options = {}) {
88 ✔
1167
    if (typeof remoteNodeName !== 'string' || remoteNodeName.trim() === '') {
103 !
UNCOV
1168
      throw new TypeError('Remote node name must be a non-empty string');
×
1169
    }
×
1170

103 ✔
1171
    const parameterClient = new ParameterClient(this, remoteNodeName, options);
103 ✔
1172
    debug(
103 ✔
1173
      'Finish creating parameter client for remote node = %s.',
103 ✔
1174
      remoteNodeName
103 ✔
1175
    );
103 ✔
1176
    this._parameterClients.push(parameterClient);
103 ✔
1177

103 ✔
1178
    return parameterClient;
103 ✔
1179
  }
103 ✔
1180

88 ✔
1181
  /**
88 ✔
1182
   * Create a ParameterWatcher for watching parameter changes on a remote node.
88 ✔
1183
   * @param {string} remoteNodeName - The name of the remote node whose parameters to watch.
88 ✔
1184
   * @param {string[]} parameterNames - Array of parameter names to watch.
88 ✔
1185
   * @param {object} [options] - Options for parameter watcher.
88 ✔
1186
   * @param {number} [options.timeout=5000] - Default timeout in milliseconds for service calls.
88 ✔
1187
   * @return {ParameterWatcher} - An instance of ParameterWatcher.
88 ✔
1188
   */
88 ✔
1189
  createParameterWatcher(remoteNodeName, parameterNames, options = {}) {
88 ✔
1190
    const watcher = new ParameterWatcher(
57 ✔
1191
      this,
57 ✔
1192
      remoteNodeName,
57 ✔
1193
      parameterNames,
57 ✔
1194
      options
57 ✔
1195
    );
57 ✔
1196
    debug(
57 ✔
1197
      'Finish creating parameter watcher for remote node = %s.',
57 ✔
1198
      remoteNodeName
57 ✔
1199
    );
57 ✔
1200
    this._parameterWatchers.push(watcher);
57 ✔
1201

57 ✔
1202
    return watcher;
57 ✔
1203
  }
57 ✔
1204

88 ✔
1205
  /**
88 ✔
1206
   * Create a guard condition.
88 ✔
1207
   * @param {Function} callback - The callback to be called when the guard condition is triggered.
88 ✔
1208
   * @return {GuardCondition} - An instance of GuardCondition.
88 ✔
1209
   */
88 ✔
1210
  createGuardCondition(callback) {
88 ✔
1211
    if (typeof callback !== 'function') {
266 !
UNCOV
1212
      throw new TypeValidationError('callback', callback, 'function', {
×
1213
        nodeName: this.name(),
×
1214
        entityType: 'guard_condition',
×
1215
      });
×
1216
    }
×
1217

266 ✔
1218
    let guard = GuardCondition.createGuardCondition(callback, this.context);
266 ✔
1219
    debug('Finish creating guard condition');
266 ✔
1220
    this._guards.push(guard);
266 ✔
1221
    this.syncHandles();
266 ✔
1222

266 ✔
1223
    return guard;
266 ✔
1224
  }
266 ✔
1225

88 ✔
1226
  /**
88 ✔
1227
   * Destroy all resource allocated by this node, including
88 ✔
1228
   * <code>Timer</code>s/<code>Publisher</code>s/<code>Subscription</code>s
88 ✔
1229
   * /<code>Client</code>s/<code>Service</code>s
88 ✔
1230
   * @return {undefined}
88 ✔
1231
   */
88 ✔
1232
  destroy() {
88 ✔
1233
    this._dispatchGeneration++;
1,139 ✔
1234
    if (this.spinning) {
1,139 ✔
1235
      this.stop();
660 ✔
1236
    }
660 ✔
1237

1,139 ✔
1238
    // Action servers/clients require manual destruction due to circular reference with goal handles.
1,139 ✔
1239
    this._actionClients.forEach((actionClient) => actionClient.destroy());
1,139 ✔
1240
    this._actionServers.forEach((actionServer) => actionServer.destroy());
1,139 ✔
1241

1,139 ✔
1242
    this._parameterClients.forEach((paramClient) => paramClient.destroy());
1,139 ✔
1243
    this._parameterWatchers.forEach((watcher) => watcher.destroy());
1,139 ✔
1244
    this._parameterEventHandlers.forEach((handler) => handler.destroy());
1,139 ✔
1245

1,139 ✔
1246
    this.context.onNodeDestroyed(this);
1,139 ✔
1247

1,139 ✔
1248
    if (this._enableRosout) {
1,139 ✔
1249
      rclnodejs.finiRosoutPublisherForNode(this.handle);
1,066 ✔
1250
      this._enableRosout = false;
1,066 ✔
1251
    }
1,066 ✔
1252

1,139 ✔
1253
    // Timer handles are children of their clock handle rather than of the
1,139 ✔
1254
    // node handle, so release them here instead of waiting for the clock to
1,139 ✔
1255
    // be garbage collected.
1,139 ✔
1256
    this._timers.forEach((timer) => timer.handle.release());
1,139 ✔
1257
    this.handle.release();
1,139 ✔
1258
    this._clock = null;
1,139 ✔
1259
    this._timers = [];
1,139 ✔
1260
    this._publishers = [];
1,139 ✔
1261
    this._subscriptions = [];
1,139 ✔
1262
    this._clients = [];
1,139 ✔
1263
    this._services = [];
1,139 ✔
1264
    this._guards = [];
1,139 ✔
1265
    this._actionClients = [];
1,139 ✔
1266
    this._actionServers = [];
1,139 ✔
1267
    this._parameterClients = [];
1,139 ✔
1268
    this._parameterWatchers = [];
1,139 ✔
1269
    this._parameterEventHandlers = [];
1,139 ✔
1270

1,139 ✔
1271
    if (this._rateTimerServer) {
1,139 ✔
1272
      this._rateTimerServer.shutdown();
5 ✔
1273
      this._rateTimerServer = null;
5 ✔
1274
    }
5 ✔
1275
  }
1,139 ✔
1276

88 ✔
1277
  /**
88 ✔
1278
   * Destroy a Publisher.
88 ✔
1279
   * @param {Publisher} publisher - The Publisher to be destroyed.
88 ✔
1280
   * @return {undefined}
88 ✔
1281
   */
88 ✔
1282
  destroyPublisher(publisher) {
88 ✔
1283
    if (!(publisher instanceof Publisher)) {
35 ✔
1284
      throw new TypeValidationError(
2 ✔
1285
        'publisher',
2 ✔
1286
        publisher,
2 ✔
1287
        'Publisher instance',
2 ✔
1288
        {
2 ✔
1289
          nodeName: this.name(),
2 ✔
1290
        }
2 ✔
1291
      );
2 ✔
1292
    }
2 ✔
1293
    if (publisher.events) {
35 ✔
1294
      publisher.events.forEach((event) => {
4 ✔
1295
        this._destroyEntity(event, this._events);
4 ✔
1296
      });
4 ✔
1297
      publisher.events = [];
4 ✔
1298
    }
4 ✔
1299
    this._destroyEntity(publisher, this._publishers, false);
33 ✔
1300
  }
35 ✔
1301

88 ✔
1302
  /**
88 ✔
1303
   * Destroy a Subscription.
88 ✔
1304
   * @param {Subscription} subscription - The Subscription to be destroyed.
88 ✔
1305
   * @return {undefined}
88 ✔
1306
   */
88 ✔
1307
  destroySubscription(subscription) {
88 ✔
1308
    if (!(subscription instanceof Subscription)) {
233 ✔
1309
      throw new TypeValidationError(
2 ✔
1310
        'subscription',
2 ✔
1311
        subscription,
2 ✔
1312
        'Subscription instance',
2 ✔
1313
        {
2 ✔
1314
          nodeName: this.name(),
2 ✔
1315
        }
2 ✔
1316
      );
2 ✔
1317
    }
2 ✔
1318
    if (subscription.events) {
233 ✔
1319
      subscription.events.forEach((event) => {
5 ✔
1320
        this._destroyEntity(event, this._events);
5 ✔
1321
      });
5 ✔
1322
      subscription.events = [];
5 ✔
1323
    }
5 ✔
1324

231 ✔
1325
    this._destroyEntity(subscription, this._subscriptions);
231 ✔
1326
  }
233 ✔
1327

88 ✔
1328
  /**
88 ✔
1329
   * Destroy a Client.
88 ✔
1330
   * @param {Client} client - The Client to be destroyed.
88 ✔
1331
   * @return {undefined}
88 ✔
1332
   */
88 ✔
1333
  destroyClient(client) {
88 ✔
1334
    if (!(client instanceof Client)) {
129 ✔
1335
      throw new TypeValidationError('client', client, 'Client instance', {
2 ✔
1336
        nodeName: this.name(),
2 ✔
1337
      });
2 ✔
1338
    }
2 ✔
1339
    this._destroyEntity(client, this._clients);
127 ✔
1340
  }
129 ✔
1341

88 ✔
1342
  /**
88 ✔
1343
   * Destroy a Service.
88 ✔
1344
   * @param {Service} service - The Service to be destroyed.
88 ✔
1345
   * @return {undefined}
88 ✔
1346
   */
88 ✔
1347
  destroyService(service) {
88 ✔
1348
    if (!(service instanceof Service)) {
14 ✔
1349
      throw new TypeValidationError('service', service, 'Service instance', {
2 ✔
1350
        nodeName: this.name(),
2 ✔
1351
      });
2 ✔
1352
    }
2 ✔
1353
    this._destroyEntity(service, this._services);
12 ✔
1354
  }
14 ✔
1355

88 ✔
1356
  /**
88 ✔
1357
   * Destroy a ParameterClient.
88 ✔
1358
   * @param {ParameterClient} parameterClient - The ParameterClient to be destroyed.
88 ✔
1359
   * @return {undefined}
88 ✔
1360
   */
88 ✔
1361
  destroyParameterClient(parameterClient) {
88 ✔
1362
    if (!(parameterClient instanceof ParameterClient)) {
54 !
UNCOV
1363
      throw new TypeError('Invalid argument');
×
1364
    }
×
1365
    this._removeEntityFromArray(parameterClient, this._parameterClients);
54 ✔
1366
    parameterClient.destroy();
54 ✔
1367
  }
54 ✔
1368

88 ✔
1369
  /**
88 ✔
1370
   * Destroy a ParameterWatcher.
88 ✔
1371
   * @param {ParameterWatcher} watcher - The ParameterWatcher to be destroyed.
88 ✔
1372
   * @return {undefined}
88 ✔
1373
   */
88 ✔
1374
  destroyParameterWatcher(watcher) {
88 ✔
1375
    if (!(watcher instanceof ParameterWatcher)) {
1 !
UNCOV
1376
      throw new TypeError('Invalid argument');
×
1377
    }
×
1378
    this._removeEntityFromArray(watcher, this._parameterWatchers);
1 ✔
1379
    watcher.destroy();
1 ✔
1380
  }
1 ✔
1381

88 ✔
1382
  /**
88 ✔
1383
   * Create a ParameterEventHandler that monitors parameter changes on any node.
88 ✔
1384
   *
88 ✔
1385
   * Unlike {@link ParameterWatcher} which watches specific parameters on a single
88 ✔
1386
   * remote node, ParameterEventHandler can register callbacks for parameters on
88 ✔
1387
   * any node in the ROS 2 graph by subscribing to /parameter_events.
88 ✔
1388
   *
88 ✔
1389
   * @param {object} [options] - Options for the handler
88 ✔
1390
   * @param {object} [options.qos] - QoS profile for the parameter_events subscription
88 ✔
1391
   * @return {ParameterEventHandler} - An instance of ParameterEventHandler
88 ✔
1392
   * @see {@link ParameterEventHandler}
88 ✔
1393
   */
88 ✔
1394
  createParameterEventHandler(options = {}) {
88 ✔
1395
    const handler = new ParameterEventHandler(this, options);
18 ✔
1396
    debug('Created ParameterEventHandler on node=%s', this.name());
18 ✔
1397
    this._parameterEventHandlers.push(handler);
18 ✔
1398
    return handler;
18 ✔
1399
  }
18 ✔
1400

88 ✔
1401
  /**
88 ✔
1402
   * Destroy a ParameterEventHandler.
88 ✔
1403
   * @param {ParameterEventHandler} handler - The handler to be destroyed.
88 ✔
1404
   * @return {undefined}
88 ✔
1405
   */
88 ✔
1406
  destroyParameterEventHandler(handler) {
88 ✔
1407
    if (!(handler instanceof ParameterEventHandler)) {
1 !
UNCOV
1408
      throw new TypeError('Invalid argument');
×
1409
    }
×
1410
    this._removeEntityFromArray(handler, this._parameterEventHandlers);
1 ✔
1411
    handler.destroy();
1 ✔
1412
  }
1 ✔
1413

88 ✔
1414
  /**
88 ✔
1415
   * Destroy a Timer.
88 ✔
1416
   * A destroyed timer is skipped even if it was already ready for dispatch.
88 ✔
1417
   * @param {Timer} timer - The Timer to be destroyed.
88 ✔
1418
   * @return {undefined}
88 ✔
1419
   */
88 ✔
1420
  destroyTimer(timer) {
88 ✔
1421
    if (!(timer instanceof Timer)) {
118 ✔
1422
      throw new TypeValidationError('timer', timer, 'Timer instance', {
2 ✔
1423
        nodeName: this.name(),
2 ✔
1424
      });
2 ✔
1425
    }
2 ✔
1426
    this._destroyEntity(timer, this._timers);
116 ✔
1427
  }
118 ✔
1428

88 ✔
1429
  /**
88 ✔
1430
   * Destroy a guard condition.
88 ✔
1431
   * @param {GuardCondition} guard - The guard condition to be destroyed.
88 ✔
1432
   * @return {undefined}
88 ✔
1433
   */
88 ✔
1434
  destroyGuardCondition(guard) {
88 ✔
1435
    if (!(guard instanceof GuardCondition)) {
228 !
UNCOV
1436
      throw new TypeValidationError('guard', guard, 'GuardCondition instance', {
×
1437
        nodeName: this.name(),
×
1438
      });
×
1439
    }
×
1440
    this._destroyEntity(guard, this._guards);
228 ✔
1441
  }
228 ✔
1442

88 ✔
1443
  /**
88 ✔
1444
   * Get the name of the node.
88 ✔
1445
   * @return {string}
88 ✔
1446
   */
88 ✔
1447
  name() {
88 ✔
1448
    return rclnodejs.getNodeName(this.handle);
6,343 ✔
1449
  }
6,343 ✔
1450

88 ✔
1451
  /**
88 ✔
1452
   * Get the namespace of the node.
88 ✔
1453
   * @return {string}
88 ✔
1454
   */
88 ✔
1455
  namespace() {
88 ✔
1456
    return rclnodejs.getNamespace(this.handle);
5,812 ✔
1457
  }
5,812 ✔
1458

88 ✔
1459
  /**
88 ✔
1460
   * Get the context in which this node was created.
88 ✔
1461
   * @return {Context}
88 ✔
1462
   */
88 ✔
1463
  get context() {
88 ✔
1464
    return this._context;
7,997 ✔
1465
  }
7,997 ✔
1466

88 ✔
1467
  /**
88 ✔
1468
   * Get the nodes logger.
88 ✔
1469
   * @returns {Logger} - The logger for the node.
88 ✔
1470
   */
88 ✔
1471
  getLogger() {
88 ✔
1472
    return this._logger;
400 ✔
1473
  }
400 ✔
1474

88 ✔
1475
  /**
88 ✔
1476
   * Get the clock used by the node.
88 ✔
1477
   * @returns {Clock} - The nodes clock.
88 ✔
1478
   */
88 ✔
1479
  getClock() {
88 ✔
1480
    return this._clock;
169 ✔
1481
  }
169 ✔
1482

88 ✔
1483
  /**
88 ✔
1484
   * Get the current time using the node's clock.
88 ✔
1485
   * @returns {Timer} - The current time.
88 ✔
1486
   */
88 ✔
1487
  now() {
88 ✔
1488
    return this.getClock().now();
2 ✔
1489
  }
2 ✔
1490

88 ✔
1491
  /**
88 ✔
1492
   * Get the list of published topics discovered by the provided node for the remote node name.
88 ✔
1493
   * @param {string} nodeName - The name of the node.
88 ✔
1494
   * @param {string} namespace - The name of the namespace.
88 ✔
1495
   * @param {boolean} noDemangle - If true topic names and types returned will not be demangled, default: false.
88 ✔
1496
   * @return {Array<{name: string, types: Array<string>}>} - An array of the names and types.
88 ✔
1497
   */
88 ✔
1498
  getPublisherNamesAndTypesByNode(nodeName, namespace, noDemangle = false) {
88 ✔
1499
    return rclnodejs.getPublisherNamesAndTypesByNode(
2 ✔
1500
      this.handle,
2 ✔
1501
      nodeName,
2 ✔
1502
      namespace,
2 ✔
1503
      noDemangle
2 ✔
1504
    );
2 ✔
1505
  }
2 ✔
1506

88 ✔
1507
  /**
88 ✔
1508
   * Get the list of published topics discovered by the provided node for the remote node name.
88 ✔
1509
   * @param {string} nodeName - The name of the node.
88 ✔
1510
   * @param {string} namespace - The name of the namespace.
88 ✔
1511
   * @param {boolean} noDemangle - If true topic names and types returned will not be demangled, default: false.
88 ✔
1512
   * @return {Array<{name: string, types: Array<string>}>} - An array of the names and types.
88 ✔
1513
   */
88 ✔
1514
  getSubscriptionNamesAndTypesByNode(nodeName, namespace, noDemangle = false) {
88 ✔
UNCOV
1515
    return rclnodejs.getSubscriptionNamesAndTypesByNode(
×
1516
      this.handle,
×
1517
      nodeName,
×
1518
      namespace,
×
1519
      noDemangle
×
1520
    );
×
1521
  }
×
1522

88 ✔
1523
  /**
88 ✔
1524
   * Get service names and types for which a remote node has servers.
88 ✔
1525
   * @param {string} nodeName - The name of the node.
88 ✔
1526
   * @param {string} namespace - The name of the namespace.
88 ✔
1527
   * @return {Array<{name: string, types: Array<string>}>} - An array of the names and types.
88 ✔
1528
   */
88 ✔
1529
  getServiceNamesAndTypesByNode(nodeName, namespace) {
88 ✔
UNCOV
1530
    return rclnodejs.getServiceNamesAndTypesByNode(
×
1531
      this.handle,
×
1532
      nodeName,
×
1533
      namespace
×
1534
    );
×
1535
  }
×
1536

88 ✔
1537
  /**
88 ✔
1538
   * Get service names and types for which a remote node has clients.
88 ✔
1539
   * @param {string} nodeName - The name of the node.
88 ✔
1540
   * @param {string} namespace - The name of the namespace.
88 ✔
1541
   * @return {Array<{name: string, types: Array<string>}>} - An array of the names and types.
88 ✔
1542
   */
88 ✔
1543
  getClientNamesAndTypesByNode(nodeName, namespace) {
88 ✔
UNCOV
1544
    return rclnodejs.getClientNamesAndTypesByNode(
×
1545
      this.handle,
×
1546
      nodeName,
×
1547
      namespace
×
1548
    );
×
1549
  }
×
1550

88 ✔
1551
  /**
88 ✔
1552
   * Get the list of topics discovered by the provided node.
88 ✔
1553
   * @param {boolean} noDemangle - If true topic names and types returned will not be demangled, default: false.
88 ✔
1554
   * @return {Array<{name: string, types: Array<string>}>} - An array of the names and types.
88 ✔
1555
   */
88 ✔
1556
  getTopicNamesAndTypes(noDemangle = false) {
88 ✔
UNCOV
1557
    return rclnodejs.getTopicNamesAndTypes(this.handle, noDemangle);
×
1558
  }
×
1559

88 ✔
1560
  /**
88 ✔
1561
   * Get the list of services discovered by the provided node.
88 ✔
1562
   * @return {Array<{name: string, types: Array<string>}>} - An array of the names and types.
88 ✔
1563
   */
88 ✔
1564
  getServiceNamesAndTypes() {
88 ✔
1565
    return rclnodejs.getServiceNamesAndTypes(this.handle);
2 ✔
1566
  }
2 ✔
1567

88 ✔
1568
  /**
88 ✔
1569
   * Return the number of action clients on an action.
88 ✔
1570
   * @param {string} actionName - The action name to query.
88 ✔
1571
   * @returns {number|null} The number of action clients, or null when the ROS distro is older than Lyrical.
88 ✔
1572
   */
88 ✔
1573
  countActionClients(actionName) {
88 ✔
1574
    if (typeof rclnodejs.countActionClients !== 'function') {
2 !
UNCOV
1575
      console.warn(
×
1576
        'countActionClients is not supported by this version of ROS 2'
×
1577
      );
×
1578
      return null;
×
1579
    }
×
1580
    return rclnodejs.countActionClients(
2 ✔
1581
      this.handle,
2 ✔
1582
      this._getValidatedActionName(actionName)
2 ✔
1583
    );
2 ✔
1584
  }
2 ✔
1585

88 ✔
1586
  /**
88 ✔
1587
   * Return the number of action servers on an action.
88 ✔
1588
   * @param {string} actionName - The action name to query.
88 ✔
1589
   * @returns {number|null} The number of action servers, or null when the ROS distro is older than Lyrical.
88 ✔
1590
   */
88 ✔
1591
  countActionServers(actionName) {
88 ✔
1592
    if (typeof rclnodejs.countActionServers !== 'function') {
2 !
UNCOV
1593
      console.warn(
×
1594
        'countActionServers is not supported by this version of ROS 2'
×
1595
      );
×
1596
      return null;
×
1597
    }
×
1598
    return rclnodejs.countActionServers(
2 ✔
1599
      this.handle,
2 ✔
1600
      this._getValidatedActionName(actionName)
2 ✔
1601
    );
2 ✔
1602
  }
2 ✔
1603

88 ✔
1604
  /**
88 ✔
1605
   * Return endpoint information for action clients on an action.
88 ✔
1606
   * @param {string} actionName - The action name to query.
88 ✔
1607
   * @returns {Array<ActionEndpointInfo>|null} The action clients, or null when the ROS distro is older than Rolling.
88 ✔
1608
   */
88 ✔
1609
  getActionClientsInfoByAction(actionName) {
88 ✔
UNCOV
1610
    if (typeof rclnodejs.getActionClientsInfoByAction !== 'function') {
×
1611
      console.warn(
×
1612
        'getActionClientsInfoByAction is not supported by this version of ROS 2'
×
1613
      );
×
1614
      return null;
×
1615
    }
×
1616
    return rclnodejs
×
1617
      .getActionClientsInfoByAction(
×
1618
        this.handle,
×
1619
        this._getValidatedActionName(actionName)
×
1620
      )
×
1621
      .map((info) => new ActionEndpointInfo(info));
×
1622
  }
×
1623

88 ✔
1624
  /**
88 ✔
1625
   * Return endpoint information for action servers on an action.
88 ✔
1626
   * @param {string} actionName - The action name to query.
88 ✔
1627
   * @returns {Array<ActionEndpointInfo>|null} The action servers, or null when the ROS distro is older than Rolling.
88 ✔
1628
   */
88 ✔
1629
  getActionServersInfoByAction(actionName) {
88 ✔
UNCOV
1630
    if (typeof rclnodejs.getActionServersInfoByAction !== 'function') {
×
1631
      console.warn(
×
1632
        'getActionServersInfoByAction is not supported by this version of ROS 2'
×
1633
      );
×
1634
      return null;
×
1635
    }
×
1636
    return rclnodejs
×
1637
      .getActionServersInfoByAction(
×
1638
        this.handle,
×
1639
        this._getValidatedActionName(actionName)
×
1640
      )
×
1641
      .map((info) => new ActionEndpointInfo(info));
×
1642
  }
×
1643

88 ✔
1644
  /**
88 ✔
1645
   * Return a list of publishers on a given topic.
88 ✔
1646
   *
88 ✔
1647
   * The returned parameter is a list of TopicEndpointInfo objects, where each will contain
88 ✔
1648
   * the node name, node namespace, topic type, topic endpoint's GID, and its QoS profile.
88 ✔
1649
   *
88 ✔
1650
   * When the `no_mangle` parameter is `true`, the provided `topic` should be a valid
88 ✔
1651
   * topic name for the middleware (useful when combining ROS with native middleware (e.g. DDS)
88 ✔
1652
   * apps).  When the `no_mangle` parameter is `false`, the provided `topic` should
88 ✔
1653
   * follow ROS topic name conventions.
88 ✔
1654
   *
88 ✔
1655
   * `topic` may be a relative, private, or fully qualified topic name.
88 ✔
1656
   *  A relative or private topic will be expanded using this node's namespace and name.
88 ✔
1657
   *  The queried `topic` is not remapped.
88 ✔
1658
   *
88 ✔
1659
   * @param {string} topic - The topic on which to find the publishers.
88 ✔
1660
   * @param {boolean} [noDemangle=false] - If `true`, `topic` needs to be a valid middleware topic
88 ✔
1661
   *                               name, otherwise it should be a valid ROS topic name. Defaults to `false`.
88 ✔
1662
   * @returns {Array} - list of publishers
88 ✔
1663
   */
88 ✔
1664
  getPublishersInfoByTopic(topic, noDemangle = false) {
88 ✔
1665
    return rclnodejs.getPublishersInfoByTopic(
6 ✔
1666
      this.handle,
6 ✔
1667
      this._getValidatedTopic(topic, noDemangle),
6 ✔
1668
      noDemangle
6 ✔
1669
    );
6 ✔
1670
  }
6 ✔
1671

88 ✔
1672
  /**
88 ✔
1673
   * Return a list of subscriptions on a given topic.
88 ✔
1674
   *
88 ✔
1675
   * The returned parameter is a list of TopicEndpointInfo objects, where each will contain
88 ✔
1676
   * the node name, node namespace, topic type, topic endpoint's GID, and its QoS profile.
88 ✔
1677
   *
88 ✔
1678
   * When the `no_mangle` parameter is `true`, the provided `topic` should be a valid
88 ✔
1679
   * topic name for the middleware (useful when combining ROS with native middleware (e.g. DDS)
88 ✔
1680
   * apps).  When the `no_mangle` parameter is `false`, the provided `topic` should
88 ✔
1681
   * follow ROS topic name conventions.
88 ✔
1682
   *
88 ✔
1683
   * `topic` may be a relative, private, or fully qualified topic name.
88 ✔
1684
   *  A relative or private topic will be expanded using this node's namespace and name.
88 ✔
1685
   *  The queried `topic` is not remapped.
88 ✔
1686
   *
88 ✔
1687
   * @param {string} topic - The topic on which to find the subscriptions.
88 ✔
1688
   * @param {boolean} [noDemangle=false] -  If `true`, `topic` needs to be a valid middleware topic
88 ✔
1689
                                    name, otherwise it should be a valid ROS topic name. Defaults to `false`.
88 ✔
1690
   * @returns {Array} - list of subscriptions
88 ✔
1691
   */
88 ✔
1692
  getSubscriptionsInfoByTopic(topic, noDemangle = false) {
88 ✔
1693
    return rclnodejs.getSubscriptionsInfoByTopic(
2 ✔
1694
      this.handle,
2 ✔
1695
      this._getValidatedTopic(topic, noDemangle),
2 ✔
1696
      noDemangle
2 ✔
1697
    );
2 ✔
1698
  }
2 ✔
1699

88 ✔
1700
  /**
88 ✔
1701
   * Return a list of clients on a given service.
88 ✔
1702
   *
88 ✔
1703
   * The returned parameter is a list of ServiceEndpointInfo objects, where each will contain
88 ✔
1704
   * the node name, node namespace, service type, service endpoint's GID, and its QoS profile.
88 ✔
1705
   *
88 ✔
1706
   * When the `no_mangle` parameter is `true`, the provided `service` should be a valid
88 ✔
1707
   * service name for the middleware (useful when combining ROS with native middleware (e.g. DDS)
88 ✔
1708
   * apps).  When the `no_mangle` parameter is `false`, the provided `service` should
88 ✔
1709
   * follow ROS service name conventions.
88 ✔
1710
   *
88 ✔
1711
   * `service` may be a relative, private, or fully qualified service name.
88 ✔
1712
   *  A relative or private service will be expanded using this node's namespace and name.
88 ✔
1713
   *  The queried `service` is not remapped.
88 ✔
1714
   *
88 ✔
1715
   * @param {string} service - The service on which to find the clients.
88 ✔
1716
   * @param {boolean} [noDemangle=false] - If `true`, `service` needs to be a valid middleware service
88 ✔
1717
   *                               name, otherwise it should be a valid ROS service name. Defaults to `false`.
88 ✔
1718
   * @returns {Array} - list of clients
88 ✔
1719
   */
88 ✔
1720
  getClientsInfoByService(service, noDemangle = false) {
88 ✔
1721
    if (DistroUtils.getDistroId() < DistroUtils.DistroId.LYRICAL) {
2 !
UNCOV
1722
      console.warn(
×
1723
        'getClientsInfoByService is not supported by this version of ROS 2'
×
1724
      );
×
1725
      return null;
×
1726
    }
×
1727
    return rclnodejs.getClientsInfoByService(
2 ✔
1728
      this.handle,
2 ✔
1729
      this._getValidatedServiceName(service, noDemangle),
2 ✔
1730
      noDemangle
2 ✔
1731
    );
2 ✔
1732
  }
2 ✔
1733

88 ✔
1734
  /**
88 ✔
1735
   * Return a list of servers on a given service.
88 ✔
1736
   *
88 ✔
1737
   * The returned parameter is a list of ServiceEndpointInfo objects, where each will contain
88 ✔
1738
   * the node name, node namespace, service type, service endpoint's GID, and its QoS profile.
88 ✔
1739
   *
88 ✔
1740
   * When the `no_mangle` parameter is `true`, the provided `service` should be a valid
88 ✔
1741
   * service name for the middleware (useful when combining ROS with native middleware (e.g. DDS)
88 ✔
1742
   * apps).  When the `no_mangle` parameter is `false`, the provided `service` should
88 ✔
1743
   * follow ROS service name conventions.
88 ✔
1744
   *
88 ✔
1745
   * `service` may be a relative, private, or fully qualified service name.
88 ✔
1746
   *  A relative or private service will be expanded using this node's namespace and name.
88 ✔
1747
   *  The queried `service` is not remapped.
88 ✔
1748
   *
88 ✔
1749
   * @param {string} service - The service on which to find the servers.
88 ✔
1750
   * @param {boolean} [noDemangle=false] - If `true`, `service` needs to be a valid middleware service
88 ✔
1751
   *                               name, otherwise it should be a valid ROS service name. Defaults to `false`.
88 ✔
1752
   * @returns {Array} - list of servers
88 ✔
1753
   */
88 ✔
1754
  getServersInfoByService(service, noDemangle = false) {
88 ✔
1755
    if (DistroUtils.getDistroId() < DistroUtils.DistroId.LYRICAL) {
2 !
UNCOV
1756
      console.warn(
×
1757
        'getServersInfoByService is not supported by this version of ROS 2'
×
1758
      );
×
1759
      return null;
×
1760
    }
×
1761
    return rclnodejs.getServersInfoByService(
2 ✔
1762
      this.handle,
2 ✔
1763
      this._getValidatedServiceName(service, noDemangle),
2 ✔
1764
      noDemangle
2 ✔
1765
    );
2 ✔
1766
  }
2 ✔
1767

88 ✔
1768
  /**
88 ✔
1769
   * Get the list of nodes discovered by the provided node.
88 ✔
1770
   * @return {Array<string>} - An array of the names.
88 ✔
1771
   */
88 ✔
1772
  getNodeNames() {
88 ✔
1773
    return this.getNodeNamesAndNamespaces().map((item) => item.name);
27 ✔
1774
  }
27 ✔
1775

88 ✔
1776
  /**
88 ✔
1777
   * Get the list of nodes and their namespaces discovered by the provided node.
88 ✔
1778
   * @return {Array<{name: string, namespace: string}>} An array of the names and namespaces.
88 ✔
1779
   */
88 ✔
1780
  getNodeNamesAndNamespaces() {
88 ✔
1781
    return rclnodejs.getNodeNames(this.handle, /*getEnclaves=*/ false);
29 ✔
1782
  }
29 ✔
1783

88 ✔
1784
  /**
88 ✔
1785
   * Get the list of nodes and their namespaces with enclaves discovered by the provided node.
88 ✔
1786
   * @return {Array<{name: string, namespace: string, enclave: string}>} An array of the names, namespaces and enclaves.
88 ✔
1787
   */
88 ✔
1788
  getNodeNamesAndNamespacesWithEnclaves() {
88 ✔
1789
    return rclnodejs.getNodeNames(this.handle, /*getEnclaves=*/ true);
1 ✔
1790
  }
1 ✔
1791

88 ✔
1792
  /**
88 ✔
1793
   * Return the number of publishers on a given topic.
88 ✔
1794
   * @param {string} topic - The name of the topic.
88 ✔
1795
   * @returns {number} - Number of publishers on the given topic.
88 ✔
1796
   */
88 ✔
1797
  countPublishers(topic) {
88 ✔
1798
    let expandedTopic = rclnodejs.expandTopicName(
8 ✔
1799
      topic,
8 ✔
1800
      this.name(),
8 ✔
1801
      this.namespace()
8 ✔
1802
    );
8 ✔
1803
    rclnodejs.validateTopicName(expandedTopic);
8 ✔
1804

8 ✔
1805
    return rclnodejs.countPublishers(this.handle, expandedTopic);
8 ✔
1806
  }
8 ✔
1807

88 ✔
1808
  /**
88 ✔
1809
   * Return the number of subscribers on a given topic.
88 ✔
1810
   * @param {string} topic - The name of the topic.
88 ✔
1811
   * @returns {number} - Number of subscribers on the given topic.
88 ✔
1812
   */
88 ✔
1813
  countSubscribers(topic) {
88 ✔
1814
    let expandedTopic = rclnodejs.expandTopicName(
6 ✔
1815
      topic,
6 ✔
1816
      this.name(),
6 ✔
1817
      this.namespace()
6 ✔
1818
    );
6 ✔
1819
    rclnodejs.validateTopicName(expandedTopic);
6 ✔
1820

6 ✔
1821
    return rclnodejs.countSubscribers(this.handle, expandedTopic);
6 ✔
1822
  }
6 ✔
1823

88 ✔
1824
  /**
88 ✔
1825
   * Get the number of clients on a given service name.
88 ✔
1826
   * @param {string} serviceName - the service name
88 ✔
1827
   * @returns {Number}
88 ✔
1828
   */
88 ✔
1829
  countClients(serviceName) {
88 ✔
1830
    if (DistroUtils.getDistroId() <= DistroUtils.getDistroId('humble')) {
2 !
UNCOV
1831
      console.warn('countClients is not supported by this version of ROS 2');
×
1832
      return null;
×
1833
    }
×
1834
    return rclnodejs.countClients(this.handle, serviceName);
2 ✔
1835
  }
2 ✔
1836

88 ✔
1837
  /**
88 ✔
1838
   * Get the number of services on a given service name.
88 ✔
1839
   * @param {string} serviceName - the service name
88 ✔
1840
   * @returns {Number}
88 ✔
1841
   */
88 ✔
1842
  countServices(serviceName) {
88 ✔
1843
    if (DistroUtils.getDistroId() <= DistroUtils.getDistroId('humble')) {
1 !
UNCOV
1844
      console.warn('countServices is not supported by this version of ROS 2');
×
1845
      return null;
×
1846
    }
×
1847
    return rclnodejs.countServices(this.handle, serviceName);
1 ✔
1848
  }
1 ✔
1849

88 ✔
1850
  /**
88 ✔
1851
   * Get the list of parameter-overrides found on the commandline and
88 ✔
1852
   * in the NodeOptions.parameter_overrides property.
88 ✔
1853
   *
88 ✔
1854
   * @return {Array<Parameter>} - An array of Parameters.
88 ✔
1855
   */
88 ✔
1856
  getParameterOverrides() {
88 ✔
1857
    return Array.from(this._parameterOverrides.values());
8 ✔
1858
  }
8 ✔
1859

88 ✔
1860
  /**
88 ✔
1861
   * Declare a parameter.
88 ✔
1862
   *
88 ✔
1863
   * Internally, register a parameter and it's descriptor.
88 ✔
1864
   * If a parameter-override exists, it's value will replace that of the parameter
88 ✔
1865
   * unless ignoreOverride is true.
88 ✔
1866
   * If the descriptor is undefined, then a ParameterDescriptor will be inferred
88 ✔
1867
   * from the parameter's state.
88 ✔
1868
   *
88 ✔
1869
   * If a parameter by the same name has already been declared then an Error is thrown.
88 ✔
1870
   * A parameter must be undeclared before attempting to redeclare it.
88 ✔
1871
   *
88 ✔
1872
   * @param {Parameter} parameter - Parameter to declare.
88 ✔
1873
   * @param {ParameterDescriptor} [descriptor] - Optional descriptor for parameter.
88 ✔
1874
   * @param {boolean} [ignoreOverride] - When true disregard any parameter-override that may be present.
88 ✔
1875
   * @return {Parameter} - The newly declared parameter.
88 ✔
1876
   */
88 ✔
1877
  declareParameter(parameter, descriptor, ignoreOverride = false) {
88 ✔
1878
    const parameters = this.declareParameters(
2,848 ✔
1879
      [parameter],
2,848 ✔
1880
      descriptor ? [descriptor] : [],
2,848 ✔
1881
      ignoreOverride
2,848 ✔
1882
    );
2,848 ✔
1883
    return parameters.length == 1 ? parameters[0] : null;
2,848 !
1884
  }
2,848 ✔
1885

88 ✔
1886
  /**
88 ✔
1887
   * Declare a list of parameters.
88 ✔
1888
   *
88 ✔
1889
   * Internally register parameters with their corresponding descriptor one by one
88 ✔
1890
   * in the order they are provided. This is an atomic operation. If an error
88 ✔
1891
   * occurs the process halts and no further parameters are declared.
88 ✔
1892
   * Parameters that have already been processed are undeclared.
88 ✔
1893
   *
88 ✔
1894
   * While descriptors is an optional parameter, when provided there must be
88 ✔
1895
   * a descriptor for each parameter; otherwise an Error is thrown.
88 ✔
1896
   * If descriptors is not provided then a descriptor will be inferred
88 ✔
1897
   * from each parameter's state.
88 ✔
1898
   *
88 ✔
1899
   * When a parameter-override is available, the parameter's value
88 ✔
1900
   * will be replaced with that of the parameter-override unless ignoreOverrides
88 ✔
1901
   * is true.
88 ✔
1902
   *
88 ✔
1903
   * If a parameter by the same name has already been declared then an Error is thrown.
88 ✔
1904
   * A parameter must be undeclared before attempting to redeclare it.
88 ✔
1905
   *
88 ✔
1906
   * Prior to declaring the parameters each SetParameterEventCallback registered
88 ✔
1907
   * using setOnParameterEventCallback() is called in succession with the parameters
88 ✔
1908
   * list. Any SetParameterEventCallback that retuns does not return a successful
88 ✔
1909
   * result will cause the entire operation to terminate with no changes to the
88 ✔
1910
   * parameters. When all SetParameterEventCallbacks return successful then the
88 ✔
1911
   * list of parameters is updated.
88 ✔
1912
   *
88 ✔
1913
   * @param {Parameter[]} parameters - The parameters to declare.
88 ✔
1914
   * @param {ParameterDescriptor[]} [descriptors] - Optional descriptors,
88 ✔
1915
   *    a 1-1 correspondence with parameters.
88 ✔
1916
   * @param {boolean} ignoreOverrides - When true, parameter-overrides are
88 ✔
1917
   *    not considered, i.e.,ignored.
88 ✔
1918
   * @return {Parameter[]} - The declared parameters.
88 ✔
1919
   */
88 ✔
1920
  declareParameters(parameters, descriptors = [], ignoreOverrides = false) {
88 ✔
1921
    if (!Array.isArray(parameters)) {
2,848 !
UNCOV
1922
      throw new TypeValidationError('parameters', parameters, 'Array', {
×
1923
        nodeName: this.name(),
×
1924
      });
×
1925
    }
×
1926
    if (!Array.isArray(descriptors)) {
2,848 !
UNCOV
1927
      throw new TypeValidationError('descriptors', descriptors, 'Array', {
×
1928
        nodeName: this.name(),
×
1929
      });
×
1930
    }
×
1931
    if (descriptors.length > 0 && parameters.length !== descriptors.length) {
2,848 !
UNCOV
1932
      throw new ValidationError(
×
1933
        'Each parameter must have a corresponding ParameterDescriptor',
×
1934
        {
×
1935
          code: 'PARAMETER_DESCRIPTOR_MISMATCH',
×
1936
          argumentName: 'descriptors',
×
1937
          providedValue: descriptors.length,
×
1938
          expectedType: `Array with length ${parameters.length}`,
×
1939
          nodeName: this.name(),
×
1940
        }
×
1941
      );
×
1942
    }
×
1943

2,848 ✔
1944
    const declaredDescriptors = [];
2,848 ✔
1945
    const declaredParameters = [];
2,848 ✔
1946
    const declaredParameterCollisions = [];
2,848 ✔
1947
    for (let i = 0; i < parameters.length; i++) {
2,848 ✔
1948
      let parameter =
2,848 ✔
1949
        !ignoreOverrides && this._parameterOverrides.has(parameters[i].name)
2,848 ✔
1950
          ? this._parameterOverrides.get(parameters[i].name)
2,848 ✔
1951
          : parameters[i];
2,848 ✔
1952

2,848 ✔
1953
      // stop processing parameters that have already been declared
2,848 ✔
1954
      if (this._parameters.has(parameter.name)) {
2,848 !
UNCOV
1955
        declaredParameterCollisions.push(parameter);
×
1956
        continue;
×
1957
      }
×
1958

2,848 ✔
1959
      // create descriptor for parameter if not provided
2,848 ✔
1960
      let descriptor =
2,848 ✔
1961
        descriptors.length > 0
2,848 ✔
1962
          ? descriptors[i]
2,848 ✔
1963
          : ParameterDescriptor.fromParameter(parameter);
2,848 ✔
1964

2,848 ✔
1965
      descriptor.validate();
2,848 ✔
1966

2,848 ✔
1967
      declaredDescriptors.push(descriptor);
2,848 ✔
1968
      declaredParameters.push(parameter);
2,848 ✔
1969
    }
2,848 ✔
1970

2,848 ✔
1971
    if (declaredParameterCollisions.length > 0) {
2,848 !
UNCOV
1972
      const errorMsg =
×
1973
        declaredParameterCollisions.length == 1
×
1974
          ? `Parameter(${declaredParameterCollisions[0]}) already declared.`
×
1975
          : `Multiple parameters already declared, e.g., Parameter(${declaredParameterCollisions[0]}).`;
×
1976
      throw new Error(errorMsg);
×
1977
    }
×
1978

2,848 ✔
1979
    // register descriptor
2,848 ✔
1980
    for (const descriptor of declaredDescriptors) {
2,848 ✔
1981
      this._parameterDescriptors.set(descriptor.name, descriptor);
2,848 ✔
1982
    }
2,848 ✔
1983

2,848 ✔
1984
    const result = this._setParametersAtomically(declaredParameters, true);
2,848 ✔
1985
    if (!result.successful) {
2,848 !
UNCOV
1986
      // unregister descriptors
×
1987
      for (const descriptor of declaredDescriptors) {
×
1988
        this._parameterDescriptors.delete(descriptor.name);
×
1989
      }
×
1990

×
1991
      throw new Error(result.reason);
×
1992
    }
×
1993

2,848 ✔
1994
    return this.getParameters(declaredParameters.map((param) => param.name));
2,848 ✔
1995
  }
2,848 ✔
1996

88 ✔
1997
  /**
88 ✔
1998
   * Undeclare a parameter.
88 ✔
1999
   *
88 ✔
2000
   * Readonly parameters can not be undeclared or updated.
88 ✔
2001
   * @param {string} name - Name of parameter to undeclare.
88 ✔
2002
   * @return {undefined} -
88 ✔
2003
   */
88 ✔
2004
  undeclareParameter(name) {
88 ✔
2005
    if (!this.hasParameter(name)) return;
1 !
2006

1 ✔
2007
    const descriptor = this.getParameterDescriptor(name);
1 ✔
2008
    if (descriptor.readOnly) {
1 !
UNCOV
2009
      throw new Error(
×
2010
        `${name} parameter is read-only and can not be undeclared`
×
2011
      );
×
2012
    }
×
2013

1 ✔
2014
    this._parameters.delete(name);
1 ✔
2015
    this._parameterDescriptors.delete(name);
1 ✔
2016
  }
1 ✔
2017

88 ✔
2018
  /**
88 ✔
2019
   * Determine if a parameter has been declared.
88 ✔
2020
   * @param {string} name - name of parameter
88 ✔
2021
   * @returns {boolean} - Return true if parameter is declared; false otherwise.
88 ✔
2022
   */
88 ✔
2023
  hasParameter(name) {
88 ✔
2024
    return this._parameters.has(name);
7,301 ✔
2025
  }
7,301 ✔
2026

88 ✔
2027
  /**
88 ✔
2028
   * Get a declared parameter by name.
88 ✔
2029
   *
88 ✔
2030
   * If unable to locate a declared parameter then a
88 ✔
2031
   * parameter with type == PARAMETER_NOT_SET is returned.
88 ✔
2032
   *
88 ✔
2033
   * @param {string} name - The name of the parameter.
88 ✔
2034
   * @return {Parameter} - The parameter.
88 ✔
2035
   */
88 ✔
2036
  getParameter(name) {
88 ✔
2037
    return this.getParameters([name])[0];
2,213 ✔
2038
  }
2,213 ✔
2039

88 ✔
2040
  /**
88 ✔
2041
   * Get a list of parameters.
88 ✔
2042
   *
88 ✔
2043
   * Find and return the declared parameters.
88 ✔
2044
   * If no names are provided return all declared parameters.
88 ✔
2045
   *
88 ✔
2046
   * If unable to locate a declared parameter then a
88 ✔
2047
   * parameter with type == PARAMETER_NOT_SET is returned in
88 ✔
2048
   * it's place.
88 ✔
2049
   *
88 ✔
2050
   * @param {string[]} [names] - The names of the declared parameters
88 ✔
2051
   *    to find or null indicating to return all declared parameters.
88 ✔
2052
   * @return {Parameter[]} - The parameters.
88 ✔
2053
   */
88 ✔
2054
  getParameters(names = []) {
88 ✔
2055
    let params = [];
5,099 ✔
2056

5,099 ✔
2057
    if (names.length == 0) {
5,099 ✔
2058
      // get all parameters
12 ✔
2059
      params = [...this._parameters.values()];
12 ✔
2060
      return params;
12 ✔
2061
    }
12 ✔
2062

5,087 ✔
2063
    for (const name of names) {
5,099 ✔
2064
      const param = this.hasParameter(name)
5,094 ✔
2065
        ? this._parameters.get(name)
5,094 ✔
2066
        : new Parameter(name, ParameterType.PARAMETER_NOT_SET);
5,094 ✔
2067

5,094 ✔
2068
      params.push(param);
5,094 ✔
2069
    }
5,094 ✔
2070

5,087 ✔
2071
    return params;
5,087 ✔
2072
  }
5,099 ✔
2073

88 ✔
2074
  /**
88 ✔
2075
   * Get the types of given parameters.
88 ✔
2076
   *
88 ✔
2077
   * Return the types of given parameters.
88 ✔
2078
   *
88 ✔
2079
   * @param {string[]} [names] - The names of the declared parameters.
88 ✔
2080
   * @return {Uint8Array} - The types.
88 ✔
2081
   */
88 ✔
2082
  getParameterTypes(names = []) {
88 ✔
2083
    let types = [];
3 ✔
2084

3 ✔
2085
    for (const name of names) {
3 ✔
2086
      const descriptor = this._parameterDescriptors.get(name);
7 ✔
2087
      if (descriptor) {
7 ✔
2088
        types.push(descriptor.type);
7 ✔
2089
      }
7 ✔
2090
    }
7 ✔
2091
    return types;
3 ✔
2092
  }
3 ✔
2093

88 ✔
2094
  /**
88 ✔
2095
   * Get the names of all declared parameters.
88 ✔
2096
   *
88 ✔
2097
   * @return {Array<string>} - The declared parameter names or empty array if
88 ✔
2098
   *    no parameters have been declared.
88 ✔
2099
   */
88 ✔
2100
  getParameterNames() {
88 ✔
2101
    return this.getParameters().map((param) => param.name);
7 ✔
2102
  }
7 ✔
2103

88 ✔
2104
  /**
88 ✔
2105
   * Determine if a parameter descriptor exists.
88 ✔
2106
   *
88 ✔
2107
   * @param {string} name - The name of a descriptor to for.
88 ✔
2108
   * @return {boolean} - true if a descriptor has been declared; otherwise false.
88 ✔
2109
   */
88 ✔
2110
  hasParameterDescriptor(name) {
88 ✔
2111
    return !!this.getParameterDescriptor(name);
2,887 ✔
2112
  }
2,887 ✔
2113

88 ✔
2114
  /**
88 ✔
2115
   * Get a declared parameter descriptor by name.
88 ✔
2116
   *
88 ✔
2117
   * If unable to locate a declared parameter descriptor then a
88 ✔
2118
   * descriptor with type == PARAMETER_NOT_SET is returned.
88 ✔
2119
   *
88 ✔
2120
   * @param {string} name - The name of the parameter descriptor to find.
88 ✔
2121
   * @return {ParameterDescriptor} - The parameter descriptor.
88 ✔
2122
   */
88 ✔
2123
  getParameterDescriptor(name) {
88 ✔
2124
    return this.getParameterDescriptors([name])[0];
5,775 ✔
2125
  }
5,775 ✔
2126

88 ✔
2127
  /**
88 ✔
2128
   * Find a list of declared ParameterDescriptors.
88 ✔
2129
   *
88 ✔
2130
   * If no names are provided return all declared descriptors.
88 ✔
2131
   *
88 ✔
2132
   * If unable to locate a declared descriptor then a
88 ✔
2133
   * descriptor with type == PARAMETER_NOT_SET is returned in
88 ✔
2134
   * it's place.
88 ✔
2135
   *
88 ✔
2136
   * @param {string[]} [names] - The names of the declared parameter
88 ✔
2137
   *    descriptors to find or null indicating to return all declared descriptors.
88 ✔
2138
   * @return {ParameterDescriptor[]} - The parameter descriptors.
88 ✔
2139
   */
88 ✔
2140
  getParameterDescriptors(names = []) {
88 ✔
2141
    let descriptors = [];
5,778 ✔
2142

5,778 ✔
2143
    if (names.length == 0) {
5,778 !
UNCOV
2144
      // get all parameters
×
2145
      descriptors = [...this._parameterDescriptors.values()];
×
2146
      return descriptors;
×
2147
    }
×
2148

5,778 ✔
2149
    for (const name of names) {
5,778 ✔
2150
      let descriptor = this._parameterDescriptors.get(name);
5,780 ✔
2151
      if (!descriptor) {
5,780 !
UNCOV
2152
        descriptor = new ParameterDescriptor(
×
2153
          name,
×
2154
          ParameterType.PARAMETER_NOT_SET
×
2155
        );
×
2156
      }
×
2157
      descriptors.push(descriptor);
5,780 ✔
2158
    }
5,780 ✔
2159

5,778 ✔
2160
    return descriptors;
5,778 ✔
2161
  }
5,778 ✔
2162

88 ✔
2163
  /**
88 ✔
2164
   * Replace a declared parameter.
88 ✔
2165
   *
88 ✔
2166
   * The parameter being replaced must be a declared parameter who's descriptor
88 ✔
2167
   * is not readOnly; otherwise an Error is thrown.
88 ✔
2168
   *
88 ✔
2169
   * @param {Parameter} parameter - The new parameter.
88 ✔
2170
   * @return {rclnodejs.rcl_interfaces.msg.SetParameterResult} - The result of the operation.
88 ✔
2171
   */
88 ✔
2172
  setParameter(parameter) {
88 ✔
2173
    const results = this.setParameters([parameter]);
26 ✔
2174
    return results[0];
26 ✔
2175
  }
26 ✔
2176

88 ✔
2177
  /**
88 ✔
2178
   * Replace a list of declared parameters.
88 ✔
2179
   *
88 ✔
2180
   * Declared parameters are replaced in the order they are provided and
88 ✔
2181
   * a ParameterEvent is published for each individual parameter change.
88 ✔
2182
   *
88 ✔
2183
   * Prior to setting the parameters each SetParameterEventCallback registered
88 ✔
2184
   * using setOnParameterEventCallback() is called in succession with the parameters
88 ✔
2185
   * list. Any SetParameterEventCallback that retuns does not return a successful
88 ✔
2186
   * result will cause the entire operation to terminate with no changes to the
88 ✔
2187
   * parameters. When all SetParameterEventCallbacks return successful then the
88 ✔
2188
   * list of parameters is updated.
88 ✔
2189
   *
88 ✔
2190
   * If an error occurs, the process is stopped and returned. Parameters
88 ✔
2191
   * set before an error remain unchanged.
88 ✔
2192
   *
88 ✔
2193
   * @param {Parameter[]} parameters - The parameters to set.
88 ✔
2194
   * @return {rclnodejs.rcl_interfaces.msg.SetParameterResult[]} - A list of SetParameterResult, one for each parameter that was set.
88 ✔
2195
   */
88 ✔
2196
  setParameters(parameters = []) {
88 ✔
2197
    return parameters.map((parameter) =>
37 ✔
2198
      this.setParametersAtomically([parameter])
40 ✔
2199
    );
37 ✔
2200
  }
37 ✔
2201

88 ✔
2202
  /**
88 ✔
2203
   * Repalce a list of declared parameters atomically.
88 ✔
2204
   *
88 ✔
2205
   * Declared parameters are replaced in the order they are provided.
88 ✔
2206
   * A single ParameterEvent is published collectively for all changed
88 ✔
2207
   * parameters.
88 ✔
2208
   *
88 ✔
2209
   * Prior to setting the parameters each SetParameterEventCallback registered
88 ✔
2210
   * using setOnParameterEventCallback() is called in succession with the parameters
88 ✔
2211
   * list. Any SetParameterEventCallback that retuns does not return a successful
88 ✔
2212
   * result will cause the entire operation to terminate with no changes to the
88 ✔
2213
   * parameters. When all SetParameterEventCallbacks return successful then the
88 ✔
2214
   * list of parameters is updated.d
88 ✔
2215
   *
88 ✔
2216
   * If an error occurs, the process stops immediately. All parameters updated to
88 ✔
2217
   * the point of the error are reverted to their previous state.
88 ✔
2218
   *
88 ✔
2219
   * @param {Parameter[]} parameters - The parameters to set.
88 ✔
2220
   * @return {rclnodejs.rcl_interfaces.msg.SetParameterResult} - describes the result of setting 1 or more parameters.
88 ✔
2221
   */
88 ✔
2222
  setParametersAtomically(parameters = []) {
88 ✔
2223
    return this._setParametersAtomically(parameters);
41 ✔
2224
  }
41 ✔
2225

88 ✔
2226
  /**
88 ✔
2227
   * Internal method for updating parameters atomically.
88 ✔
2228
   *
88 ✔
2229
   * Prior to setting the parameters each SetParameterEventCallback registered
88 ✔
2230
   * using setOnParameterEventCallback() is called in succession with the parameters
88 ✔
2231
   * list. Any SetParameterEventCallback that retuns does not return a successful
88 ✔
2232
   * result will cause the entire operation to terminate with no changes to the
88 ✔
2233
   * parameters. When all SetParameterEventCallbacks return successful then the
88 ✔
2234
   * list of parameters is updated.
88 ✔
2235
   *
88 ✔
2236
   * @param {Paramerter[]} parameters - The parameters to update.
88 ✔
2237
   * @param {boolean} declareParameterMode - When true parameters are being declared;
88 ✔
2238
   *    otherwise they are being changed.
88 ✔
2239
   * @return {SetParameterResult} - A single collective result.
88 ✔
2240
   */
88 ✔
2241
  _setParametersAtomically(parameters = [], declareParameterMode = false) {
88 ✔
2242
    // 1) PRE callbacks — pipeline: each callback receives the output of the previous
2,889 ✔
2243
    if (this._preSetParametersCallbacks.length > 0) {
2,889 ✔
2244
      for (const callback of this._preSetParametersCallbacks) {
6 ✔
2245
        const result = callback(parameters);
7 ✔
2246
        if (!Array.isArray(result)) {
7 !
UNCOV
2247
          return {
×
2248
            successful: false,
×
2249
            reason:
×
2250
              'pre-set parameters callback must return an array of Parameters',
×
2251
          };
×
2252
        }
×
2253
        parameters = result;
7 ✔
2254
        if (parameters.length === 0) {
7 ✔
2255
          return {
2 ✔
2256
            successful: false,
2 ✔
2257
            reason:
2 ✔
2258
              'parameter list is empty after pre-set callback; set rejected',
2 ✔
2259
          };
2 ✔
2260
        }
2 ✔
2261
      }
7 ✔
2262
    }
4 ✔
2263

2,887 ✔
2264
    // 2) Validate
2,887 ✔
2265
    let result = this._validateParameters(parameters, declareParameterMode);
2,887 ✔
2266
    if (!result.successful) {
2,889 !
UNCOV
2267
      return result;
×
2268
    }
×
2269

2,887 ✔
2270
    // give all SetParametersCallbacks a chance to veto this change
2,887 ✔
2271
    for (const callback of this._setParametersCallbacks) {
2,889 ✔
2272
      result = callback(parameters);
1,780 ✔
2273
      if (!result.successful) {
1,780 ✔
2274
        // a callback has vetoed a parameter change
3 ✔
2275
        return result;
3 ✔
2276
      }
3 ✔
2277
    }
1,780 ✔
2278

2,884 ✔
2279
    // collectively track updates to parameters for use
2,884 ✔
2280
    // when publishing a ParameterEvent
2,884 ✔
2281
    const newParameters = [];
2,884 ✔
2282
    const changedParameters = [];
2,884 ✔
2283
    const deletedParameters = [];
2,884 ✔
2284

2,884 ✔
2285
    for (const parameter of parameters) {
2,884 ✔
2286
      if (parameter.type == ParameterType.PARAMETER_NOT_SET) {
2,884 ✔
2287
        this.undeclareParameter(parameter.name);
1 ✔
2288
        deletedParameters.push(parameter);
1 ✔
2289
      } else {
2,884 ✔
2290
        this._parameters.set(parameter.name, parameter);
2,883 ✔
2291
        if (declareParameterMode) {
2,883 ✔
2292
          newParameters.push(parameter);
2,848 ✔
2293
        } else {
2,883 ✔
2294
          changedParameters.push(parameter);
35 ✔
2295
        }
35 ✔
2296
      }
2,883 ✔
2297
    }
2,884 ✔
2298

2,884 ✔
2299
    // create ParameterEvent
2,884 ✔
2300
    const parameterEvent = new (loader.loadInterface(
2,884 ✔
2301
      PARAMETER_EVENT_MSG_TYPE
2,884 ✔
2302
    ))();
2,884 ✔
2303

2,884 ✔
2304
    const { seconds, nanoseconds } = this._clock.now().secondsAndNanoseconds;
2,884 ✔
2305
    parameterEvent.stamp = {
2,884 ✔
2306
      sec: Number(seconds),
2,884 ✔
2307
      nanosec: Number(nanoseconds),
2,884 ✔
2308
    };
2,884 ✔
2309

2,884 ✔
2310
    parameterEvent.node =
2,884 ✔
2311
      this.namespace() === '/'
2,884 ✔
2312
        ? this.namespace() + this.name()
2,889 ✔
2313
        : this.namespace() + '/' + this.name();
2,889 ✔
2314

2,889 ✔
2315
    if (newParameters.length > 0) {
2,889 ✔
2316
      parameterEvent['new_parameters'] = newParameters.map((parameter) =>
2,848 ✔
2317
        parameter.toParameterMessage()
2,848 ✔
2318
      );
2,848 ✔
2319
    }
2,848 ✔
2320
    if (changedParameters.length > 0) {
2,889 ✔
2321
      parameterEvent['changed_parameters'] = changedParameters.map(
35 ✔
2322
        (parameter) => parameter.toParameterMessage()
35 ✔
2323
      );
35 ✔
2324
    }
35 ✔
2325
    if (deletedParameters.length > 0) {
2,889 ✔
2326
      parameterEvent['deleted_parameters'] = deletedParameters.map(
1 ✔
2327
        (parameter) => parameter.toParameterMessage()
1 ✔
2328
      );
1 ✔
2329
    }
1 ✔
2330

2,884 ✔
2331
    // Publish ParameterEvent.
2,884 ✔
2332
    this._parameterEventPublisher.publish(parameterEvent);
2,884 ✔
2333

2,884 ✔
2334
    // POST callbacks — for side effects after successful set
2,884 ✔
2335
    for (const callback of this._postSetParametersCallbacks) {
2,889 ✔
2336
      callback(parameters);
4 ✔
2337
    }
4 ✔
2338

2,884 ✔
2339
    return {
2,884 ✔
2340
      successful: true,
2,884 ✔
2341
      reason: '',
2,884 ✔
2342
    };
2,884 ✔
2343
  }
2,889 ✔
2344

88 ✔
2345
  /**
88 ✔
2346
   * This callback is called when declaring a parameter or setting a parameter.
88 ✔
2347
   * The callback is provided a list of parameters and returns a SetParameterResult
88 ✔
2348
   * to indicate approval or veto of the operation.
88 ✔
2349
   *
88 ✔
2350
   * @callback SetParametersCallback
88 ✔
2351
   * @param {Parameter[]} parameters - The message published
88 ✔
2352
   * @returns {rcl_interfaces.msg.SetParameterResult} -
88 ✔
2353
   *
88 ✔
2354
   * @see [Node.addOnSetParametersCallback]{@link Node#addOnSetParametersCallback}
88 ✔
2355
   * @see [Node.removeOnSetParametersCallback]{@link Node#removeOnSetParametersCallback}
88 ✔
2356
   */
88 ✔
2357

88 ✔
2358
  /**
88 ✔
2359
   * Add a callback to the front of the list of callbacks invoked for parameter declaration
88 ✔
2360
   * and setting. No checks are made for duplicate callbacks.
88 ✔
2361
   *
88 ✔
2362
   * @param {SetParametersCallback} callback - The callback to add.
88 ✔
2363
   * @returns {undefined}
88 ✔
2364
   */
88 ✔
2365
  addOnSetParametersCallback(callback) {
88 ✔
2366
    this._setParametersCallbacks.unshift(callback);
1,136 ✔
2367
  }
1,136 ✔
2368

88 ✔
2369
  /**
88 ✔
2370
   * Remove a callback from the list of SetParametersCallbacks.
88 ✔
2371
   * If the callback is not found the process is a nop.
88 ✔
2372
   *
88 ✔
2373
   * @param {SetParametersCallback} callback - The callback to be removed
88 ✔
2374
   * @returns {undefined}
88 ✔
2375
   */
88 ✔
2376
  removeOnSetParametersCallback(callback) {
88 ✔
2377
    const idx = this._setParametersCallbacks.indexOf(callback);
2 ✔
2378
    if (idx > -1) {
2 ✔
2379
      this._setParametersCallbacks.splice(idx, 1);
2 ✔
2380
    }
2 ✔
2381
  }
2 ✔
2382

88 ✔
2383
  /**
88 ✔
2384
   * A callback invoked before parameter validation and setting.
88 ✔
2385
   * It receives the parameter list and must return a (possibly modified) parameter list.
88 ✔
2386
   *
88 ✔
2387
   * @callback PreSetParametersCallback
88 ✔
2388
   * @param {Parameter[]} parameters - The parameters about to be set.
88 ✔
2389
   * @returns {Parameter[]} - The modified parameter list to proceed with.
88 ✔
2390
   *
88 ✔
2391
   * @see [Node.addPreSetParametersCallback]{@link Node#addPreSetParametersCallback}
88 ✔
2392
   * @see [Node.removePreSetParametersCallback]{@link Node#removePreSetParametersCallback}
88 ✔
2393
   */
88 ✔
2394

88 ✔
2395
  /**
88 ✔
2396
   * Add a callback invoked before parameter validation.
88 ✔
2397
   * The callback receives the parameter list and must return a (possibly modified)
88 ✔
2398
   * parameter list. This can be used to coerce, add, or remove parameters before
88 ✔
2399
   * they are validated and applied. If any pre-set callback returns an empty list,
88 ✔
2400
   * the set is rejected.
88 ✔
2401
   *
88 ✔
2402
   * @param {PreSetParametersCallback} callback - The callback to add.
88 ✔
2403
   * @returns {undefined}
88 ✔
2404
   */
88 ✔
2405
  addPreSetParametersCallback(callback) {
88 ✔
2406
    this._preSetParametersCallbacks.unshift(callback);
8 ✔
2407
  }
8 ✔
2408

88 ✔
2409
  /**
88 ✔
2410
   * Remove a pre-set parameters callback.
88 ✔
2411
   *
88 ✔
2412
   * @param {PreSetParametersCallback} callback - The callback to remove.
88 ✔
2413
   * @returns {undefined}
88 ✔
2414
   */
88 ✔
2415
  removePreSetParametersCallback(callback) {
88 ✔
2416
    const idx = this._preSetParametersCallbacks.indexOf(callback);
1 ✔
2417
    if (idx > -1) {
1 ✔
2418
      this._preSetParametersCallbacks.splice(idx, 1);
1 ✔
2419
    }
1 ✔
2420
  }
1 ✔
2421

88 ✔
2422
  /**
88 ✔
2423
   * A callback invoked after parameters have been successfully set.
88 ✔
2424
   * It receives the final parameter list. For side effects only (return value is ignored).
88 ✔
2425
   *
88 ✔
2426
   * @callback PostSetParametersCallback
88 ✔
2427
   * @param {Parameter[]} parameters - The parameters that were set.
88 ✔
2428
   * @returns {undefined}
88 ✔
2429
   *
88 ✔
2430
   * @see [Node.addPostSetParametersCallback]{@link Node#addPostSetParametersCallback}
88 ✔
2431
   * @see [Node.removePostSetParametersCallback]{@link Node#removePostSetParametersCallback}
88 ✔
2432
   */
88 ✔
2433

88 ✔
2434
  /**
88 ✔
2435
   * Add a callback invoked after parameters are successfully set.
88 ✔
2436
   * The callback receives the final parameter list. Useful for triggering
88 ✔
2437
   * side effects (e.g., reconfiguring a component when a parameter changes).
88 ✔
2438
   *
88 ✔
2439
   * @param {PostSetParametersCallback} callback - The callback to add.
88 ✔
2440
   * @returns {undefined}
88 ✔
2441
   */
88 ✔
2442
  addPostSetParametersCallback(callback) {
88 ✔
2443
    this._postSetParametersCallbacks.unshift(callback);
8 ✔
2444
  }
8 ✔
2445

88 ✔
2446
  /**
88 ✔
2447
   * Remove a post-set parameters callback.
88 ✔
2448
   *
88 ✔
2449
   * @param {PostSetParametersCallback} callback - The callback to remove.
88 ✔
2450
   * @returns {undefined}
88 ✔
2451
   */
88 ✔
2452
  removePostSetParametersCallback(callback) {
88 ✔
2453
    const idx = this._postSetParametersCallbacks.indexOf(callback);
1 ✔
2454
    if (idx > -1) {
1 ✔
2455
      this._postSetParametersCallbacks.splice(idx, 1);
1 ✔
2456
    }
1 ✔
2457
  }
1 ✔
2458

88 ✔
2459
  /**
88 ✔
2460
   * Get the fully qualified name of the node.
88 ✔
2461
   *
88 ✔
2462
   * @returns {string} - String containing the fully qualified name of the node.
88 ✔
2463
   */
88 ✔
2464
  getFullyQualifiedName() {
88 ✔
2465
    return rclnodejs.getFullyQualifiedName(this.handle);
1 ✔
2466
  }
1 ✔
2467

88 ✔
2468
  /**
88 ✔
2469
   * Get the RMW implementation identifier
88 ✔
2470
   * @returns {string} - The RMW implementation identifier.
88 ✔
2471
   */
88 ✔
2472
  getRMWImplementationIdentifier() {
88 ✔
2473
    return rclnodejs.getRMWImplementationIdentifier();
1 ✔
2474
  }
1 ✔
2475

88 ✔
2476
  /**
88 ✔
2477
   * Return a topic name expanded and remapped.
88 ✔
2478
   * @param {string} topicName - Topic name to be expanded and remapped.
88 ✔
2479
   * @param {boolean} [onlyExpand=false] - If `true`, remapping rules won't be applied.
88 ✔
2480
   * @returns {string} - A fully qualified topic name.
88 ✔
2481
   */
88 ✔
2482
  resolveTopicName(topicName, onlyExpand = false) {
88 ✔
2483
    if (typeof topicName !== 'string') {
15 !
UNCOV
2484
      throw new TypeValidationError('topicName', topicName, 'string', {
×
2485
        nodeName: this.name(),
×
2486
      });
×
2487
    }
×
2488
    return rclnodejs.resolveName(
15 ✔
2489
      this.handle,
15 ✔
2490
      topicName,
15 ✔
2491
      onlyExpand,
15 ✔
2492
      /*isService=*/ false
15 ✔
2493
    );
15 ✔
2494
  }
15 ✔
2495

88 ✔
2496
  /**
88 ✔
2497
   * Return a service name expanded and remapped.
88 ✔
2498
   * @param {string} service - Service name to be expanded and remapped.
88 ✔
2499
   * @param {boolean} [onlyExpand=false] - If `true`, remapping rules won't be applied.
88 ✔
2500
   * @returns {string} - A fully qualified service name.
88 ✔
2501
   */
88 ✔
2502
  resolveServiceName(service, onlyExpand = false) {
88 ✔
2503
    if (typeof service !== 'string') {
7 !
UNCOV
2504
      throw new TypeValidationError('service', service, 'string', {
×
2505
        nodeName: this.name(),
×
2506
      });
×
2507
    }
×
2508
    return rclnodejs.resolveName(
7 ✔
2509
      this.handle,
7 ✔
2510
      service,
7 ✔
2511
      onlyExpand,
7 ✔
2512
      /*isService=*/ true
7 ✔
2513
    );
7 ✔
2514
  }
7 ✔
2515

88 ✔
2516
  // returns on 1st error or result {successful, reason}
88 ✔
2517
  _validateParameters(parameters = [], declareParameterMode = false) {
88 ✔
2518
    for (const parameter of parameters) {
2,887 ✔
2519
      // detect invalid parameter
2,887 ✔
2520
      try {
2,887 ✔
2521
        parameter.validate();
2,887 ✔
2522
      } catch {
2,887 !
UNCOV
2523
        return {
×
2524
          successful: false,
×
2525
          reason: `Invalid ${parameter.name}`,
×
2526
        };
×
2527
      }
×
2528

2,887 ✔
2529
      // detect undeclared parameter
2,887 ✔
2530
      if (!this.hasParameterDescriptor(parameter.name)) {
2,887 !
UNCOV
2531
        return {
×
2532
          successful: false,
×
2533
          reason: `Parameter ${parameter.name} has not been declared`,
×
2534
        };
×
2535
      }
×
2536

2,887 ✔
2537
      // detect readonly parameter that can not be updated
2,887 ✔
2538
      const descriptor = this.getParameterDescriptor(parameter.name);
2,887 ✔
2539
      if (!declareParameterMode && descriptor.readOnly) {
2,887 !
UNCOV
2540
        return {
×
2541
          successful: false,
×
2542
          reason: `Parameter ${parameter.name} is readonly`,
×
2543
        };
×
2544
      }
×
2545

2,887 ✔
2546
      // validate parameter against descriptor if not an undeclare action
2,887 ✔
2547
      if (parameter.type != ParameterType.PARAMETER_NOT_SET) {
2,887 ✔
2548
        try {
2,886 ✔
2549
          descriptor.validateParameter(parameter);
2,886 ✔
2550
        } catch {
2,886 !
UNCOV
2551
          return {
×
2552
            successful: false,
×
2553
            reason: `Parameter ${parameter.name} does not  readonly`,
×
2554
          };
×
2555
        }
×
2556
      }
2,886 ✔
2557
    }
2,887 ✔
2558

2,887 ✔
2559
    return {
2,887 ✔
2560
      successful: true,
2,887 ✔
2561
      reason: null,
2,887 ✔
2562
    };
2,887 ✔
2563
  }
2,887 ✔
2564

88 ✔
2565
  // Get a Map(nodeName->Parameter[]) of CLI parameter args that
88 ✔
2566
  // apply to 'this' node, .e.g., -p mynode:foo:=bar -p hello:=world
88 ✔
2567
  _getNativeParameterOverrides() {
88 ✔
2568
    const overrides = new Map();
1,115 ✔
2569

1,115 ✔
2570
    // Get native parameters from rcl context->global_arguments.
1,115 ✔
2571
    // rclnodejs returns an array of objects, 1 for each node e.g., -p my_node:foo:=bar,
1,115 ✔
2572
    // and a node named '/**' for global parameter rules,
1,115 ✔
2573
    // i.e., does not include a node identifier, e.g., -p color:=red
1,115 ✔
2574
    // {
1,115 ✔
2575
    //   name: string // node name
1,115 ✔
2576
    //   parameters[] = {
1,115 ✔
2577
    //     name: string
1,115 ✔
2578
    //     type: uint
1,115 ✔
2579
    //     value: object
1,115 ✔
2580
    // }
1,115 ✔
2581
    const cliParamOverrideData = rclnodejs.getParameterOverrides(
1,115 ✔
2582
      this.context.handle
1,115 ✔
2583
    );
1,115 ✔
2584

1,115 ✔
2585
    // convert native CLI parameterOverrides to Map<nodeName,Array<ParameterOverride>>
1,115 ✔
2586
    const cliParamOverrides = new Map();
1,115 ✔
2587
    if (cliParamOverrideData) {
1,115 ✔
2588
      for (let nodeParamData of cliParamOverrideData) {
8 ✔
2589
        const nodeName = nodeParamData.name;
12 ✔
2590
        const nodeParamOverrides = [];
12 ✔
2591
        for (let paramData of nodeParamData.parameters) {
12 ✔
2592
          const paramOverride = new Parameter(
17 ✔
2593
            paramData.name,
17 ✔
2594
            paramData.type,
17 ✔
2595
            paramData.value
17 ✔
2596
          );
17 ✔
2597
          nodeParamOverrides.push(paramOverride);
17 ✔
2598
        }
17 ✔
2599
        cliParamOverrides.set(nodeName, nodeParamOverrides);
12 ✔
2600
      }
12 ✔
2601
    }
8 ✔
2602

1,115 ✔
2603
    // collect global CLI global parameters, name == /**
1,115 ✔
2604
    let paramOverrides = cliParamOverrides.get('/**'); // array of ParameterOverrides
1,115 ✔
2605
    if (paramOverrides) {
1,115 ✔
2606
      for (const parameter of paramOverrides) {
5 ✔
2607
        overrides.set(parameter.name, parameter);
6 ✔
2608
      }
6 ✔
2609
    }
5 ✔
2610

1,115 ✔
2611
    // merge CLI node parameterOverrides with global parameterOverrides, replace existing
1,115 ✔
2612
    paramOverrides = cliParamOverrides.get(this.name()); // array of ParameterOverrides
1,115 ✔
2613
    if (paramOverrides) {
1,115 ✔
2614
      for (const parameter of paramOverrides) {
5 ✔
2615
        overrides.set(parameter.name, parameter);
7 ✔
2616
      }
7 ✔
2617
    }
5 ✔
2618

1,115 ✔
2619
    return overrides;
1,115 ✔
2620
  }
1,115 ✔
2621

88 ✔
2622
  /**
88 ✔
2623
   * Invokes the callback with a raw message of the given type. After the callback completes
88 ✔
2624
   * the message will be destroyed.
88 ✔
2625
   * @param {function} Type - Message type to create.
88 ✔
2626
   * @param {function} callback - Callback to invoke. First parameter will be the raw message,
88 ✔
2627
   * and the second is a function to retrieve the deserialized message.
88 ✔
2628
   * @returns {undefined}
88 ✔
2629
   */
88 ✔
2630
  _runWithMessageType(Type, callback) {
88 ✔
2631
    let message = new Type();
1,157 ✔
2632
    let rawMessage = message.toRawROS();
1,157 ✔
2633

1,157 ✔
2634
    try {
1,157 ✔
2635
      callback(rawMessage, () => {
1,157 ✔
2636
        let result = new Type();
1,083 ✔
2637
        result.deserialize(message.refObject);
1,083 ✔
2638

1,083 ✔
2639
        return result;
1,083 ✔
2640
      });
1,157 ✔
2641
    } finally {
1,157 ✔
2642
      Type.destroyRawROS(message);
1,157 ✔
2643
    }
1,157 ✔
2644
  }
1,157 ✔
2645

88 ✔
2646
  _addActionClient(actionClient) {
88 ✔
2647
    this._actionClients.push(actionClient);
97 ✔
2648
    this.syncHandles();
97 ✔
2649
  }
97 ✔
2650

88 ✔
2651
  _addActionServer(actionServer) {
88 ✔
2652
    this._actionServers.push(actionServer);
70 ✔
2653
    this.syncHandles();
70 ✔
2654
  }
70 ✔
2655

88 ✔
2656
  // Unlike topic/service queries, the queried action name is not remapped.
88 ✔
2657
  _getValidatedActionName(actionName) {
88 ✔
2658
    const fullyQualifiedActionName = rclnodejs.expandTopicName(
4 ✔
2659
      actionName,
4 ✔
2660
      this.name(),
4 ✔
2661
      this.namespace()
4 ✔
2662
    );
4 ✔
2663
    validateFullTopicName(fullyQualifiedActionName);
4 ✔
2664
    return fullyQualifiedActionName;
4 ✔
2665
  }
4 ✔
2666

88 ✔
2667
  _getValidatedTopic(topicName, noDemangle) {
88 ✔
2668
    if (noDemangle) {
8 !
UNCOV
2669
      return topicName;
×
2670
    }
×
2671
    const fqTopicName = rclnodejs.expandTopicName(
8 ✔
2672
      topicName,
8 ✔
2673
      this.name(),
8 ✔
2674
      this.namespace()
8 ✔
2675
    );
8 ✔
2676
    validateFullTopicName(fqTopicName);
8 ✔
2677
    return rclnodejs.remapTopicName(this.handle, fqTopicName);
8 ✔
2678
  }
8 ✔
2679

88 ✔
2680
  _getValidatedServiceName(serviceName, noDemangle) {
88 ✔
2681
    if (typeof serviceName !== 'string') {
4 !
UNCOV
2682
      throw new TypeValidationError('serviceName', serviceName, 'string', {
×
2683
        nodeName: this.name(),
×
2684
      });
×
2685
    }
×
2686

4 ✔
2687
    if (noDemangle) {
4 !
UNCOV
2688
      return serviceName;
×
2689
    }
×
2690

4 ✔
2691
    const resolvedServiceName = this.resolveServiceName(serviceName);
4 ✔
2692
    rclnodejs.validateTopicName(resolvedServiceName);
4 ✔
2693
    return resolvedServiceName;
4 ✔
2694
  }
4 ✔
2695
}
88 ✔
2696

88 ✔
2697
/**
88 ✔
2698
 * Create an Options instance initialized with default values.
88 ✔
2699
 * @returns {Options} - The new initialized instance.
88 ✔
2700
 * @static
88 ✔
2701
 * @example
88 ✔
2702
 * {
88 ✔
2703
 *   enableTypedArray: true,
88 ✔
2704
 *   isRaw: false,
88 ✔
2705
 *   qos: QoS.profileDefault,
88 ✔
2706
 *   contentFilter: undefined,
88 ✔
2707
 *   serializationMode: 'default',
88 ✔
2708
 * }
88 ✔
2709
 */
88 ✔
2710
Node.getDefaultOptions = function () {
88 ✔
2711
  return {
9,860 ✔
2712
    enableTypedArray: true,
9,860 ✔
2713
    isRaw: false,
9,860 ✔
2714
    qos: QoS.profileDefault,
9,860 ✔
2715
    contentFilter: undefined,
9,860 ✔
2716
    serializationMode: 'default',
9,860 ✔
2717
  };
9,860 ✔
2718
};
88 ✔
2719

88 ✔
2720
export default Node;
88 ✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc