rclnodejs 1.7.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 (65) hide show
  1. package/binding.gyp +2 -0
  2. package/index.js +93 -0
  3. package/lib/action/client.js +54 -1
  4. package/lib/client.js +66 -1
  5. package/lib/clock.js +178 -0
  6. package/lib/clock_change.js +49 -0
  7. package/lib/clock_event.js +88 -0
  8. package/lib/errors.js +50 -0
  9. package/lib/logging.js +78 -0
  10. package/lib/message_introspector.js +123 -0
  11. package/lib/message_validation.js +512 -0
  12. package/lib/node.js +133 -1
  13. package/lib/node_options.js +40 -1
  14. package/lib/observable_subscription.js +105 -0
  15. package/lib/publisher.js +56 -1
  16. package/lib/qos.js +57 -0
  17. package/lib/subscription.js +8 -0
  18. package/lib/timer.js +42 -0
  19. package/lib/validator.js +63 -7
  20. package/package.json +4 -2
  21. package/prebuilds/linux-arm64/humble-jammy-arm64-rclnodejs.node +0 -0
  22. package/prebuilds/linux-arm64/jazzy-noble-arm64-rclnodejs.node +0 -0
  23. package/prebuilds/linux-arm64/kilted-noble-arm64-rclnodejs.node +0 -0
  24. package/prebuilds/linux-x64/humble-jammy-x64-rclnodejs.node +0 -0
  25. package/prebuilds/linux-x64/jazzy-noble-x64-rclnodejs.node +0 -0
  26. package/prebuilds/linux-x64/kilted-noble-x64-rclnodejs.node +0 -0
  27. package/rosidl_gen/message_translator.js +0 -61
  28. package/scripts/config.js +1 -0
  29. package/src/addon.cpp +2 -0
  30. package/src/clock_event.cpp +268 -0
  31. package/src/clock_event.hpp +62 -0
  32. package/src/macros.h +2 -4
  33. package/src/rcl_action_server_bindings.cpp +21 -3
  34. package/src/rcl_bindings.cpp +59 -0
  35. package/src/rcl_context_bindings.cpp +5 -0
  36. package/src/rcl_graph_bindings.cpp +73 -0
  37. package/src/rcl_logging_bindings.cpp +158 -0
  38. package/src/rcl_node_bindings.cpp +14 -2
  39. package/src/rcl_publisher_bindings.cpp +12 -0
  40. package/src/rcl_service_bindings.cpp +7 -6
  41. package/src/rcl_subscription_bindings.cpp +51 -14
  42. package/src/rcl_time_point_bindings.cpp +135 -0
  43. package/src/rcl_timer_bindings.cpp +140 -0
  44. package/src/rcl_utilities.cpp +103 -2
  45. package/src/rcl_utilities.h +7 -1
  46. package/types/action_client.d.ts +27 -2
  47. package/types/base.d.ts +3 -0
  48. package/types/client.d.ts +29 -1
  49. package/types/clock.d.ts +86 -0
  50. package/types/clock_change.d.ts +27 -0
  51. package/types/clock_event.d.ts +51 -0
  52. package/types/errors.d.ts +49 -0
  53. package/types/index.d.ts +10 -0
  54. package/types/interfaces.d.ts +1 -1910
  55. package/types/logging.d.ts +32 -0
  56. package/types/message_introspector.d.ts +75 -0
  57. package/types/message_validation.d.ts +183 -0
  58. package/types/node.d.ts +67 -0
  59. package/types/node_options.d.ts +13 -0
  60. package/types/observable_subscription.d.ts +39 -0
  61. package/types/publisher.d.ts +28 -1
  62. package/types/qos.d.ts +18 -0
  63. package/types/subscription.d.ts +6 -0
  64. package/types/timer.d.ts +18 -0
  65. package/types/validator.d.ts +86 -0
