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

RobotWebTools / rclnodejs / 20742259610

06 Jan 2026 08:04AM UTC coverage: 80.626% (-2.2%) from 82.843%
20742259610

push

github

minggangw
Add ClockEvent support (#1354)

This PR adds comprehensive ClockEvent support to rclnodejs, enabling clock-based sleep functionality for STEADY_TIME, SYSTEM_TIME, and ROS_TIME clocks.

### New Features

**ClockEvent Class**
- Thread-safe event synchronization for clock-based waiting
- Support for steady, system, and ROS clock types
- Async worker pattern for non-blocking sleep operations
- Clock epoch synchronization between RCL and std::chrono

**Clock Sleep Methods**
- `Clock.sleepUntil(until, context)` - Sleep until absolute time point
- `Clock.sleepFor(duration, context)` - Sleep for specified duration
- Clock jump callbacks to wake on time changes
- Context-aware early wakeup on shutdown
- Detects ROS time activation/deactivation

**ClockChange Enum**
- `ROS_TIME_NO_CHANGE`, `ROS_TIME_ACTIVATED`, `ROS_TIME_DEACTIVATED`, `SYSTEM_TIME_NO_CHANGE`
- Used in clock jump callback notifications

### Critical Fixes

**BigInt Precision Loss Prevention**
- Added lossless conversion checks for nanosecond timestamps
- Prevents silent data corruption when converting BigInt to int64_t

**Missing Module Imports**
- Fixed missing Context, ClockEvent, and ClockChange imports in clock.js

### Test Coverage

- **test-clock-event.js** - Basic ClockEvent operations (4 tests)
- **test-clock-sleep.js** - Sleep methods for all clock types (11 tests)
  - Includes comprehensive ROS time active scenario with TimeSource + simulated clock messages
- **test-clock-change.js** - ClockChange enum and integration tests (11 tests)

**Results**: 1055 passing, 6 pending

### Files Changed

**Added**: 
- `src/clock_event.{cpp,hpp}`, clock_event.js, clock_change.js
- `types/clock_event.d.ts`, `types/clock_change.d.ts`
- `test/test-clock-{event,sleep,change}.js`

**Modified**:
- clock.js - Added sleep methods and imports
- `types/clock.d.ts`, index.d.ts - Added type definitions
- `binding.gyp`, `index.js`, `src/addon.cpp` - Registered bindings

**Impact**: +1397 lines, fully backward co... (continued)

1268 of 1753 branches covered (72.33%)

Branch coverage included in aggregate %.

40 of 42 new or added lines in 3 files covered. (95.24%)

122 existing lines in 10 files now uncovered.

2727 of 3202 relevant lines covered (85.17%)

465.68 hits per line

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

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

15
'use strict';
16

17
const rclnodejs = require('./native_loader.js');
26✔
18
const DistroUtils = require('./distro.js');
26✔
19

20
/**
21
 * @class - Class representing a Timer in ROS
22
 * @hideconstructor
23
 */
24

25
class Timer {
26
  constructor(handle, period, callback) {
27
    this._handle = handle;
60✔
28
    this._period = period;
60✔
29
    this.callback = callback;
60✔
30
  }
31

32
  /**
33
   * @type {bigint} - The period of the timer in nanoseconds.
34
   */
35
  get period() {
36
    return this._period;
3✔
37
  }
38

39
  get handle() {
40
    return this._handle;
11,409✔
41
  }
42

43
  /**
44
   * Check if the timer is ready.
45
   * @return {boolean} Return true if timer is ready, otherwise return false.
46
   */
47
  isReady() {
48
    return rclnodejs.isTimerReady(this._handle);
5,729✔
49
  }
50

51
  /**
52
   * Check if the timer is canceled.
53
   * @return {boolean} Return true if timer is canceled, otherwise return false.
54
   */
55
  isCanceled() {
56
    return rclnodejs.isTimerCanceled(this._handle);
3,008✔
57
  }
58

59
  /**
60
   * Cancel the timer.
61
   * @return {undefined}
62
   */
63
  cancel() {
64
    rclnodejs.cancelTimer(this._handle);
49✔
65
  }
66

67
  /**
68
   * Reset the timer.
69
   * @return {undefined}
70
   */
71
  reset() {
72
    rclnodejs.resetTimer(this._handle);
3✔
73
  }
74

75
  /**
76
   * Get the interval since the last call of this timer.
77
   * @return {bigint} - the interval value in nanoseconds.
78
   */
79
  timeSinceLastCall() {
80
    return rclnodejs.timerGetTimeSinceLastCall(this._handle);
1✔
81
  }
82

83
  /**
84
   * Get the interval until the next call will happen.
85
   * @return {bigint} - the interval value in nanoseconds.
86
   */
87
  timeUntilNextCall() {
88
    return rclnodejs.timerGetTimeUntilNextCall(this._handle);
1✔
89
  }
90

91
  /**
92
   * Get the absolute time in nanoseconds when the next callback is due.
93
   * Note: Only available on ROS2 distributions after Humble.
94
   * @return {bigint | null} - The next call time in nanoseconds, or null if the timer is canceled.
95
   *   Returns undefined if not supported on current ROS2 distribution.
96
   */
97
  getNextCallTime() {
98
    if (typeof rclnodejs.getTimerNextCallTime !== 'function') {
2!
UNCOV
99
      return undefined;
×
100
    }
101
    return rclnodejs.getTimerNextCallTime(this._handle);
2✔
102
  }
103

104
  /**
105
   * Change the timer period.
106
   * @param {bigint} period - The new period in nanoseconds.
107
   * @return {undefined}
108
   */
109
  changeTimerPeriod(period) {
110
    rclnodejs.changeTimerPeriod(this._handle, period);
1✔
111
  }
112

113
  /**
114
   * Get the timer period.
115
   * @return {bigint} - The period in nanoseconds.
116
   */
117
  get timerPeriod() {
118
    return rclnodejs.getTimerPeriod(this._handle);
2✔
119
  }
120

121
  /**
122
   * Set the on reset callback.
123
   * @param {function} callback - The callback to be called when the timer is reset.
124
   * @return {undefined}
125
   */
126
  setOnResetCallback(callback) {
127
    if (DistroUtils.getDistroId() <= DistroUtils.getDistroId('humble')) {
2!
UNCOV
128
      console.warn(
×
129
        'setOnResetCallback is not supported by this version of ROS 2'
130
      );
UNCOV
131
      return;
×
132
    }
133
    rclnodejs.setTimerOnResetCallback(this._handle, callback);
2✔
134
  }
135

136
  /**
137
   * Clear the on reset callback.
138
   * @return {undefined}
139
   */
140
  clearOnResetCallback() {
141
    if (DistroUtils.getDistroId() <= DistroUtils.getDistroId('humble')) {
1!
UNCOV
142
      console.warn(
×
143
        'clearOnResetCallback is not supported by this version of ROS 2'
144
      );
UNCOV
145
      return;
×
146
    }
147
    rclnodejs.clearTimerOnResetCallback(this._handle);
1✔
148
  }
149

150
  /**
151
   * Call a timer and starts counting again, retrieves actual and expected call time.
152
   * @return {object} - The timer information.
153
   */
154
  callTimerWithInfo() {
155
    if (DistroUtils.getDistroId() <= DistroUtils.getDistroId('humble')) {
1!
UNCOV
156
      console.warn(
×
157
        'callTimerWithInfo is not supported by this version of ROS 2'
158
      );
UNCOV
159
      return;
×
160
    }
161
    return rclnodejs.callTimerWithInfo(this._handle);
1✔
162
  }
163
}
164

165
module.exports = Timer;
26✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc