1717#include < rcl/error_handling.h>
1818#include < rcl/rcl.h>
1919
20+ #include < memory>
21+ #include < mutex>
22+ #include < unordered_map>
23+
2024#include " macros.h"
2125#include " rcl_handle.h"
2226#include " rcl_utilities.h"
2327
2428namespace rclnodejs {
2529
30+ struct TimerContext {
31+ Napi::ThreadSafeFunction on_reset_callback;
32+ };
33+
34+ static std::unordered_map<rcl_timer_t *, std::shared_ptr<TimerContext>>
35+ g_timer_contexts;
36+ static std::mutex g_timer_contexts_mutex;
37+
38+ void TimerOnResetCallbackTrampoline (const void * user_data,
39+ size_t number_of_events) {
40+ const rcl_timer_t * timer = static_cast <const rcl_timer_t *>(user_data);
41+ std::shared_ptr<TimerContext> context;
42+
43+ {
44+ std::lock_guard<std::mutex> lock (g_timer_contexts_mutex);
45+ auto it = g_timer_contexts.find (const_cast <rcl_timer_t *>(timer));
46+ if (it != g_timer_contexts.end ()) {
47+ context = it->second ;
48+ }
49+ }
50+
51+ if (context) {
52+ auto callback = [](Napi::Env env, Napi::Function js_callback,
53+ size_t * events) {
54+ js_callback.Call ({Napi::Number::New (env, *events)});
55+ delete events;
56+ };
57+ size_t * events_ptr = new size_t (number_of_events);
58+ context->on_reset_callback .BlockingCall (events_ptr, callback);
59+ }
60+ }
61+
2662Napi::Value CreateTimer (const Napi::CallbackInfo& info) {
2763 Napi::Env env = info.Env ();
2864
@@ -61,6 +97,27 @@ Napi::Value CreateTimer(const Napi::CallbackInfo& info) {
6197 auto js_obj =
6298 RclHandle::NewInstance (env, timer, clock_handle, [env](void * ptr) {
6399 rcl_timer_t * timer = reinterpret_cast <rcl_timer_t *>(ptr);
100+
101+ #if ROS_VERSION > 2205
102+ // Clear the callback first to prevent any new callbacks from being
103+ // triggered
104+ rcl_timer_set_on_reset_callback (timer, nullptr , nullptr );
105+ #endif
106+
107+ std::shared_ptr<TimerContext> context;
108+ {
109+ std::lock_guard<std::mutex> lock (g_timer_contexts_mutex);
110+ auto it = g_timer_contexts.find (timer);
111+ if (it != g_timer_contexts.end ()) {
112+ context = it->second ;
113+ g_timer_contexts.erase (it);
114+ }
115+ }
116+
117+ if (context) {
118+ context->on_reset_callback .Release ();
119+ }
120+
64121 rcl_ret_t ret = rcl_timer_fini (timer);
65122 free (ptr);
66123 THROW_ERROR_IF_NOT_EQUAL_NO_RETURN (RCL_RET_OK, ret,
@@ -215,6 +272,60 @@ Napi::Value CallTimerWithInfo(const Napi::CallbackInfo& info) {
215272 Napi::BigInt::New (env, call_info.actual_call_time ));
216273 return timer_info;
217274}
275+
276+ Napi::Value SetTimerOnResetCallback (const Napi::CallbackInfo& info) {
277+ Napi::Env env = info.Env ();
278+ RclHandle* timer_handle = RclHandle::Unwrap (info[0 ].As <Napi::Object>());
279+ rcl_timer_t * timer = reinterpret_cast <rcl_timer_t *>(timer_handle->ptr ());
280+
281+ if (!info[1 ].IsFunction ()) {
282+ Napi::TypeError::New (env, " Callback must be a function" )
283+ .ThrowAsJavaScriptException ();
284+ return env.Undefined ();
285+ }
286+
287+ Napi::Function callback = info[1 ].As <Napi::Function>();
288+
289+ std::lock_guard<std::mutex> lock (g_timer_contexts_mutex);
290+ std::shared_ptr<TimerContext> context;
291+ auto it = g_timer_contexts.find (timer);
292+ if (it == g_timer_contexts.end ()) {
293+ context = std::make_shared<TimerContext>();
294+ g_timer_contexts[timer] = context;
295+ } else {
296+ context = it->second ;
297+ context->on_reset_callback .Release ();
298+ }
299+
300+ context->on_reset_callback = Napi::ThreadSafeFunction::New (
301+ env, callback, " TimerOnResetCallback" , 0 , 1 );
302+
303+ THROW_ERROR_IF_NOT_EQUAL (RCL_RET_OK,
304+ rcl_timer_set_on_reset_callback (
305+ timer, TimerOnResetCallbackTrampoline, timer),
306+ rcl_get_error_string ().str );
307+
308+ return env.Undefined ();
309+ }
310+
311+ Napi::Value ClearTimerOnResetCallback (const Napi::CallbackInfo& info) {
312+ Napi::Env env = info.Env ();
313+ RclHandle* timer_handle = RclHandle::Unwrap (info[0 ].As <Napi::Object>());
314+ rcl_timer_t * timer = reinterpret_cast <rcl_timer_t *>(timer_handle->ptr ());
315+
316+ std::lock_guard<std::mutex> lock (g_timer_contexts_mutex);
317+ auto it = g_timer_contexts.find (timer);
318+ if (it != g_timer_contexts.end ()) {
319+ it->second ->on_reset_callback .Release ();
320+ g_timer_contexts.erase (it);
321+ }
322+
323+ THROW_ERROR_IF_NOT_EQUAL (
324+ RCL_RET_OK, rcl_timer_set_on_reset_callback (timer, nullptr , nullptr ),
325+ rcl_get_error_string ().str );
326+
327+ return env.Undefined ();
328+ }
218329#endif
219330
220331Napi::Object InitTimerBindings (Napi::Env env, Napi::Object exports) {
@@ -231,6 +342,10 @@ Napi::Object InitTimerBindings(Napi::Env env, Napi::Object exports) {
231342 exports.Set (" changeTimerPeriod" , Napi::Function::New (env, ChangeTimerPeriod));
232343 exports.Set (" getTimerPeriod" , Napi::Function::New (env, GetTimerPeriod));
233344#if ROS_VERSION > 2205 // 2205 == Humble
345+ exports.Set (" setTimerOnResetCallback" ,
346+ Napi::Function::New (env, SetTimerOnResetCallback));
347+ exports.Set (" clearTimerOnResetCallback" ,
348+ Napi::Function::New (env, ClearTimerOnResetCallback));
234349 exports.Set (" callTimerWithInfo" , Napi::Function::New (env, CallTimerWithInfo));
235350#endif
236351 return exports;
0 commit comments