@@ -0,0 +1,268 @@
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
+ #include "clock_event.hpp"
16
+
17
+ #include <rcl/error_handling.h>
18
+ #include <rcl/rcl.h>
19
+ #include <rcl/time.h>
20
+ #include <rcl/types.h>
21
+
22
+ #include <chrono>
23
+ #include <memory>
24
+ #include <mutex>
25
+ #include <stdexcept>
26
+
27
+ #include "macros.h"
28
+ #include "rcl_handle.h"
29
+
30
+ namespace rclnodejs {
31
+
32
+ template <typename ClockType>
33
+ void ClockEvent::wait_until(rcl_clock_t* clock, rcl_time_point_t until) {
34
+ // Synchronize because clock epochs might differ
35
+ rcl_time_point_value_t now_value;
36
+ rcl_ret_t ret = rcl_clock_get_now(clock, &now_value);
37
+ if (RCL_RET_OK != ret) {
38
+ throw std::runtime_error("failed to get current time");
39
+ }
40
+ rcl_time_point_t rcl_entry;
41
+ rcl_entry.nanoseconds = now_value;
42
+ rcl_entry.clock_type = clock->type;
43
+
44
+ const typename ClockType::time_point chrono_entry = ClockType::now();
45
+
46
+ rcl_duration_t delta_t;
47
+ ret = rcl_difference_times(&rcl_entry, &until, &delta_t);
48
+
49
+ if (RCL_RET_OK != ret) {
50
+ throw std::runtime_error("failed to subtract times");
51
+ }
52
+
53
+ // Cast because system clock resolution is too big for nanoseconds on Windows
54
+ // & OSX
55
+ const typename ClockType::time_point chrono_until =
56
+ chrono_entry + std::chrono::duration_cast<typename ClockType::duration>(
57
+ std::chrono::nanoseconds(delta_t.nanoseconds));
58
+
59
+ std::unique_lock<std::mutex> lock(mutex_);
60
+ cv_.wait_until(lock, chrono_until, [this]() { return state_; });
61
+ }
62
+
63
+ void ClockEvent::wait_until_ros(rcl_clock_t* clock, rcl_time_point_t until) {
64
+ bool is_enabled;
65
+ rcl_ret_t ret = rcl_is_enabled_ros_time_override(clock, &is_enabled);
66
+ if (RCL_RET_OK != ret) {
67
+ throw std::runtime_error("failed to check if ros time override is enabled");
68
+ }
69
+
70
+ // Check if ROS time is enabled in C++ to avoid TOCTTOU with TimeSource
71
+ if (is_enabled) {
72
+ std::unique_lock<std::mutex> lock(mutex_);
73
+ // Caller must have setup a time jump callback to wake this event
74
+ cv_.wait(lock, [this]() { return state_; });
75
+ } else {
76
+ // ROS time not enabled is system time
77
+ wait_until<std::chrono::system_clock>(clock, until);
78
+ }
79
+ }
80
+
81
+ bool ClockEvent::is_set() {
82
+ std::unique_lock<std::mutex> lock(mutex_);
83
+ return state_;
84
+ }
85
+
86
+ void ClockEvent::set() {
87
+ {
88
+ std::unique_lock<std::mutex> lock(mutex_);
89
+ state_ = true;
90
+ }
91
+ cv_.notify_all();
92
+ }
93
+
94
+ void ClockEvent::clear() {
95
+ {
96
+ std::unique_lock<std::mutex> lock(mutex_);
97
+ state_ = false;
98
+ }
99
+ cv_.notify_all();
100
+ }
101
+
102
+ // Explicit instantiation
103
+ template void ClockEvent::wait_until<std::chrono::steady_clock>(
104
+ rcl_clock_t* clock, rcl_time_point_t until);
105
+ template void ClockEvent::wait_until<std::chrono::system_clock>(
106
+ rcl_clock_t* clock, rcl_time_point_t until);
107
+
108
+ enum class WaitType { Steady, System, Ros };
109
+
110
+ class ClockEventWaitWorker : public Napi::AsyncWorker {
111
+ public:
112
+ ClockEventWaitWorker(Napi::Env env, ClockEvent* event, rcl_clock_t* clock,
113
+ int64_t until, WaitType type)
114
+ : Napi::AsyncWorker(env),
115
+ event_(event),
116
+ clock_(clock),
117
+ until_(until),
118
+ type_(type),
119
+ deferred_(Napi::Promise::Deferred::New(env)) {}
120
+
121
+ ~ClockEventWaitWorker() {}
122
+
123
+ void Execute() override {
124
+ try {
125
+ rcl_time_point_t until_time_point;
126
+ until_time_point.nanoseconds = until_;
127
+ until_time_point.clock_type = clock_->type;
128
+
129
+ switch (type_) {
130
+ case WaitType::Ros:
131
+ event_->wait_until_ros(clock_, until_time_point);
132
+ break;
133
+ case WaitType::Steady:
134
+ event_->wait_until<std::chrono::steady_clock>(clock_,
135
+ until_time_point);
136
+ break;
137
+ case WaitType::System:
138
+ event_->wait_until<std::chrono::system_clock>(clock_,
139
+ until_time_point);
140
+ break;
141
+ }
142
+ } catch (const std::exception& e) {
143
+ SetError(e.what());
144
+ }
145
+ }
146
+
147
+ void OnOK() override { deferred_.Resolve(Env().Undefined()); }
148
+
149
+ void OnError(const Napi::Error& e) override { deferred_.Reject(e.Value()); }
150
+
151
+ Napi::Promise Promise() { return deferred_.Promise(); }
152
+
153
+ private:
154
+ ClockEvent* event_;
155
+ rcl_clock_t* clock_;
156
+ int64_t until_;
157
+ WaitType type_;
158
+ Napi::Promise::Deferred deferred_;
159
+ };
160
+
161
+ Napi::Value CreateClockEvent(const Napi::CallbackInfo& info) {
162
+ Napi::Env env = info.Env();
163
+ ClockEvent* event = new ClockEvent();
164
+ return RclHandle::NewInstance(env, event, nullptr, [](void* ptr) {
165
+ delete static_cast<ClockEvent*>(ptr);
166
+ });
167
+ }
168
+
169
+ Napi::Value ClockEventWaitUntilSteady(const Napi::CallbackInfo& info) {
170
+ Napi::Env env = info.Env();
171
+ RclHandle* event_handle = RclHandle::Unwrap(info[0].As<Napi::Object>());
172
+ ClockEvent* event = static_cast<ClockEvent*>(event_handle->ptr());
173
+
174
+ RclHandle* clock_handle = RclHandle::Unwrap(info[1].As<Napi::Object>());
175
+ rcl_clock_t* clock = static_cast<rcl_clock_t*>(clock_handle->ptr());
176
+
177
+ bool lossless;
178
+ int64_t until = info[2].As<Napi::BigInt>().Int64Value(&lossless);
179
+ if (!lossless) {
180
+ Napi::TypeError::New(env, "until value lost precision during conversion")
181
+ .ThrowAsJavaScriptException();
182
+ return env.Undefined();
183
+ }
184
+
185
+ auto worker =
186
+ new ClockEventWaitWorker(env, event, clock, until, WaitType::Steady);
187
+ worker->Queue();
188
+ return worker->Promise();
189
+ }
190
+
191
+ Napi::Value ClockEventWaitUntilSystem(const Napi::CallbackInfo& info) {
192
+ Napi::Env env = info.Env();
193
+ RclHandle* event_handle = RclHandle::Unwrap(info[0].As<Napi::Object>());
194
+ ClockEvent* event = static_cast<ClockEvent*>(event_handle->ptr());
195
+
196
+ RclHandle* clock_handle = RclHandle::Unwrap(info[1].As<Napi::Object>());
197
+ rcl_clock_t* clock = static_cast<rcl_clock_t*>(clock_handle->ptr());
198
+
199
+ bool lossless;
200
+ int64_t until = info[2].As<Napi::BigInt>().Int64Value(&lossless);
201
+ if (!lossless) {
202
+ Napi::TypeError::New(env, "until value lost precision during conversion")
203
+ .ThrowAsJavaScriptException();
204
+ return env.Undefined();
205
+ }
206
+
207
+ auto worker =
208
+ new ClockEventWaitWorker(env, event, clock, until, WaitType::System);
209
+ worker->Queue();
210
+ return worker->Promise();
211
+ }
212
+
213
+ Napi::Value ClockEventWaitUntilRos(const Napi::CallbackInfo& info) {
214
+ Napi::Env env = info.Env();
215
+ RclHandle* event_handle = RclHandle::Unwrap(info[0].As<Napi::Object>());
216
+ ClockEvent* event = static_cast<ClockEvent*>(event_handle->ptr());
217
+
218
+ RclHandle* clock_handle = RclHandle::Unwrap(info[1].As<Napi::Object>());
219
+ rcl_clock_t* clock = static_cast<rcl_clock_t*>(clock_handle->ptr());
220
+
221
+ bool lossless;
222
+ int64_t until = info[2].As<Napi::BigInt>().Int64Value(&lossless);
223
+ if (!lossless) {
224
+ Napi::TypeError::New(env, "until value lost precision during conversion")
225
+ .ThrowAsJavaScriptException();
226
+ return env.Undefined();
227
+ }
228
+
229
+ auto worker =
230
+ new ClockEventWaitWorker(env, event, clock, until, WaitType::Ros);
231
+ worker->Queue();
232
+ return worker->Promise();
233
+ }
234
+
235
+ Napi::Value ClockEventIsSet(const Napi::CallbackInfo& info) {
236
+ RclHandle* event_handle = RclHandle::Unwrap(info[0].As<Napi::Object>());
237
+ ClockEvent* event = static_cast<ClockEvent*>(event_handle->ptr());
238
+ return Napi::Boolean::New(info.Env(), event->is_set());
239
+ }
240
+
241
+ Napi::Value ClockEventSet(const Napi::CallbackInfo& info) {
242
+ RclHandle* event_handle = RclHandle::Unwrap(info[0].As<Napi::Object>());
243
+ ClockEvent* event = static_cast<ClockEvent*>(event_handle->ptr());
244
+ event->set();
245
+ return info.Env().Undefined();
246
+ }
247
+
248
+ Napi::Value ClockEventClear(const Napi::CallbackInfo& info) {
249
+ RclHandle* event_handle = RclHandle::Unwrap(info[0].As<Napi::Object>());
250
+ ClockEvent* event = static_cast<ClockEvent*>(event_handle->ptr());
251
+ event->clear();
252
+ return info.Env().Undefined();
253
+ }
254
+
255
+ void InitClockEventBindings(Napi::Env env, Napi::Object exports) {
256
+ exports.Set("createClockEvent", Napi::Function::New(env, CreateClockEvent));
257
+ exports.Set("clockEventWaitUntilSteady",
258
+ Napi::Function::New(env, ClockEventWaitUntilSteady));
259
+ exports.Set("clockEventWaitUntilSystem",
260
+ Napi::Function::New(env, ClockEventWaitUntilSystem));
261
+ exports.Set("clockEventWaitUntilRos",
262
+ Napi::Function::New(env, ClockEventWaitUntilRos));
263
+ exports.Set("clockEventIsSet", Napi::Function::New(env, ClockEventIsSet));
264
+ exports.Set("clockEventSet", Napi::Function::New(env, ClockEventSet));
265
+ exports.Set("clockEventClear", Napi::Function::New(env, ClockEventClear));
266
+ }
267
+
268
+ } // namespace rclnodejs
@@ -0,0 +1,62 @@
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
+ #ifndef SRC_CLOCK_EVENT_HPP_
16
+ #define SRC_CLOCK_EVENT_HPP_
17
+
18
+ #include <napi.h>
19
+ #include <rcl/time.h>
20
+
21
+ #include <condition_variable>
22
+ #include <memory>
23
+ #include <mutex>
24
+
25
+ namespace rclnodejs {
26
+
27
+ class ClockEvent {
28
+ public:
29
+ /// Wait until a time specified by a system or steady clock.
30
+ /// \param clock the clock to use for time synchronization with until
31
+ /// \param until this method will block until this time is reached.
32
+ template <typename ClockType>
33
+ void wait_until(rcl_clock_t* clock, rcl_time_point_t until);
34
+
35
+ /// Wait until a time specified by a ROS clock.
36
+ /// \warning The caller is responsible for creating a time jump callback
37
+ /// to set this event when the target ROS time is reached.
38
+ /// \param clock The clock to use for time synchronization.
39
+ /// \param until This method will block until this time is reached.
40
+ void wait_until_ros(rcl_clock_t* clock, rcl_time_point_t until);
41
+
42
+ /// Indicate if the ClockEvent is set.
43
+ /// \return True if the ClockEvent is set.
44
+ bool is_set();
45
+
46
+ /// Set the event.
47
+ void set();
48
+
49
+ /// Clear the event.
50
+ void clear();
51
+
52
+ private:
53
+ bool state_ = false;
54
+ std::mutex mutex_;
55
+ std::condition_variable cv_;
56
+ };
57
+
58
+ void InitClockEventBindings(Napi::Env env, Napi::Object exports);
59
+
60
+ } // namespace rclnodejs
61
+
62
+ #endif // SRC_CLOCK_EVENT_HPP_
package/src/macros.h CHANGED
@@ -23,8 +23,7 @@
23
23
  { \
24
24
  if (lhs op rhs) { \
25
25
  rcl_reset_error(); \
26
- Napi::Error::New(env, message) \
27
- .ThrowAsJavaScriptException(); \
26
+ Napi::Error::New(env, message).ThrowAsJavaScriptException(); \
28
27
  return env.Undefined(); \
29
28
  } \
30
29
  }
@@ -33,8 +32,7 @@
33
32
  { \
34
33
  if (lhs op rhs) { \
35
34
  rcl_reset_error(); \
36
- Napi::Error::New(env, message) \
37
- .ThrowAsJavaScriptException(); \
35
+ Napi::Error::New(env, message).ThrowAsJavaScriptException(); \
38
36
  } \
39
37
  }
40
38
 
@@ -317,9 +317,27 @@ Napi::Value ActionPublishStatus(const Napi::CallbackInfo& info) {
317
317
  rcl_action_get_goal_status_array(action_server, &status_message),
318
318
  RCL_RET_OK, rcl_get_error_string().str);
319
319
 
320
- THROW_ERROR_IF_NOT_EQUAL(
321
- rcl_action_publish_status(action_server, &status_message), RCL_RET_OK,
322
- rcl_get_error_string().str);
320
+ rcl_ret_t ret = rcl_action_publish_status(action_server, &status_message);
321
+
322
+ std::string publish_error_msg;
323
+ if (ret != RCL_RET_OK) {
324
+ publish_error_msg = rcl_get_error_string().str;
325
+ rcl_reset_error();
326
+ }
327
+
328
+ rcl_ret_t ret_fini = rcl_action_goal_status_array_fini(&status_message);
329
+
330
+ if (ret != RCL_RET_OK) {
331
+ Napi::Error::New(env, publish_error_msg).ThrowAsJavaScriptException();
332
+ return env.Undefined();
333
+ }
334
+
335
+ if (ret_fini != RCL_RET_OK) {
336
+ Napi::Error::New(env, rcl_get_error_string().str)
337
+ .ThrowAsJavaScriptException();
338
+ rcl_reset_error();
339
+ return env.Undefined();
340
+ }
323
341
 
324
342
  return env.Undefined();
325
343
  }
@@ -15,6 +15,11 @@
15
15
  #include "rcl_bindings.h"
16
16
 
17
17
  #include <node.h>
18
+ #include <rcl/arguments.h>
19
+ #include <rcl/rcl.h>
20
+
21
+ #include <rcpputils/scope_exit.hpp>
22
+
18
23
  #if ROS_VERSION >= 2006
19
24
  #include <rosidl_runtime_c/string_functions.h>
20
25
  #else
@@ -23,11 +28,64 @@
23
28
 
24
29
  #include <memory>
25
30
  #include <string>
31
+ #include <vector>
26
32
 
27
33
  #include "rcl_handle.h"
34
+ #include "rcl_utilities.h"
28
35
 
29
36
  namespace rclnodejs {
30
37
 
38
+ Napi::Value RemoveROSArgs(const Napi::CallbackInfo& info) {
39
+ Napi::Env env = info.Env();
40
+ Napi::Array jsArgv = info[0].As<Napi::Array>();
41
+ size_t argc = jsArgv.Length();
42
+ char** argv = AbstractArgsFromNapiArray(jsArgv);
43
+
44
+ rcl_allocator_t allocator = rcl_get_default_allocator();
45
+ rcl_arguments_t parsed_args = rcl_get_zero_initialized_arguments();
46
+
47
+ rcl_ret_t ret = rcl_parse_arguments(argc, const_cast<const char**>(argv),
48
+ allocator, &parsed_args);
49
+
50
+ if (RCL_RET_OK != ret) {
51
+ FreeArgs(argv, argc);
52
+ Napi::Error::New(env, "Failed to parse arguments")
53
+ .ThrowAsJavaScriptException();
54
+ return env.Undefined();
55
+ }
56
+
57
+ RCPPUTILS_SCOPE_EXIT({
58
+ rcl_ret_t fini_ret = rcl_arguments_fini(&parsed_args);
59
+ if (RCL_RET_OK != fini_ret) {
60
+ // Log error but don't throw since we might be already throwing
61
+ }
62
+ });
63
+
64
+ int nonros_argc = 0;
65
+ const char** nonros_argv = nullptr;
66
+
67
+ ret = rcl_remove_ros_arguments(const_cast<const char**>(argv), &parsed_args,
68
+ allocator, &nonros_argc, &nonros_argv);
69
+
70
+ if (RCL_RET_OK != ret) {
71
+ FreeArgs(argv, argc);
72
+ Napi::Error::New(env, "Failed to remove ROS arguments")
73
+ .ThrowAsJavaScriptException();
74
+ return env.Undefined();
75
+ }
76
+
77
+ RCPPUTILS_SCOPE_EXIT({ allocator.deallocate(nonros_argv, allocator.state); });
78
+
79
+ Napi::Array result = Napi::Array::New(env, nonros_argc);
80
+ for (int i = 0; i < nonros_argc; ++i) {
81
+ result.Set(i, Napi::String::New(env, nonros_argv[i]));
82
+ }
83
+
84
+ FreeArgs(argv, argc);
85
+
86
+ return result;
87
+ }
88
+
31
89
  Napi::Value InitString(const Napi::CallbackInfo& info) {
32
90
  Napi::Env env = info.Env();
33
91
 
@@ -110,6 +168,7 @@ Napi::Object InitBindings(Napi::Env env, Napi::Object exports) {
110
168
  Napi::Function::New(env, CreateArrayBufferFromAddress));
111
169
  exports.Set("createArrayBufferCleaner",
112
170
  Napi::Function::New(env, CreateArrayBufferCleaner));
171
+ exports.Set("removeROSArgs", Napi::Function::New(env, RemoveROSArgs));
113
172
  return exports;
114
173
  }
115
174
 
@@ -73,6 +73,11 @@ Napi::Value Init(const Napi::CallbackInfo& info) {
73
73
  rcl_init(argc, argc > 0 ? argv : nullptr, &init_options, context),
74
74
  rcl_get_error_string().str);
75
75
 
76
+ ThrowIfUnparsedROSArgs(env, jsArgv, context->global_arguments);
77
+ if (env.IsExceptionPending()) {
78
+ return env.Undefined();
79
+ }
80
+
76
81
  THROW_ERROR_IF_NOT_EQUAL(
77
82
  RCL_RET_OK, rcl_logging_configure(&context->global_arguments, &allocator),
78
83
  rcl_get_error_string().str);
@@ -33,6 +33,13 @@ typedef rcl_ret_t (*rcl_get_info_by_topic_func_t)(
33
33
  const char* topic_name, bool no_mangle,
34
34
  rcl_topic_endpoint_info_array_t* info_array);
35
35
 
36
+ #if ROS_VERSION > 2505
37
+ typedef rcl_ret_t (*rcl_get_info_by_service_func_t)(
38
+ const rcl_node_t* node, rcutils_allocator_t* allocator,
39
+ const char* service_name, bool no_mangle,
40
+ rcl_service_endpoint_info_array_t* info_array);
41
+ #endif // ROS_VERSION > 2505
42
+
36
43
  Napi::Value GetPublisherNamesAndTypesByNode(const Napi::CallbackInfo& info) {
37
44
  Napi::Env env = info.Env();
38
45
 
@@ -257,6 +264,66 @@ Napi::Value GetSubscriptionsInfoByTopic(const Napi::CallbackInfo& info) {
257
264
  "subscriptions", rcl_get_subscriptions_info_by_topic);
258
265
  }
259
266
 
267
+ #if ROS_VERSION > 2505
268
+ Napi::Value GetInfoByService(
269
+ Napi::Env env, rcl_node_t* node, const char* service_name, bool no_mangle,
270
+ const char* type, rcl_get_info_by_service_func_t rcl_get_info_by_service) {
271
+ rcutils_allocator_t allocator = rcutils_get_default_allocator();
272
+ rcl_service_endpoint_info_array_t info_array =
273
+ rcl_get_zero_initialized_service_endpoint_info_array();
274
+
275
+ RCPPUTILS_SCOPE_EXIT({
276
+ rcl_ret_t fini_ret =
277
+ rcl_service_endpoint_info_array_fini(&info_array, &allocator);
278
+ if (RCL_RET_OK != fini_ret) {
279
+ Napi::Error::New(env, rcl_get_error_string().str)
280
+ .ThrowAsJavaScriptException();
281
+ rcl_reset_error();
282
+ }
283
+ });
284
+
285
+ rcl_ret_t ret = rcl_get_info_by_service(node, &allocator, service_name,
286
+ no_mangle, &info_array);
287
+ if (RCL_RET_OK != ret) {
288
+ if (RCL_RET_UNSUPPORTED == ret) {
289
+ Napi::Error::New(
290
+ env, std::string("Failed to get information by service for ") + type +
291
+ ": function not supported by RMW_IMPLEMENTATION")
292
+ .ThrowAsJavaScriptException();
293
+ return env.Undefined();
294
+ }
295
+ Napi::Error::New(
296
+ env, std::string("Failed to get information by service for ") + type)
297
+ .ThrowAsJavaScriptException();
298
+ return env.Undefined();
299
+ }
300
+
301
+ return ConvertToJSServiceEndpointInfoList(env, &info_array);
302
+ }
303
+ #endif // ROS_VERSION > 2505
304
+
305
+ #if ROS_VERSION > 2505
306
+ Napi::Value GetClientsInfoByService(const Napi::CallbackInfo& info) {
307
+ RclHandle* node_handle = RclHandle::Unwrap(info[0].As<Napi::Object>());
308
+ rcl_node_t* node = reinterpret_cast<rcl_node_t*>(node_handle->ptr());
309
+ std::string service_name = info[1].As<Napi::String>().Utf8Value();
310
+ bool no_mangle = info[2].As<Napi::Boolean>();
311
+
312
+ return GetInfoByService(info.Env(), node, service_name.c_str(), no_mangle,
313
+ "clients", rcl_get_clients_info_by_service);
314
+ }
315
+
316
+ Napi::Value GetServersInfoByService(const Napi::CallbackInfo& info) {
317
+ RclHandle* node_handle = RclHandle::Unwrap(info[0].As<Napi::Object>());
318
+ rcl_node_t* node = reinterpret_cast<rcl_node_t*>(node_handle->ptr());
319
+ std::string service_name = info[1].As<Napi::String>().Utf8Value();
320
+ bool no_mangle = info[2].As<Napi::Boolean>();
321
+
322
+ return GetInfoByService(info.Env(), node, service_name.c_str(), no_mangle,
323
+ "servers", rcl_get_servers_info_by_service);
324
+ }
325
+ #endif // ROS_VERSION > 2505
326
+
260
327
  Napi::Object InitGraphBindings(Napi::Env env, Napi::Object exports) {
261
328
  exports.Set("getPublisherNamesAndTypesByNode",
262
329
  Napi::Function::New(env, GetPublisherNamesAndTypesByNode));
@@ -274,6 +341,12 @@ Napi::Object InitGraphBindings(Napi::Env env, Napi::Object exports) {
274
341
  Napi::Function::New(env, GetPublishersInfoByTopic));
275
342
  exports.Set("getSubscriptionsInfoByTopic",
276
343
  Napi::Function::New(env, GetSubscriptionsInfoByTopic));
344
+ #if ROS_VERSION > 2505
345
+ exports.Set("getClientsInfoByService",
346
+ Napi::Function::New(env, GetClientsInfoByService));
347
+ exports.Set("getServersInfoByService",
348
+ Napi::Function::New(env, GetServersInfoByService));
349
+ #endif // ROS_VERSION > 2505
277
350
  return exports;
278
351
  }
279
352