Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 9 additions & 4 deletions lib/clock.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,15 @@ class Clock {

/**
* Add a clock callback.
* @param {object} callbackObject - The object containing _pre_callback and _post_callback methods.
* @param {object} callbackObject - The object containing callback methods.
* @param {Function} [callbackObject._pre_callback] - Optional callback invoked before a time jump. Takes no arguments.
* @param {Function} [callbackObject._post_callback] - Optional callback invoked after a time jump.
* Receives a jumpInfo object with properties:
* - clock_change {number}: Type of clock change that occurred.
* - delta {bigint}: Time delta in nanoseconds.
* @param {boolean} onClockChange - Whether to call the callback on clock change.
* @param {bigint} minForward - Minimum forward jump to trigger the callback.
* @param {bigint} minBackward - Minimum backward jump to trigger the callback.
* @param {bigint} minForward - Minimum forward jump in nanoseconds to trigger the callback.
* @param {bigint} minBackward - Minimum backward jump in nanoseconds to trigger the callback.
*/
addClockCallback(callbackObject, onClockChange, minForward, minBackward) {
if (typeof callbackObject !== 'object' || callbackObject === null) {
Expand Down Expand Up @@ -92,7 +97,7 @@ class Clock {

/**
* Remove a clock callback.
* @param {object} callbackObject - The object containing _pre_callback and _post_callback methods.
* @param {object} callbackObject - The callback object that was previously registered with addClockCallback().
*/
removeClockCallback(callbackObject) {
if (typeof callbackObject !== 'object' || callbackObject === null) {
Expand Down
8 changes: 8 additions & 0 deletions lib/timer.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,14 @@ class Timer {
return rclnodejs.timerGetTimeUntilNextCall(this._handle);
}

/**
* Get the absolute time in nanoseconds when the next callback is due.
* @return {bigint | null} - The next call time in nanoseconds, or null if the timer is canceled.
*/
getNextCallTime() {
return rclnodejs.getTimerNextCallTime(this._handle);
}

/**
* Change the timer period.
* @param {bigint} period - The new period in nanoseconds.
Expand Down
25 changes: 24 additions & 1 deletion src/rcl_timer_bindings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,10 @@ Napi::Value CreateTimer(const Napi::CallbackInfo& info) {
#if ROS_VERSION > 2205
// Clear the callback first to prevent any new callbacks from being
// triggered
rcl_timer_set_on_reset_callback(timer, nullptr, nullptr);
rcl_ret_t callback_ret =
rcl_timer_set_on_reset_callback(timer, nullptr, nullptr);
(void)callback_ret; // Suppress unused variable warning if error
// handling is not needed

Copilot AI Dec 17, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The variable callback_ret is assigned but only used with a void cast to suppress warnings. While the comment explains this is intentional, silently ignoring the return value from rcl_timer_set_on_reset_callback could mask legitimate errors during timer cleanup. Consider logging the error or at minimum documenting why it's safe to ignore this specific error condition.

Copilot uses AI. Check for mistakes.
#endif

std::shared_ptr<TimerContext> context;
Expand Down Expand Up @@ -254,6 +257,24 @@ Napi::Value GetTimerPeriod(const Napi::CallbackInfo& info) {
return Napi::BigInt::New(env, period_nsec);
}

Napi::Value GetTimerNextCallTime(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
RclHandle* timer_handle = RclHandle::Unwrap(info[0].As<Napi::Object>());
rcl_timer_t* timer = reinterpret_cast<rcl_timer_t*>(timer_handle->ptr());
int64_t next_call_time = 0;

rcl_ret_t ret = rcl_timer_get_next_call_time(timer, &next_call_time);

if (ret == RCL_RET_OK) {
return Napi::BigInt::New(env, next_call_time);
} else if (ret == RCL_RET_TIMER_CANCELED) {
return env.Null();
} else {
THROW_ERROR_IF_NOT_EQUAL(RCL_RET_OK, ret, rcl_get_error_string().str);
return env.Undefined();

Copilot AI Dec 17, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error macro on line 273 will always throw an exception, making the return statement on line 274 unreachable. The else-if block already handles the timer canceled case, so this else clause should only handle unexpected error codes. Consider removing the return statement since THROW_ERROR_IF_NOT_EQUAL will always throw when the condition fails.

Suggested change
return env.Undefined();

Copilot uses AI. Check for mistakes.
}
}

#if ROS_VERSION > 2205 // 2205 == Humble
Napi::Value CallTimerWithInfo(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
Expand Down Expand Up @@ -341,6 +362,8 @@ Napi::Object InitTimerBindings(Napi::Env env, Napi::Object exports) {
Napi::Function::New(env, TimerGetTimeUntilNextCall));
exports.Set("changeTimerPeriod", Napi::Function::New(env, ChangeTimerPeriod));
exports.Set("getTimerPeriod", Napi::Function::New(env, GetTimerPeriod));
exports.Set("getTimerNextCallTime",
Napi::Function::New(env, GetTimerNextCallTime));
#if ROS_VERSION > 2205 // 2205 == Humble
exports.Set("setTimerOnResetCallback",
Napi::Function::New(env, SetTimerOnResetCallback));
Expand Down
14 changes: 14 additions & 0 deletions test/test-timer.js
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,20 @@ describe('rclnodejs Timer class testing', function () {
rclnodejs.spin(node);
});

it('timer.getNextCallTime', function (done) {
var timer = node.createTimer(TIMER_INTERVAL, function () {
var nextCallTime = timer.getNextCallTime();
assert.deepStrictEqual(typeof nextCallTime, 'bigint');
assert.ok(nextCallTime > 0n);
timer.cancel();
// After cancellation, should return null
var canceledCallTime = timer.getNextCallTime();
assert.strictEqual(canceledCallTime, null);
done();
});
rclnodejs.spin(node);
});

it('timer.timerPeriod', function (done) {
const timer = node.createTimer(BigInt('100000000'), () => {});
assert.deepStrictEqual(timer.timerPeriod, BigInt('100000000'));
Expand Down
1 change: 1 addition & 0 deletions test/types/index.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,7 @@ expectType<bigint>(timer.period);
expectType<boolean>(timer.isReady());
expectType<bigint>(timer.timeSinceLastCall());
expectType<bigint>(timer.timeUntilNextCall());
expectType<bigint | null>(timer.getNextCallTime());
expectType<boolean>(timer.isCanceled());
expectType<void>(timer.cancel());
expectType<void>(timer.changeTimerPeriod(BigInt(100000)));
Expand Down
43 changes: 37 additions & 6 deletions types/clock.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,35 @@
declare module 'rclnodejs' {
/**
* Jump information provided to clock jump callbacks.
*/
interface ClockJumpInfo {
/**
* Type of clock change that occurred.
*/
clock_change: number;

/**
* Time delta in nanoseconds.
*/
delta: bigint;
}

/**
* Callback object for clock jump events.
*/
interface ClockCallbackObject {
/**
* Optional callback invoked before a time jump.
*/
_pre_callback?: () => void;

/**
* Optional callback invoked after a time jump.
* @param jumpInfo - Information about the time jump.
*/
_post_callback?: (jumpInfo: ClockJumpInfo) => void;
}

/**
* A ROS Clock.
*/
Expand All @@ -19,23 +50,23 @@ declare module 'rclnodejs' {

/**
* Add a clock callback.
* @param callbackObject - The object containing _pre_callback and _post_callback methods.
* @param callbackObject - The object containing callback methods.
* @param onClockChange - Whether to call the callback on clock change.
* @param minForward - Minimum forward jump to trigger the callback.
* @param minBackward - Minimum backward jump to trigger the callback.
* @param minForward - Minimum forward jump in nanoseconds to trigger the callback.
* @param minBackward - Minimum backward jump in nanoseconds to trigger the callback.
*/
addClockCallback(
callbackObject: object,
callbackObject: ClockCallbackObject,
onClockChange: boolean,
minForward: bigint,
minBackward: bigint
): void;

/**
* Remove a clock callback.
* @param callbackObject - The object containing _pre_callback and _post_callback methods.
* @param callbackObject - The callback object that was previously registered with addClockCallback().
*/
removeClockCallback(callbackObject: object): void;
removeClockCallback(callbackObject: ClockCallbackObject): void;

/**
* Return the current time.
Expand Down
7 changes: 7 additions & 0 deletions types/timer.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,13 @@ declare module 'rclnodejs' {
*/
timeUntilNextCall(): bigint;

/**
* Get the absolute time in nanoseconds when the next callback is due.
*
* @returns The next call time in nanoseconds, or null if the timer is canceled.
*/
getNextCallTime(): bigint | null;

/**
* Change the timer period.
* @param period - The new period in nanoseconds.
Expand Down
Loading