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

RobotWebTools / rclnodejs / 17610628993

10 Sep 2025 10:16AM UTC coverage: 84.306% (-0.2%) from 84.555%
17610628993

push

github

minggangw
Generate missing messages at runtime (#1257)

This PR implements runtime message generation capabilities for rclnodejs, allowing custom ROS2 message types to be dynamically discovered, built, and loaded during execution rather than requiring pre-generation.

- Adds runtime message discovery and generation functionality to the interface loader
- Implements synchronous message generation using worker processes to avoid blocking the main thread
- Includes comprehensive test infrastructure with a custom message package for validation

Fix: #1257

787 of 1031 branches covered (76.33%)

Branch coverage included in aggregate %.

31 of 36 new or added lines in 1 file covered. (86.11%)

2 existing lines in 1 file now uncovered.

1942 of 2206 relevant lines covered (88.03%)

433.73 hits per line

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

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

15
'use strict';
16

17
const rclnodejs = require('bindings')('rclnodejs');
26✔
18
const DistroUtils = require('./distro.js');
26✔
19
const Entity = require('./entity.js');
26✔
20

21
/**
22
 * Enumeration for PublisherEventCallbacks event types.
23
 * @enum {number}
24
 */
25
const PublisherEventType = {
26✔
26
  /** @member {number} */
27
  PUBLISHER_OFFERED_DEADLINE_MISSED: 0,
28
  /** @member {number} */
29
  PUBLISHER_LIVELINESS_LOST: 1,
30
  /** @member {number} */
31
  PUBLISHER_OFFERED_INCOMPATIBLE_QOS: 2,
32
  /** @member {number} */
33
  PUBLISHER_INCOMPATIBLE_TYPE: 3,
34
  /** @member {number} */
35
  PUBLISHER_MATCHED: 4,
36
};
37

38
/**
39
 * Enumeration for SubscriptionEventCallbacks event types.
40
 * @enum {number}
41
 */
42
const SubscriptionEventType = {
26✔
43
  /** @member {number} */
44
  SUBSCRIPTION_REQUESTED_DEADLINE_MISSED: 0,
45
  /** @member {number} */
46
  SUBSCRIPTION_LIVELINESS_CHANGED: 1,
47
  /** @member {number} */
48
  SUBSCRIPTION_REQUESTED_INCOMPATIBLE_QOS: 2,
49
  /** @member {number} */
50
  SUBSCRIPTION_MESSAGE_LOST: 3,
51
  /** @member {number} */
52
  SUBSCRIPTION_INCOMPATIBLE_TYPE: 4,
53
  /** @member {number} */
54
  SUBSCRIPTION_MATCHED: 5,
55
};
56

57
class EventHandler extends Entity {
58
  constructor(handle, callback, eventType, eventTypeName) {
59
    super(handle, null, null);
11✔
60
    this._callback = callback;
11✔
61
    this._eventType = eventType;
11✔
62
    this._eventTypeName = eventTypeName;
11✔
63
  }
64

65
  takeData() {
66
    const data = rclnodejs.takeEvent(this._handle, {
10✔
67
      [this._eventTypeName]: this._eventType,
68
    });
69
    if (this._callback) {
10!
70
      this._callback(data);
10✔
71
    }
72
  }
73
}
74

75
/**
76
 * @class - Class representing a ROS 2 PublisherEventCallbacks
77
 * @hideconstructor
78
 */
79
class PublisherEventCallbacks {
80
  constructor() {
81
    if (DistroUtils.getDistroId() < DistroUtils.getDistroId('jazzy')) {
2!
UNCOV
82
      throw new Error(
×
83
        'PublisherEventCallbacks is only available in ROS 2 Jazzy and later.'
84
      );
85
    }
86
    this._deadline = null;
2✔
87
    this._incompatible_qos = null;
2✔
88
    this._liveliness = null;
2✔
89
    this._incompatible_type = null;
2✔
90
    this._matched = null;
2✔
91
    this._eventHandlers = [];
2✔
92
  }
93

94
  /**
95
   * Set deadline missed callback.
96
   * @param {function} callback - The callback function to be called.
97
   */
98
  set deadline(callback) {
99
    this._deadline = callback;
1✔
100
  }
101

102
  /**
103
   * Get deadline missed callback.
104
   * @return {function} - The callback function.
105
   */
106
  get deadline() {
107
    return this._deadline;
×
108
  }
109

110
  /**
111
   * Set incompatible QoS callback.
112
   * @param {function} callback - The callback function to be called.
113
   */
114
  set incompatibleQos(callback) {
115
    this._incompatible_qos = callback;
1✔
116
  }
117

118
  /**
119
   * Get incompatible QoS callback.
120
   * @return {function} - The callback function.
121
   */
122
  get incompatibleQos() {
123
    return this._incompatible_qos;
×
124
  }
125

126
  /**
127
   * Set liveliness lost callback.
128
   * @param {function} callback - The callback function to be called.
129
   */
130
  set liveliness(callback) {
131
    this._liveliness = callback;
1✔
132
  }
133

134
  /**
135
   * Get liveliness lost callback.
136
   * @return {function} - The callback function.
137
   */
138
  get liveliness() {
139
    return this._liveliness;
×
140
  }
141

142
  /**
143
   * Set incompatible type callback.
144
   * @param {function} callback - The callback function to be called.
145
   */
146
  set incompatibleType(callback) {
147
    this._incompatible_type = callback;
×
148
  }
149

150
  /**
151
   * Get incompatible type callback.
152
   * @return {function} - The callback function.
153
   */
154
  get incompatibleType() {
155
    return this._incompatible_type;
×
156
  }
157

158
  /**
159
   * Set matched callback.
160
   * @param {function} callback - The callback function to be called.
161
   */
162
  set matched(callback) {
163
    this._matched = callback;
1✔
164
  }
165

166
  /**
167
   * Get matched callback.
168
   * @return {function} - The callback function.
169
   */
170
  get matched() {
171
    return this._matched;
×
172
  }
173

174
  createEventHandlers(publisherHandle) {
175
    if (this._deadline) {
2✔
176
      const deadlineHandle = rclnodejs.createPublisherEventHandle(
1✔
177
        publisherHandle,
178
        PublisherEventType.PUBLISHER_OFFERED_DEADLINE_MISSED
179
      );
180
      this._eventHandlers.push(
1✔
181
        new EventHandler(
182
          deadlineHandle,
183
          this._deadline,
184
          PublisherEventType.PUBLISHER_OFFERED_DEADLINE_MISSED,
185
          'publisher_event_type'
186
        )
187
      );
188
    }
189

190
    if (this._incompatible_qos) {
2✔
191
      const incompatibleQosHandle = rclnodejs.createPublisherEventHandle(
1✔
192
        publisherHandle,
193
        PublisherEventType.PUBLISHER_OFFERED_INCOMPATIBLE_QOS
194
      );
195
      this._eventHandlers.push(
1✔
196
        new EventHandler(
197
          incompatibleQosHandle,
198
          this._incompatible_qos,
199
          PublisherEventType.PUBLISHER_OFFERED_INCOMPATIBLE_QOS,
200
          'publisher_event_type'
201
        )
202
      );
203
    }
204

205
    if (this._liveliness) {
2✔
206
      const livelinessHandle = rclnodejs.createPublisherEventHandle(
1✔
207
        publisherHandle,
208
        PublisherEventType.PUBLISHER_LIVELINESS_LOST
209
      );
210
      this._eventHandlers.push(
1✔
211
        new EventHandler(
212
          livelinessHandle,
213
          this._liveliness,
214
          PublisherEventType.PUBLISHER_LIVELINESS_LOST,
215
          'publisher_event_type'
216
        )
217
      );
218
    }
219

220
    if (this._incompatible_type) {
2!
221
      const incompatibleTypeHandle = rclnodejs.createPublisherEventHandle(
×
222
        publisherHandle,
223
        PublisherEventType.PUBLISHER_INCOMPATIBLE_TYPE
224
      );
225
      this._eventHandlers.push(
×
226
        new EventHandler(
227
          incompatibleTypeHandle,
228
          this._incompatible_type,
229
          PublisherEventType.PUBLISHER_INCOMPATIBLE_TYPE,
230
          'publisher_event_type'
231
        )
232
      );
233
    }
234

235
    if (this._matched) {
2✔
236
      const matchedHandle = rclnodejs.createPublisherEventHandle(
1✔
237
        publisherHandle,
238
        PublisherEventType.PUBLISHER_MATCHED
239
      );
240
      this._eventHandlers.push(
1✔
241
        new EventHandler(
242
          matchedHandle,
243
          this._matched,
244
          PublisherEventType.PUBLISHER_MATCHED,
245
          'publisher_event_type'
246
        )
247
      );
248
    }
249

250
    return this._eventHandlers;
2✔
251
  }
252

253
  get eventHandlers() {
254
    return this._eventHandlers;
×
255
  }
256
}
257

258
/**
259
 * @class - Class representing a ROS 2 SubscriptionEventCallbacks
260
 * @hideconstructor
261
 */
262
class SubscriptionEventCallbacks {
263
  constructor() {
264
    if (DistroUtils.getDistroId() < DistroUtils.getDistroId('jazzy')) {
4!
UNCOV
265
      throw new Error(
×
266
        'SubscriptionEventCallbacks is only available in ROS 2 Jazzy and later.'
267
      );
268
    }
269
    this._deadline = null;
4✔
270
    this._incompatible_qos = null;
4✔
271
    this._liveliness = null;
4✔
272
    this._message_lost = null;
4✔
273
    this._incompatible_type = null;
4✔
274
    this._matched = null;
4✔
275
    this._eventHandlers = [];
4✔
276
  }
277

278
  /**
279
   * Set the callback for deadline missed event.
280
   * @param {function} callback - The callback function to be called.
281
   */
282
  set deadline(callback) {
283
    this._deadline = callback;
3✔
284
  }
285

286
  /**
287
   * Get the callback for deadline missed event.
288
   * @return {function} - The callback function.
289
   */
290
  get deadline() {
291
    return this._deadline;
×
292
  }
293

294
  /**
295
   * Set the callback for incompatible QoS event.
296
   * @param {function} callback - The callback function to be called.
297
   */
298
  set incompatibleQos(callback) {
299
    this._incompatible_qos = callback;
1✔
300
  }
301

302
  /**
303
   * Get the callback for incompatible QoS event.
304
   * @return {function} - The callback function.
305
   */
306
  get incompatibleQos() {
307
    return this._incompatible_qos;
×
308
  }
309

310
  /**
311
   * Set the callback for liveliness changed event.
312
   * @param {function} callback - The callback function to be called.
313
   */
314
  set liveliness(callback) {
315
    this._liveliness = callback;
2✔
316
  }
317

318
  /**
319
   * Get the callback for liveliness changed event.
320
   * @return {function} - The callback function.
321
   */
322
  get liveliness() {
323
    return this._liveliness;
×
324
  }
325

326
  /**
327
   * Set the callback for message lost event.
328
   * @param {function} callback - The callback function to be called.
329
   */
330
  set messageLost(callback) {
331
    this._message_lost = callback;
×
332
  }
333

334
  /**
335
   * Get the callback for message lost event.
336
   * @return {function} - The callback function.
337
   */
338
  get messageLost() {
339
    return this._message_lost;
×
340
  }
341

342
  /**
343
   * Set the callback for incompatible type event.
344
   * @param {function} callback - The callback function to be called.
345
   */
346
  set incompatibleType(callback) {
347
    this._incompatible_type = callback;
×
348
  }
349

350
  /**
351
   * Get the callback for incompatible type event.
352
   * @return {function} - The callback function.
353
   */
354
  get incompatibleType() {
355
    return this._incompatible_type;
×
356
  }
357

358
  /**
359
   * Set the callback for matched event.
360
   * @param {function} callback - The callback function to be called.
361
   */
362
  set matched(callback) {
363
    this._matched = callback;
1✔
364
  }
365

366
  /**
367
   * Get the callback for matched event.
368
   * @return {function} - The callback function.
369
   */
370
  get matched() {
371
    return this._matched;
×
372
  }
373

374
  createEventHandlers(subscriptionHandle) {
375
    if (this._deadline) {
4✔
376
      const deadlineHandle = rclnodejs.createSubscriptionEventHandle(
3✔
377
        subscriptionHandle,
378
        SubscriptionEventType.SUBSCRIPTION_REQUESTED_DEADLINE_MISSED
379
      );
380
      this._eventHandlers.push(
3✔
381
        new EventHandler(
382
          deadlineHandle,
383
          this._deadline,
384
          SubscriptionEventType.SUBSCRIPTION_REQUESTED_DEADLINE_MISSED,
385
          'subscription_event_type'
386
        )
387
      );
388
    }
389

390
    if (this._incompatible_qos) {
4✔
391
      const incompatibleQosHandle = rclnodejs.createSubscriptionEventHandle(
1✔
392
        subscriptionHandle,
393
        SubscriptionEventType.SUBSCRIPTION_REQUESTED_INCOMPATIBLE_QOS
394
      );
395
      this._eventHandlers.push(
1✔
396
        new EventHandler(
397
          incompatibleQosHandle,
398
          this._incompatible_qos,
399
          SubscriptionEventType.SUBSCRIPTION_REQUESTED_INCOMPATIBLE_QOS,
400
          'subscription_event_type'
401
        )
402
      );
403
    }
404

405
    if (this._liveliness) {
4✔
406
      const livelinessHandle = rclnodejs.createSubscriptionEventHandle(
2✔
407
        subscriptionHandle,
408
        SubscriptionEventType.SUBSCRIPTION_LIVELINESS_CHANGED
409
      );
410
      this._eventHandlers.push(
2✔
411
        new EventHandler(
412
          livelinessHandle,
413
          this._liveliness,
414
          SubscriptionEventType.SUBSCRIPTION_LIVELINESS_CHANGED,
415
          'subscription_event_type'
416
        )
417
      );
418
    }
419

420
    if (this._message_lost) {
4!
421
      const messageLostHandle = rclnodejs.createSubscriptionEventHandle(
×
422
        subscriptionHandle,
423
        SubscriptionEventType.SUBSCRIPTION_MESSAGE_LOST
424
      );
425
      this._eventHandlers.push(
×
426
        new EventHandler(
427
          messageLostHandle,
428
          this._message_lost,
429
          SubscriptionEventType.SUBSCRIPTION_MESSAGE_LOST,
430
          'subscription_event_type'
431
        )
432
      );
433
    }
434

435
    if (this._incompatible_type) {
4!
436
      const incompatibleTypeHandle = rclnodejs.createSubscriptionEventHandle(
×
437
        subscriptionHandle,
438
        SubscriptionEventType.SUBSCRIPTION_INCOMPATIBLE_TYPE
439
      );
440
      this._eventHandlers.push(
×
441
        new EventHandler(
442
          incompatibleTypeHandle,
443
          this._incompatible_type,
444
          SubscriptionEventType.SUBSCRIPTION_INCOMPATIBLE_TYPE,
445
          'subscription_event_type'
446
        )
447
      );
448
    }
449

450
    if (this._matched) {
4✔
451
      const matchedHandle = rclnodejs.createSubscriptionEventHandle(
1✔
452
        subscriptionHandle,
453
        SubscriptionEventType.SUBSCRIPTION_MATCHED
454
      );
455
      this._eventHandlers.push(
1✔
456
        new EventHandler(
457
          matchedHandle,
458
          this._matched,
459
          SubscriptionEventType.SUBSCRIPTION_MATCHED,
460
          'subscription_event_type'
461
        )
462
      );
463
    }
464

465
    return this._eventHandlers;
4✔
466
  }
467
}
468

469
module.exports = {
26✔
470
  PublisherEventCallbacks,
471
  PublisherEventType,
472
  SubscriptionEventCallbacks,
473
  SubscriptionEventType,
474
};
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc