rclnodejs 1.6.0 → 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/binding.gyp +2 -0
  2. package/index.js +152 -0
  3. package/lib/action/client.js +109 -10
  4. package/lib/action/deferred.js +8 -2
  5. package/lib/action/server.js +10 -1
  6. package/lib/action/uuid.js +4 -1
  7. package/lib/client.js +218 -4
  8. package/lib/clock.js +182 -1
  9. package/lib/clock_change.js +49 -0
  10. package/lib/clock_event.js +88 -0
  11. package/lib/context.js +12 -2
  12. package/lib/duration.js +37 -12
  13. package/lib/errors.js +621 -0
  14. package/lib/event_handler.js +21 -4
  15. package/lib/interface_loader.js +52 -12
  16. package/lib/lifecycle.js +8 -2
  17. package/lib/logging.js +90 -3
  18. package/lib/message_introspector.js +123 -0
  19. package/lib/message_serialization.js +10 -2
  20. package/lib/message_validation.js +512 -0
  21. package/lib/native_loader.js +9 -4
  22. package/lib/node.js +403 -50
  23. package/lib/node_options.js +40 -1
  24. package/lib/observable_subscription.js +105 -0
  25. package/lib/parameter.js +172 -35
  26. package/lib/parameter_client.js +506 -0
  27. package/lib/parameter_watcher.js +309 -0
  28. package/lib/publisher.js +56 -1
  29. package/lib/qos.js +79 -5
  30. package/lib/rate.js +6 -1
  31. package/lib/serialization.js +7 -2
  32. package/lib/subscription.js +8 -0
  33. package/lib/time.js +136 -21
  34. package/lib/time_source.js +13 -4
  35. package/lib/timer.js +42 -0
  36. package/lib/utils.js +27 -1
  37. package/lib/validator.js +74 -19
  38. package/package.json +4 -2
  39. package/prebuilds/linux-arm64/humble-jammy-arm64-rclnodejs.node +0 -0
  40. package/prebuilds/linux-arm64/jazzy-noble-arm64-rclnodejs.node +0 -0
  41. package/prebuilds/linux-arm64/kilted-noble-arm64-rclnodejs.node +0 -0
  42. package/prebuilds/linux-x64/humble-jammy-x64-rclnodejs.node +0 -0
  43. package/prebuilds/linux-x64/jazzy-noble-x64-rclnodejs.node +0 -0
  44. package/prebuilds/linux-x64/kilted-noble-x64-rclnodejs.node +0 -0
  45. package/rosidl_gen/message_translator.js +0 -61
  46. package/scripts/config.js +1 -0
  47. package/src/addon.cpp +2 -0
  48. package/src/clock_event.cpp +268 -0
  49. package/src/clock_event.hpp +62 -0
  50. package/src/macros.h +2 -4
  51. package/src/rcl_action_server_bindings.cpp +21 -3
  52. package/src/rcl_bindings.cpp +59 -0
  53. package/src/rcl_context_bindings.cpp +5 -0
  54. package/src/rcl_graph_bindings.cpp +73 -0
  55. package/src/rcl_logging_bindings.cpp +158 -0
  56. package/src/rcl_node_bindings.cpp +14 -2
  57. package/src/rcl_publisher_bindings.cpp +12 -0
  58. package/src/rcl_service_bindings.cpp +7 -6
  59. package/src/rcl_subscription_bindings.cpp +51 -14
  60. package/src/rcl_time_point_bindings.cpp +135 -0
  61. package/src/rcl_timer_bindings.cpp +140 -0
  62. package/src/rcl_utilities.cpp +103 -2
  63. package/src/rcl_utilities.h +7 -1
  64. package/types/action_client.d.ts +27 -2
  65. package/types/base.d.ts +6 -0
  66. package/types/client.d.ts +65 -1
  67. package/types/clock.d.ts +86 -0
  68. package/types/clock_change.d.ts +27 -0
  69. package/types/clock_event.d.ts +51 -0
  70. package/types/errors.d.ts +496 -0
  71. package/types/index.d.ts +10 -0
  72. package/types/logging.d.ts +32 -0
  73. package/types/message_introspector.d.ts +75 -0
  74. package/types/message_validation.d.ts +183 -0
  75. package/types/node.d.ts +107 -0
  76. package/types/node_options.d.ts +13 -0
  77. package/types/observable_subscription.d.ts +39 -0
  78. package/types/parameter_client.d.ts +252 -0
  79. package/types/parameter_watcher.d.ts +104 -0
  80. package/types/publisher.d.ts +28 -1
  81. package/types/qos.d.ts +18 -0
  82. package/types/subscription.d.ts +6 -0
  83. package/types/timer.d.ts +18 -0
  84. package/types/validator.d.ts +86 -0
@@ -15,15 +15,105 @@
15
15
  #include "rcl_logging_bindings.h"
16
16
 
17
17
  #include <rcl/error_handling.h>
18
+ #include <rcl/logging.h>
19
+ #include <rcl/logging_rosout.h>
18
20
  #include <rcl/rcl.h>
21
+ #include <rcl_logging_interface/rcl_logging_interface.h>
19
22
 
23
+ #include <mutex>
20
24
  #include <string>
21
25
 
22
26
  #include "macros.h"
27
+ #include "rcl_handle.h"
23
28
  #include "rcl_utilities.h"
24
29
 
25
30
  namespace rclnodejs {
26
31
 
32
+ Napi::Value InitRosoutPublisherForNode(const Napi::CallbackInfo& info) {
33
+ Napi::Env env = info.Env();
34
+ RclHandle* node_handle = RclHandle::Unwrap(info[0].As<Napi::Object>());
35
+ rcl_node_t* node = reinterpret_cast<rcl_node_t*>(node_handle->ptr());
36
+
37
+ if (rcl_logging_rosout_enabled()) {
38
+ rcl_ret_t ret = rcl_logging_rosout_init_publisher_for_node(node);
39
+ if (ret != RCL_RET_OK) {
40
+ Napi::Error::New(env, rcl_get_error_string().str)
41
+ .ThrowAsJavaScriptException();
42
+ rcl_reset_error();
43
+ }
44
+ }
45
+ return env.Undefined();
46
+ }
47
+
48
+ static std::recursive_mutex logging_mutex;
49
+
50
+ static void rclnodejs_thread_safe_logging_output_handler(
51
+ const rcutils_log_location_t* location, int severity, const char* name,
52
+ rcutils_time_point_value_t timestamp, const char* format, va_list* args) {
53
+ try {
54
+ std::lock_guard<std::recursive_mutex> lock(logging_mutex);
55
+ rcl_logging_multiple_output_handler(location, severity, name, timestamp,
56
+ format, args);
57
+ } catch (const std::exception& ex) {
58
+ RCUTILS_SAFE_FWRITE_TO_STDERR(
59
+ "rclnodejs failed to get the global logging mutex: ");
60
+ RCUTILS_SAFE_FWRITE_TO_STDERR(ex.what());
61
+ RCUTILS_SAFE_FWRITE_TO_STDERR("\n");
62
+ } catch (...) {
63
+ RCUTILS_SAFE_FWRITE_TO_STDERR(
64
+ "rclnodejs failed to get the global logging mutex\n");
65
+ }
66
+ }
67
+
68
+ Napi::Value LoggingConfigure(const Napi::CallbackInfo& info) {
69
+ Napi::Env env = info.Env();
70
+ RclHandle* context_handle = RclHandle::Unwrap(info[0].As<Napi::Object>());
71
+ rcl_context_t* context =
72
+ reinterpret_cast<rcl_context_t*>(context_handle->ptr());
73
+
74
+ rcl_allocator_t allocator = rcl_get_default_allocator();
75
+
76
+ std::lock_guard<std::recursive_mutex> lock(logging_mutex);
77
+ rcl_ret_t ret = rcl_logging_configure_with_output_handler(
78
+ &context->global_arguments, &allocator,
79
+ rclnodejs_thread_safe_logging_output_handler);
80
+
81
+ if (ret != RCL_RET_OK) {
82
+ Napi::Error::New(env, rcl_get_error_string().str)
83
+ .ThrowAsJavaScriptException();
84
+ rcl_reset_error();
85
+ }
86
+ return env.Undefined();
87
+ }
88
+
89
+ Napi::Value FiniRosoutPublisherForNode(const Napi::CallbackInfo& info) {
90
+ Napi::Env env = info.Env();
91
+ RclHandle* node_handle = RclHandle::Unwrap(info[0].As<Napi::Object>());
92
+ rcl_node_t* node = reinterpret_cast<rcl_node_t*>(node_handle->ptr());
93
+
94
+ if (rcl_logging_rosout_enabled()) {
95
+ rcl_ret_t ret = rcl_logging_rosout_fini_publisher_for_node(node);
96
+ if (ret != RCL_RET_OK) {
97
+ Napi::Error::New(env, rcl_get_error_string().str)
98
+ .ThrowAsJavaScriptException();
99
+ rcl_reset_error();
100
+ }
101
+ }
102
+ return env.Undefined();
103
+ }
104
+
105
+ Napi::Value LoggingFini(const Napi::CallbackInfo& info) {
106
+ Napi::Env env = info.Env();
107
+ std::lock_guard<std::recursive_mutex> lock(logging_mutex);
108
+ rcl_ret_t ret = rcl_logging_fini();
109
+ if (ret != RCL_RET_OK) {
110
+ Napi::Error::New(env, rcl_get_error_string().str)
111
+ .ThrowAsJavaScriptException();
112
+ rcl_reset_error();
113
+ }
114
+ return env.Undefined();
115
+ }
116
+
27
117
  Napi::Value setLoggerLevel(const Napi::CallbackInfo& info) {
28
118
  Napi::Env env = info.Env();
29
119
 
@@ -84,12 +174,80 @@ Napi::Value IsEnableFor(const Napi::CallbackInfo& info) {
84
174
  return Napi::Boolean::New(env, enabled);
85
175
  }
86
176
 
177
+ Napi::Value GetLoggingDirectory(const Napi::CallbackInfo& info) {
178
+ Napi::Env env = info.Env();
179
+ rcutils_allocator_t allocator = rcutils_get_default_allocator();
180
+ char* directory_path = nullptr;
181
+ rcl_logging_ret_t ret =
182
+ rcl_logging_get_logging_directory(allocator, &directory_path);
183
+
184
+ if (ret != RCL_LOGGING_RET_OK) {
185
+ Napi::Error::New(env, rcutils_get_error_string().str)
186
+ .ThrowAsJavaScriptException();
187
+ rcutils_reset_error();
188
+ return env.Undefined();
189
+ }
190
+
191
+ Napi::String result = Napi::String::New(env, directory_path);
192
+ allocator.deallocate(directory_path, allocator.state);
193
+ return result;
194
+ }
195
+
196
+ #if ROS_VERSION > 2205
197
+ Napi::Value AddRosoutSublogger(const Napi::CallbackInfo& info) {
198
+ Napi::Env env = info.Env();
199
+ std::string logger_name = info[0].As<Napi::String>().Utf8Value();
200
+ std::string sublogger_name = info[1].As<Napi::String>().Utf8Value();
201
+
202
+ rcl_ret_t ret = rcl_logging_rosout_add_sublogger(logger_name.c_str(),
203
+ sublogger_name.c_str());
204
+ if (ret == RCL_RET_OK) {
205
+ return Napi::Boolean::New(env, true);
206
+ } else if (ret == RCL_RET_NOT_FOUND) {
207
+ rcl_reset_error();
208
+ return Napi::Boolean::New(env, false);
209
+ } else {
210
+ Napi::Error::New(env, rcl_get_error_string().str)
211
+ .ThrowAsJavaScriptException();
212
+ rcl_reset_error();
213
+ return env.Undefined();
214
+ }
215
+ }
216
+
217
+ Napi::Value RemoveRosoutSublogger(const Napi::CallbackInfo& info) {
218
+ Napi::Env env = info.Env();
219
+ std::string logger_name = info[0].As<Napi::String>().Utf8Value();
220
+ std::string sublogger_name = info[1].As<Napi::String>().Utf8Value();
221
+
222
+ rcl_ret_t ret = rcl_logging_rosout_remove_sublogger(logger_name.c_str(),
223
+ sublogger_name.c_str());
224
+ if (ret != RCL_RET_OK) {
225
+ rcl_reset_error();
226
+ }
227
+ return env.Undefined();
228
+ }
229
+ #endif
230
+
87
231
  Napi::Object InitLoggingBindings(Napi::Env env, Napi::Object exports) {
88
232
  exports.Set("setLoggerLevel", Napi::Function::New(env, setLoggerLevel));
89
233
  exports.Set("getLoggerEffectiveLevel",
90
234
  Napi::Function::New(env, GetLoggerEffectiveLevel));
91
235
  exports.Set("log", Napi::Function::New(env, Log));
92
236
  exports.Set("isEnableFor", Napi::Function::New(env, IsEnableFor));
237
+ exports.Set("getLoggingDirectory",
238
+ Napi::Function::New(env, GetLoggingDirectory));
239
+ exports.Set("initRosoutPublisherForNode",
240
+ Napi::Function::New(env, InitRosoutPublisherForNode));
241
+ exports.Set("finiRosoutPublisherForNode",
242
+ Napi::Function::New(env, FiniRosoutPublisherForNode));
243
+ #if ROS_VERSION > 2205
244
+ exports.Set("addRosoutSublogger",
245
+ Napi::Function::New(env, AddRosoutSublogger));
246
+ exports.Set("removeRosoutSublogger",
247
+ Napi::Function::New(env, RemoveRosoutSublogger));
248
+ #endif
249
+ exports.Set("loggingConfigure", Napi::Function::New(env, LoggingConfigure));
250
+ exports.Set("loggingFini", Napi::Function::New(env, LoggingFini));
93
251
  return exports;
94
252
  }
95
253
 
@@ -26,8 +26,10 @@
26
26
  #include <rcl_yaml_param_parser/types.h>
27
27
 
28
28
  #include <rcpputils/scope_exit.hpp>
29
- // NOLINTNEXTLINE
29
+ // NOLINTBEGIN
30
+ #include <memory>
30
31
  #include <string>
32
+ // NOLINTEND
31
33
 
32
34
  #include "macros.h"
33
35
  #include "rcl_handle.h"
@@ -191,12 +193,17 @@ Napi::Value CreateNode(const Napi::CallbackInfo& info) {
191
193
  rcl_arguments_t arguments = rcl_get_zero_initialized_arguments();
192
194
  rcl_ret_t ret =
193
195
  rcl_parse_arguments(argc, argv, rcl_get_default_allocator(), &arguments);
194
- if ((ret != RCL_RET_OK) || HasUnparsedROSArgs(arguments)) {
196
+ if (ret != RCL_RET_OK) {
195
197
  Napi::Error::New(env, "failed to parse arguments")
196
198
  .ThrowAsJavaScriptException();
197
199
  return env.Undefined();
198
200
  }
199
201
 
202
+ ThrowIfUnparsedROSArgs(env, jsArgv, arguments);
203
+ if (env.IsExceptionPending()) {
204
+ return env.Undefined();
205
+ }
206
+
200
207
  RCPPUTILS_SCOPE_EXIT({
201
208
  if (RCL_RET_OK != rcl_arguments_fini(&arguments)) {
202
209
  Napi::Error::New(env, "failed to fini arguments")
@@ -212,6 +219,11 @@ Napi::Value CreateNode(const Napi::CallbackInfo& info) {
212
219
  options.use_global_arguments = use_global_arguments;
213
220
  options.arguments = arguments;
214
221
 
222
+ if (info.Length() > 5 && !info[5].IsUndefined() && !info[5].IsNull()) {
223
+ std::unique_ptr<rmw_qos_profile_t> qos_profile = GetQoSProfile(info[5]);
224
+ options.rosout_qos = *qos_profile;
225
+ }
226
+
215
227
  THROW_ERROR_IF_NOT_EQUAL(RCL_RET_OK,
216
228
  rcl_node_init(node, node_name.c_str(),
217
229
  name_space.c_str(), context, &options),
@@ -147,6 +147,17 @@ Napi::Value WaitForAllAcked(const Napi::CallbackInfo& info) {
147
147
  return env.Undefined();
148
148
  }
149
149
 
150
+ Napi::Value AssertLiveliness(const Napi::CallbackInfo& info) {
151
+ Napi::Env env = info.Env();
152
+ rcl_publisher_t* publisher = reinterpret_cast<rcl_publisher_t*>(
153
+ RclHandle::Unwrap(info[0].As<Napi::Object>())->ptr());
154
+
155
+ THROW_ERROR_IF_NOT_EQUAL(rcl_publisher_assert_liveliness(publisher),
156
+ RCL_RET_OK, rcl_get_error_string().str);
157
+
158
+ return env.Undefined();
159
+ }
160
+
150
161
  Napi::Object InitPublisherBindings(Napi::Env env, Napi::Object exports) {
151
162
  exports.Set("createPublisher", Napi::Function::New(env, CreatePublisher));
152
163
  exports.Set("publish", Napi::Function::New(env, Publish));
@@ -155,6 +166,7 @@ Napi::Object InitPublisherBindings(Napi::Env env, Napi::Object exports) {
155
166
  exports.Set("getSubscriptionCount",
156
167
  Napi::Function::New(env, GetSubscriptionCount));
157
168
  exports.Set("waitForAllAcked", Napi::Function::New(env, WaitForAllAcked));
169
+ exports.Set("assertLiveliness", Napi::Function::New(env, AssertLiveliness));
158
170
  return exports;
159
171
  }
160
172
 
@@ -77,11 +77,11 @@ Napi::Value RclTakeRequest(const Napi::CallbackInfo& info) {
77
77
 
78
78
  rcl_service_t* service = reinterpret_cast<rcl_service_t*>(
79
79
  RclHandle::Unwrap(info[0].As<Napi::Object>())->ptr());
80
- rmw_request_id_t* header =
81
- reinterpret_cast<rmw_request_id_t*>(malloc(sizeof(rmw_request_id_t)));
80
+ rmw_service_info_t* header =
81
+ reinterpret_cast<rmw_service_info_t*>(malloc(sizeof(rmw_service_info_t)));
82
82
 
83
83
  void* taken_request = info[2].As<Napi::Buffer<char>>().Data();
84
- rcl_ret_t ret = rcl_take_request(service, header, taken_request);
84
+ rcl_ret_t ret = rcl_take_request_with_info(service, header, taken_request);
85
85
  if (ret != RCL_RET_SERVICE_TAKE_FAILED) {
86
86
  auto js_obj = RclHandle::NewInstance(env, header, nullptr,
87
87
  [](void* ptr) { free(ptr); });
@@ -98,11 +98,12 @@ Napi::Value SendResponse(const Napi::CallbackInfo& info) {
98
98
  RclHandle::Unwrap(info[0].As<Napi::Object>())->ptr());
99
99
  void* buffer = info[1].As<Napi::Buffer<char>>().Data();
100
100
 
101
- rmw_request_id_t* header = reinterpret_cast<rmw_request_id_t*>(
101
+ rmw_service_info_t* header = reinterpret_cast<rmw_service_info_t*>(
102
102
  RclHandle::Unwrap(info[2].As<Napi::Object>())->ptr());
103
103
 
104
- THROW_ERROR_IF_NOT_EQUAL(rcl_send_response(service, header, buffer),
105
- RCL_RET_OK, rcl_get_error_string().str);
104
+ THROW_ERROR_IF_NOT_EQUAL(
105
+ rcl_send_response(service, &(header->request_id), buffer), RCL_RET_OK,
106
+ rcl_get_error_string().str);
106
107
 
107
108
  return env.Undefined();
108
109
  }
@@ -76,7 +76,6 @@ Napi::Value CreateSubscription(const Napi::CallbackInfo& info) {
76
76
  subscription_ops.qos = *qos_profile;
77
77
  }
78
78
 
79
- #if ROS_VERSION >= 2205 // 2205 => Humble+
80
79
  if (options.Has("contentFilter")) {
81
80
  // configure content-filter
82
81
  Napi::Value contentFilterVal = options.Get("contentFilter");
@@ -123,7 +122,6 @@ Napi::Value CreateSubscription(const Napi::CallbackInfo& info) {
123
122
  }
124
123
  }
125
124
  }
126
- #endif
127
125
 
128
126
  const rosidl_message_type_support_t* ts =
129
127
  GetMessageTypeSupport(package_name, message_sub_folder, message_name);
@@ -201,9 +199,6 @@ Napi::Value GetSubscriptionTopic(const Napi::CallbackInfo& info) {
201
199
  }
202
200
 
203
201
  Napi::Value HasContentFilter(const Napi::CallbackInfo& info) {
204
- #if ROS_VERSION < 2205 // 2205 => Humble+
205
- return Napi::Boolean::New(info.Env(), false);
206
- #else
207
202
  Napi::Env env = info.Env();
208
203
 
209
204
  RclHandle* subscription_handle =
@@ -213,13 +208,9 @@ Napi::Value HasContentFilter(const Napi::CallbackInfo& info) {
213
208
 
214
209
  bool is_valid = rcl_subscription_is_cft_enabled(subscription);
215
210
  return Napi::Boolean::New(env, is_valid);
216
- #endif
217
211
  }
218
212
 
219
213
  Napi::Value SetContentFilter(const Napi::CallbackInfo& info) {
220
- #if ROS_VERSION < 2205 // 2205 => Humble+
221
- return Napi::Boolean::New(info.Env(), false);
222
- #else
223
214
  Napi::Env env = info.Env();
224
215
 
225
216
  RclHandle* subscription_handle =
@@ -272,13 +263,9 @@ Napi::Value SetContentFilter(const Napi::CallbackInfo& info) {
272
263
  }
273
264
 
274
265
  return Napi::Boolean::New(env, true);
275
- #endif
276
266
  }
277
267
 
278
268
  Napi::Value ClearContentFilter(const Napi::CallbackInfo& info) {
279
- #if ROS_VERSION < 2205 // 2205 => Humble+
280
- return Napi::Boolean::New(info.Env(), false);
281
- #else
282
269
  Napi::Env env = info.Env();
283
270
 
284
271
  RclHandle* subscription_handle =
@@ -301,7 +288,56 @@ Napi::Value ClearContentFilter(const Napi::CallbackInfo& info) {
301
288
  rcl_get_error_string().str);
302
289
 
303
290
  return Napi::Boolean::New(env, true);
304
- #endif
291
+ }
292
+
293
+ Napi::Value GetContentFilter(const Napi::CallbackInfo& info) {
294
+ Napi::Env env = info.Env();
295
+
296
+ RclHandle* subscription_handle =
297
+ RclHandle::Unwrap(info[0].As<Napi::Object>());
298
+ rcl_subscription_t* subscription =
299
+ reinterpret_cast<rcl_subscription_t*>(subscription_handle->ptr());
300
+
301
+ rcl_subscription_content_filter_options_t options =
302
+ rcl_get_zero_initialized_subscription_content_filter_options();
303
+
304
+ rcl_ret_t ret = rcl_subscription_get_content_filter(subscription, &options);
305
+ if (ret != RCL_RET_OK) {
306
+ Napi::Error::New(env, rcl_get_error_string().str)
307
+ .ThrowAsJavaScriptException();
308
+ rcl_reset_error();
309
+ return env.Undefined();
310
+ }
311
+
312
+ // Create result object
313
+ Napi::Object result = Napi::Object::New(env);
314
+ result.Set(
315
+ "expression",
316
+ Napi::String::New(
317
+ env,
318
+ options.rmw_subscription_content_filter_options.filter_expression));
319
+
320
+ size_t param_count = options.rmw_subscription_content_filter_options
321
+ .expression_parameters.size;
322
+ Napi::Array parameters = Napi::Array::New(env, param_count);
323
+ for (size_t i = 0; i < param_count; ++i) {
324
+ parameters[i] =
325
+ Napi::String::New(env, options.rmw_subscription_content_filter_options
326
+ .expression_parameters.data[i]);
327
+ }
328
+ result.Set("parameters", parameters);
329
+
330
+ // Cleanup
331
+ rcl_ret_t fini_ret =
332
+ rcl_subscription_content_filter_options_fini(subscription, &options);
333
+ if (fini_ret != RCL_RET_OK) {
334
+ Napi::Error::New(env, rcl_get_error_string().str)
335
+ .ThrowAsJavaScriptException();
336
+ rcl_reset_error();
337
+ return env.Undefined();
338
+ }
339
+
340
+ return result;
305
341
  }
306
342
 
307
343
  Napi::Value GetPublisherCount(const Napi::CallbackInfo& info) {
@@ -327,6 +363,7 @@ Napi::Object InitSubscriptionBindings(Napi::Env env, Napi::Object exports) {
327
363
  Napi::Function::New(env, GetSubscriptionTopic));
328
364
  exports.Set("hasContentFilter", Napi::Function::New(env, HasContentFilter));
329
365
  exports.Set("setContentFilter", Napi::Function::New(env, SetContentFilter));
366
+ exports.Set("getContentFilter", Napi::Function::New(env, GetContentFilter));
330
367
  exports.Set("clearContentFilter",
331
368
  Napi::Function::New(env, ClearContentFilter));
332
369
  exports.Set("getPublisherCount", Napi::Function::New(env, GetPublisherCount));
@@ -175,9 +175,144 @@ Napi::Value ClockGetNow(const Napi::CallbackInfo& info) {
175
175
  return Napi::BigInt::New(env, time_point.nanoseconds);
176
176
  }
177
177
 
178
+ struct JumpCallbackData {
179
+ Napi::ThreadSafeFunction tsfn_pre;
180
+ Napi::ThreadSafeFunction tsfn_post;
181
+ };
182
+
183
+ struct JumpCallbackContext {
184
+ rcl_time_jump_t time_jump;
185
+ bool before_jump;
186
+ };
187
+
188
+ void _rclnodejs_on_time_jump(const rcl_time_jump_t* time_jump, bool before_jump,
189
+ void* user_data) {
190
+ JumpCallbackData* data = static_cast<JumpCallbackData*>(user_data);
191
+
192
+ auto context = new JumpCallbackContext{*time_jump, before_jump};
193
+
194
+ if (before_jump) {
195
+ auto callback = [](Napi::Env env, Napi::Function js_callback,
196
+ JumpCallbackContext* context) {
197
+ js_callback.Call({});
198
+ delete context;
199
+ };
200
+ data->tsfn_pre.NonBlockingCall(context, callback);
201
+ } else {
202
+ auto callback = [](Napi::Env env, Napi::Function js_callback,
203
+ JumpCallbackContext* context) {
204
+ Napi::Object jump_info = Napi::Object::New(env);
205
+ jump_info.Set("clock_change",
206
+ static_cast<int32_t>(context->time_jump.clock_change));
207
+ jump_info.Set("delta", Napi::BigInt::New(
208
+ env, context->time_jump.delta.nanoseconds));
209
+ js_callback.Call({jump_info});
210
+ delete context;
211
+ };
212
+ data->tsfn_post.NonBlockingCall(context, callback);
213
+ }
214
+ }
215
+
216
+ Napi::Value ClockAddJumpCallback(const Napi::CallbackInfo& info) {
217
+ Napi::Env env = info.Env();
218
+
219
+ RclHandle* clock_handle = RclHandle::Unwrap(info[0].As<Napi::Object>());
220
+ rcl_clock_t* clock = reinterpret_cast<rcl_clock_t*>(clock_handle->ptr());
221
+
222
+ Napi::Object callback_obj = info[1].As<Napi::Object>();
223
+ Napi::Function pre_callback =
224
+ callback_obj.Get("_pre_callback").As<Napi::Function>();
225
+ Napi::Function post_callback =
226
+ callback_obj.Get("_post_callback").As<Napi::Function>();
227
+
228
+ bool on_clock_change = info[2].As<Napi::Boolean>();
229
+
230
+ bool lossless;
231
+ int64_t min_forward = info[3].As<Napi::BigInt>().Int64Value(&lossless);
232
+ if (!lossless) {
233
+ Napi::TypeError::New(
234
+ env, "min_forward BigInt value cannot be represented as int64_t")
235
+ .ThrowAsJavaScriptException();
236
+ return env.Undefined();
237
+ }
238
+ int64_t min_backward = info[4].As<Napi::BigInt>().Int64Value(&lossless);
239
+ if (!lossless) {
240
+ Napi::TypeError::New(
241
+ env, "min_backward BigInt value cannot be represented as int64_t")
242
+ .ThrowAsJavaScriptException();
243
+ return env.Undefined();
244
+ }
245
+
246
+ rcl_jump_threshold_t threshold;
247
+ threshold.on_clock_change = on_clock_change;
248
+ threshold.min_forward.nanoseconds = min_forward;
249
+ threshold.min_backward.nanoseconds = min_backward;
250
+
251
+ JumpCallbackData* data = new JumpCallbackData();
252
+ data->tsfn_pre = Napi::ThreadSafeFunction::New(
253
+ env, pre_callback, "ClockJumpPreCallback", 10, 1, [](Napi::Env) {});
254
+ data->tsfn_post =
255
+ Napi::ThreadSafeFunction::New(env, post_callback, "ClockJumpPostCallback",
256
+ 10, 1, [data](Napi::Env) { delete data; });
257
+
258
+ Napi::Object handle_obj = Napi::Object::New(env);
259
+ handle_obj.Set("_cpp_handle",
260
+ Napi::External<JumpCallbackData>::New(env, data));
261
+
262
+ rcl_ret_t ret = rcl_clock_add_jump_callback(clock, threshold,
263
+ _rclnodejs_on_time_jump, data);
264
+
265
+ if (ret != RCL_RET_OK) {
266
+ data->tsfn_pre.Release();
267
+ data->tsfn_post.Release();
268
+ THROW_ERROR_IF_NOT_EQUAL(RCL_RET_OK, ret, rcl_get_error_string().str);
269
+ }
270
+
271
+ callback_obj.Set("_cpp_handle", handle_obj.Get("_cpp_handle"));
272
+
273
+ return env.Undefined();
274
+ }
275
+
276
+ Napi::Value ClockRemoveJumpCallback(const Napi::CallbackInfo& info) {
277
+ Napi::Env env = info.Env();
278
+
279
+ RclHandle* clock_handle = RclHandle::Unwrap(info[0].As<Napi::Object>());
280
+ rcl_clock_t* clock = reinterpret_cast<rcl_clock_t*>(clock_handle->ptr());
281
+
282
+ Napi::Object handle_obj = info[1].As<Napi::Object>();
283
+ Napi::Value cpp_handle = handle_obj.Get("_cpp_handle");
284
+
285
+ if (cpp_handle.IsUndefined() || !cpp_handle.IsExternal()) {
286
+ Napi::Error::New(env,
287
+ "Callback object was not registered or already removed")
288
+ .ThrowAsJavaScriptException();
289
+ return env.Undefined();
290
+ }
291
+
292
+ JumpCallbackData* data =
293
+ cpp_handle.As<Napi::External<JumpCallbackData>>().Data();
294
+
295
+ rcl_ret_t ret =
296
+ rcl_clock_remove_jump_callback(clock, _rclnodejs_on_time_jump, data);
297
+
298
+ if (ret == RCL_RET_OK) {
299
+ data->tsfn_pre.Release();
300
+ data->tsfn_post.Release();
301
+ handle_obj.Set("_cpp_handle", env.Undefined());
302
+ } else {
303
+ THROW_ERROR_IF_NOT_EQUAL(RCL_RET_OK, ret, rcl_get_error_string().str);
304
+ }
305
+
306
+ return env.Undefined();
307
+ }
308
+
178
309
  Napi::Object InitTimePointBindings(Napi::Env env, Napi::Object exports) {
179
310
  exports.Set("createClock", Napi::Function::New(env, CreateClock));
180
311
  exports.Set("clockGetNow", Napi::Function::New(env, ClockGetNow));
312
+ exports.Set("clockAddJumpCallback",
313
+ Napi::Function::New(env, ClockAddJumpCallback));
314
+ exports.Set("clockRemoveJumpCallback",
315
+ Napi::Function::New(env, ClockRemoveJumpCallback));
181
316
  exports.Set("createTimePoint", Napi::Function::New(env, CreateTimePoint));
182
317
  exports.Set("getNanoseconds", Napi::Function::New(env, GetNanoseconds));
183
318
  exports.Set("createDuration", Napi::Function::New(env, CreateDuration));