rclnodejs 1.8.3 → 1.9.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.
- package/README.md +46 -37
- package/index.js +39 -0
- package/lib/action/client.js +61 -3
- package/lib/action/server_goal_handle.js +26 -1
- package/lib/message_info.js +94 -0
- package/lib/node.js +260 -13
- package/lib/parameter_event_handler.js +566 -0
- package/lib/parameter_watcher.js +12 -12
- package/lib/qos_overriding_options.js +358 -0
- package/lib/subscription.js +38 -5
- package/lib/timer.js +3 -2
- package/lib/wait_for_message.js +111 -0
- package/package.json +7 -4
- package/prebuilds/linux-arm64/humble-jammy-arm64-rclnodejs.node +0 -0
- package/prebuilds/linux-arm64/jazzy-noble-arm64-rclnodejs.node +0 -0
- package/prebuilds/linux-arm64/kilted-noble-arm64-rclnodejs.node +0 -0
- package/prebuilds/linux-x64/humble-jammy-x64-rclnodejs.node +0 -0
- package/prebuilds/linux-x64/jazzy-noble-x64-rclnodejs.node +0 -0
- package/prebuilds/linux-x64/kilted-noble-x64-rclnodejs.node +0 -0
- package/scripts/run_asan_test.sh +118 -0
- package/src/executor.cpp +36 -2
- package/src/executor.h +11 -0
- package/src/rcl_action_client_bindings.cpp +70 -1
- package/src/rcl_context_bindings.cpp +3 -3
- package/src/rcl_graph_bindings.cpp +2 -2
- package/src/rcl_subscription_bindings.cpp +70 -2
- package/src/rcl_timer_bindings.cpp +21 -2
- package/src/rcl_utilities.cpp +2 -2
- package/tools/jsdoc/Makefile +5 -0
- package/tools/jsdoc/README.md +96 -0
- package/tools/jsdoc/build-index.js +610 -0
- package/tools/jsdoc/publish.js +854 -0
- package/tools/jsdoc/regenerate-published-docs.js +605 -0
- package/tools/jsdoc/static/fonts/OpenSans-Bold-webfont.eot +0 -0
- package/tools/jsdoc/static/fonts/OpenSans-Bold-webfont.svg +1830 -0
- package/tools/jsdoc/static/fonts/OpenSans-Bold-webfont.woff +0 -0
- package/tools/jsdoc/static/fonts/OpenSans-BoldItalic-webfont.eot +0 -0
- package/tools/jsdoc/static/fonts/OpenSans-BoldItalic-webfont.svg +1830 -0
- package/tools/jsdoc/static/fonts/OpenSans-BoldItalic-webfont.woff +0 -0
- package/tools/jsdoc/static/fonts/OpenSans-Italic-webfont.eot +0 -0
- package/tools/jsdoc/static/fonts/OpenSans-Italic-webfont.svg +1830 -0
- package/tools/jsdoc/static/fonts/OpenSans-Italic-webfont.woff +0 -0
- package/tools/jsdoc/static/fonts/OpenSans-Light-webfont.eot +0 -0
- package/tools/jsdoc/static/fonts/OpenSans-Light-webfont.svg +1831 -0
- package/tools/jsdoc/static/fonts/OpenSans-Light-webfont.woff +0 -0
- package/tools/jsdoc/static/fonts/OpenSans-LightItalic-webfont.eot +0 -0
- package/tools/jsdoc/static/fonts/OpenSans-LightItalic-webfont.svg +1835 -0
- package/tools/jsdoc/static/fonts/OpenSans-LightItalic-webfont.woff +0 -0
- package/tools/jsdoc/static/fonts/OpenSans-Regular-webfont.eot +0 -0
- package/tools/jsdoc/static/fonts/OpenSans-Regular-webfont.svg +1831 -0
- package/tools/jsdoc/static/fonts/OpenSans-Regular-webfont.woff +0 -0
- package/tools/jsdoc/static/scripts/linenumber.js +25 -0
- package/tools/jsdoc/static/scripts/prettify/Apache-License-2.0.txt +202 -0
- package/tools/jsdoc/static/scripts/prettify/lang-css.js +36 -0
- package/tools/jsdoc/static/scripts/prettify/prettify.js +738 -0
- package/tools/jsdoc/static/styles/jsdoc-default.css +1012 -0
- package/tools/jsdoc/static/styles/prettify-jsdoc.css +111 -0
- package/tools/jsdoc/static/styles/prettify-tomorrow.css +132 -0
- package/tools/jsdoc/tmpl/augments.tmpl +10 -0
- package/tools/jsdoc/tmpl/container.tmpl +193 -0
- package/tools/jsdoc/tmpl/details.tmpl +143 -0
- package/tools/jsdoc/tmpl/example.tmpl +2 -0
- package/tools/jsdoc/tmpl/examples.tmpl +13 -0
- package/tools/jsdoc/tmpl/exceptions.tmpl +17 -0
- package/tools/jsdoc/tmpl/layout.tmpl +83 -0
- package/tools/jsdoc/tmpl/mainpage.tmpl +163 -0
- package/tools/jsdoc/tmpl/members.tmpl +43 -0
- package/tools/jsdoc/tmpl/method.tmpl +124 -0
- package/tools/jsdoc/tmpl/params.tmpl +133 -0
- package/tools/jsdoc/tmpl/properties.tmpl +110 -0
- package/tools/jsdoc/tmpl/returns.tmpl +12 -0
- package/tools/jsdoc/tmpl/source.tmpl +8 -0
- package/tools/jsdoc/tmpl/tutorial.tmpl +19 -0
- package/tools/jsdoc/tmpl/type.tmpl +7 -0
- package/types/action_client.d.ts +8 -0
- package/types/index.d.ts +34 -0
- package/types/message_info.d.ts +72 -0
- package/types/node.d.ts +90 -5
- package/types/parameter_event_handler.d.ts +150 -0
- package/types/qos.d.ts +55 -0
- package/types/subscription.d.ts +14 -2
- package/types/timer.d.ts +3 -2
- package/test_data_integrity.js +0 -108
- package/test_repro_exact.js +0 -57
- package/test_repro_hz.js +0 -86
- package/test_repro_pub.js +0 -36
- package/test_repro_stress.js +0 -83
- package/test_repro_sub.js +0 -64
- package/test_xproc_data.js +0 -64
- package/types/interfaces.d.ts +0 -8895
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Run tests with AddressSanitizer (ASan/LSan) to detect memory leaks and errors
|
|
3
|
+
# in the native N-API addon.
|
|
4
|
+
#
|
|
5
|
+
# Requires: ROS2 sourced, g++ with libasan
|
|
6
|
+
#
|
|
7
|
+
# Usage:
|
|
8
|
+
# bash scripts/run_asan_test.sh # rebuild + run all tests
|
|
9
|
+
# bash scripts/run_asan_test.sh test/test-node.js # rebuild + run specific test(s)
|
|
10
|
+
# bash scripts/run_asan_test.sh --no-build test/test-node.js # skip rebuild
|
|
11
|
+
# bash scripts/run_asan_test.sh --exclude test/test-serialization.js # exclude specific test(s)
|
|
12
|
+
|
|
13
|
+
set -e
|
|
14
|
+
|
|
15
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
16
|
+
PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
17
|
+
cd "$PROJECT_DIR"
|
|
18
|
+
|
|
19
|
+
# Parse flags
|
|
20
|
+
DO_BUILD=1
|
|
21
|
+
ARGS=()
|
|
22
|
+
EXCLUDES=()
|
|
23
|
+
NEXT_IS_EXCLUDE=0
|
|
24
|
+
for arg in "$@"; do
|
|
25
|
+
if [[ $NEXT_IS_EXCLUDE -eq 1 ]]; then
|
|
26
|
+
EXCLUDES+=("$arg")
|
|
27
|
+
NEXT_IS_EXCLUDE=0
|
|
28
|
+
elif [[ "$arg" == "--no-build" ]]; then
|
|
29
|
+
DO_BUILD=0
|
|
30
|
+
elif [[ "$arg" == "--exclude" ]]; then
|
|
31
|
+
NEXT_IS_EXCLUDE=1
|
|
32
|
+
else
|
|
33
|
+
ARGS+=("$arg")
|
|
34
|
+
fi
|
|
35
|
+
done
|
|
36
|
+
|
|
37
|
+
if [[ $NEXT_IS_EXCLUDE -eq 1 ]]; then
|
|
38
|
+
echo "Error: --exclude requires a test file argument"
|
|
39
|
+
echo "Usage: bash scripts/run_asan_test.sh --exclude test/test-foo.js"
|
|
40
|
+
exit 1
|
|
41
|
+
fi
|
|
42
|
+
|
|
43
|
+
# Step 1: Build with ASan
|
|
44
|
+
if [[ $DO_BUILD -eq 1 ]]; then
|
|
45
|
+
echo "=== Building with AddressSanitizer ==="
|
|
46
|
+
CXXFLAGS="-fsanitize=address" npx node-gyp -j 16 rebuild --debug
|
|
47
|
+
fi
|
|
48
|
+
|
|
49
|
+
# Step 2: Locate libasan
|
|
50
|
+
LIBASAN=$(g++ -print-file-name=libasan.so)
|
|
51
|
+
if [[ "$LIBASAN" == "libasan.so" ]]; then
|
|
52
|
+
# g++ returned just the name, try to find the full path
|
|
53
|
+
LIBASAN=$(find /usr/lib* -name "libasan.so*" -type f 2>/dev/null | head -1)
|
|
54
|
+
fi
|
|
55
|
+
|
|
56
|
+
if [[ -z "$LIBASAN" || ! -f "$LIBASAN" ]]; then
|
|
57
|
+
echo "Warning: Could not find libasan.so, proceeding without LD_PRELOAD"
|
|
58
|
+
echo "If you see 'ASan not the first DSO' errors, install libasan: sudo apt install libasan6"
|
|
59
|
+
LIBASAN=""
|
|
60
|
+
fi
|
|
61
|
+
|
|
62
|
+
# Step 3: Set up environment
|
|
63
|
+
export LSAN_OPTIONS="suppressions=$PROJECT_DIR/suppr.txt"
|
|
64
|
+
if [[ -n "$LIBASAN" ]]; then
|
|
65
|
+
export LD_PRELOAD="$LIBASAN"
|
|
66
|
+
fi
|
|
67
|
+
|
|
68
|
+
# Step 3.5: Temporarily hide prebuilds so the debug build is loaded
|
|
69
|
+
PREBUILDS_DIR="$PROJECT_DIR/prebuilds"
|
|
70
|
+
PREBUILDS_BAK="$PROJECT_DIR/.prebuilds_asan_bak"
|
|
71
|
+
MOVED_PREBUILDS=0
|
|
72
|
+
if [[ -d "$PREBUILDS_DIR" ]]; then
|
|
73
|
+
if [[ -d "$PREBUILDS_BAK" ]]; then
|
|
74
|
+
echo "Error: $PREBUILDS_BAK already exists (previous interrupted run?)."
|
|
75
|
+
echo "Please remove it manually and retry: rm -rf $PREBUILDS_BAK"
|
|
76
|
+
exit 1
|
|
77
|
+
fi
|
|
78
|
+
mv "$PREBUILDS_DIR" "$PREBUILDS_BAK"
|
|
79
|
+
MOVED_PREBUILDS=1
|
|
80
|
+
fi
|
|
81
|
+
|
|
82
|
+
# Restore prebuilds on exit (even on failure)
|
|
83
|
+
cleanup() {
|
|
84
|
+
if [[ $MOVED_PREBUILDS -eq 1 && -d "$PREBUILDS_BAK" ]]; then
|
|
85
|
+
mv "$PREBUILDS_BAK" "$PREBUILDS_DIR"
|
|
86
|
+
fi
|
|
87
|
+
}
|
|
88
|
+
trap cleanup EXIT
|
|
89
|
+
|
|
90
|
+
# Step 4: Build mocha exclude args from --exclude flags, blocklist.json, and asan_blocklist.json
|
|
91
|
+
MOCHA_EXCLUDES=()
|
|
92
|
+
for exc in "${EXCLUDES[@]}"; do
|
|
93
|
+
MOCHA_EXCLUDES+=(--ignore "$exc")
|
|
94
|
+
done
|
|
95
|
+
|
|
96
|
+
# Auto-exclude tests from blocklist.json (Linux entries) and asan_blocklist.json
|
|
97
|
+
for blocklist in "$PROJECT_DIR/test/blocklist.json" "$PROJECT_DIR/test/asan_blocklist.json"; do
|
|
98
|
+
if [[ -f "$blocklist" ]]; then
|
|
99
|
+
while IFS= read -r test_file; do
|
|
100
|
+
MOCHA_EXCLUDES+=(--ignore "test/$test_file")
|
|
101
|
+
done < <(node -e "
|
|
102
|
+
const bl = require('$blocklist');
|
|
103
|
+
const entries = bl.Linux || bl;
|
|
104
|
+
if (Array.isArray(entries)) entries.forEach(t => console.log(t));
|
|
105
|
+
" 2>/dev/null)
|
|
106
|
+
fi
|
|
107
|
+
done
|
|
108
|
+
|
|
109
|
+
# Step 5: Run tests
|
|
110
|
+
if [[ ${#ARGS[@]} -gt 0 ]]; then
|
|
111
|
+
echo "=== Running ASan test: ${ARGS[*]} ==="
|
|
112
|
+
node --expose-gc node_modules/.bin/mocha -r test/gc-on-exit.js "${MOCHA_EXCLUDES[@]}" "${ARGS[@]}"
|
|
113
|
+
else
|
|
114
|
+
echo "=== Running all ASan tests ==="
|
|
115
|
+
node --expose-gc node_modules/.bin/mocha -r test/gc-on-exit.js "${MOCHA_EXCLUDES[@]}" 'test/test-*.js'
|
|
116
|
+
fi
|
|
117
|
+
|
|
118
|
+
echo "=== ASan test complete ==="
|
package/src/executor.cpp
CHANGED
|
@@ -48,7 +48,8 @@ Executor::Executor(Napi::Env env, HandleManager* handle_manager,
|
|
|
48
48
|
handle_manager_(handle_manager),
|
|
49
49
|
delegate_(delegate),
|
|
50
50
|
context_(nullptr),
|
|
51
|
-
env_(env)
|
|
51
|
+
env_(env),
|
|
52
|
+
work_pending_(false) {
|
|
52
53
|
running_.store(false);
|
|
53
54
|
}
|
|
54
55
|
|
|
@@ -105,6 +106,8 @@ void Executor::Stop() {
|
|
|
105
106
|
// Stop thread first, and then uv_close
|
|
106
107
|
// Make sure async_ is not used anymore
|
|
107
108
|
running_.store(false);
|
|
109
|
+
// Wake the background thread in case it is waiting on the condvar.
|
|
110
|
+
work_done_cv_.notify_all();
|
|
108
111
|
handle_manager_->StopWaitingHandles();
|
|
109
112
|
uv_thread_join(&background_thread_);
|
|
110
113
|
|
|
@@ -116,7 +119,7 @@ void Executor::Stop() {
|
|
|
116
119
|
// Important Notice:
|
|
117
120
|
// This might be called after Executor::~Executor()
|
|
118
121
|
// Don't free Executor::async_ in Executor's dtor
|
|
119
|
-
delete async;
|
|
122
|
+
delete reinterpret_cast<uv_async_t*>(async);
|
|
120
123
|
handle_closed = true;
|
|
121
124
|
});
|
|
122
125
|
while (!handle_closed) uv_run(uv_default_loop(), UV_RUN_ONCE);
|
|
@@ -133,6 +136,21 @@ bool Executor::IsMainThread() {
|
|
|
133
136
|
|
|
134
137
|
void Executor::DoWork(uv_async_t* handle) {
|
|
135
138
|
Executor* executor = reinterpret_cast<Executor*>(handle->data);
|
|
139
|
+
|
|
140
|
+
// RAII guard: always clear work_pending_ and notify the background thread,
|
|
141
|
+
// even if ExecuteReadyHandles() throws (e.g. from N-API callbacks).
|
|
142
|
+
// Without this, the background thread would block forever on work_done_cv_.
|
|
143
|
+
struct WorkDoneGuard {
|
|
144
|
+
Executor* exec;
|
|
145
|
+
~WorkDoneGuard() {
|
|
146
|
+
{
|
|
147
|
+
std::lock_guard<std::mutex> lock(exec->work_done_mutex_);
|
|
148
|
+
exec->work_pending_ = false;
|
|
149
|
+
}
|
|
150
|
+
exec->work_done_cv_.notify_one();
|
|
151
|
+
}
|
|
152
|
+
} guard{executor};
|
|
153
|
+
|
|
136
154
|
executor->ExecuteReadyHandles();
|
|
137
155
|
}
|
|
138
156
|
|
|
@@ -159,7 +177,23 @@ void Executor::Run(void* arg) {
|
|
|
159
177
|
|
|
160
178
|
if (!uv_is_closing(reinterpret_cast<uv_handle_t*>(executor->async_)) &&
|
|
161
179
|
handle_manager->ready_handles_count() > 0) {
|
|
180
|
+
// Tell the main thread there is work to do, then wait for it to
|
|
181
|
+
// finish before re-entering rcl_wait. This prevents a data race
|
|
182
|
+
// where the background thread holds subscriptions in the wait set
|
|
183
|
+
// while the main thread modifies their state (e.g. content filter).
|
|
184
|
+
{
|
|
185
|
+
std::lock_guard<std::mutex> lock(executor->work_done_mutex_);
|
|
186
|
+
executor->work_pending_ = true;
|
|
187
|
+
}
|
|
162
188
|
uv_async_send(executor->async_);
|
|
189
|
+
|
|
190
|
+
// Wait until DoWork() signals completion.
|
|
191
|
+
{
|
|
192
|
+
std::unique_lock<std::mutex> lock(executor->work_done_mutex_);
|
|
193
|
+
executor->work_done_cv_.wait(lock, [executor] {
|
|
194
|
+
return !executor->work_pending_ || !executor->running_.load();
|
|
195
|
+
});
|
|
196
|
+
}
|
|
163
197
|
}
|
|
164
198
|
}
|
|
165
199
|
|
package/src/executor.h
CHANGED
|
@@ -20,7 +20,9 @@
|
|
|
20
20
|
#include <uv.h>
|
|
21
21
|
|
|
22
22
|
#include <atomic>
|
|
23
|
+
#include <condition_variable>
|
|
23
24
|
#include <exception>
|
|
25
|
+
#include <mutex>
|
|
24
26
|
#include <vector>
|
|
25
27
|
|
|
26
28
|
#include "rcl_handle.h"
|
|
@@ -72,6 +74,15 @@ class Executor {
|
|
|
72
74
|
Napi::Env env_;
|
|
73
75
|
|
|
74
76
|
std::atomic_bool running_;
|
|
77
|
+
|
|
78
|
+
// Synchronization: the background thread waits after uv_async_send until
|
|
79
|
+
// the main thread finishes ExecuteReadyHandles. This prevents the
|
|
80
|
+
// background thread from re-entering rcl_wait (which holds a reference to
|
|
81
|
+
// subscriptions) while the main thread modifies subscription state (e.g.
|
|
82
|
+
// content filter changes).
|
|
83
|
+
std::mutex work_done_mutex_;
|
|
84
|
+
std::condition_variable work_done_cv_;
|
|
85
|
+
bool work_pending_; // true while the main thread is processing handles
|
|
75
86
|
};
|
|
76
87
|
|
|
77
88
|
} // namespace rclnodejs
|
|
@@ -250,6 +250,67 @@ Napi::Value ActionSendCancelRequest(const Napi::CallbackInfo& info) {
|
|
|
250
250
|
return Napi::Number::New(env, static_cast<double>(sequence_number));
|
|
251
251
|
}
|
|
252
252
|
|
|
253
|
+
#if ROS_VERSION >= 5000 // ROS2 Rolling
|
|
254
|
+
Napi::Value ActionConfigureFeedbackSubFilterAddGoalId(
|
|
255
|
+
const Napi::CallbackInfo& info) {
|
|
256
|
+
Napi::Env env = info.Env();
|
|
257
|
+
|
|
258
|
+
RclHandle* action_client_handle =
|
|
259
|
+
RclHandle::Unwrap(info[0].As<Napi::Object>());
|
|
260
|
+
rcl_action_client_t* action_client =
|
|
261
|
+
reinterpret_cast<rcl_action_client_t*>(action_client_handle->ptr());
|
|
262
|
+
|
|
263
|
+
auto goal_id_buffer = info[1].As<Napi::Buffer<uint8_t>>();
|
|
264
|
+
const uint8_t* goal_id_array = goal_id_buffer.Data();
|
|
265
|
+
size_t goal_id_size = goal_id_buffer.Length();
|
|
266
|
+
|
|
267
|
+
rcl_ret_t ret =
|
|
268
|
+
rcl_action_client_configure_feedback_subscription_filter_add_goal_id(
|
|
269
|
+
action_client, goal_id_array, goal_id_size);
|
|
270
|
+
|
|
271
|
+
if (RCL_RET_OK != ret) {
|
|
272
|
+
std::string error_text{
|
|
273
|
+
"Failed to add goal id to feedback subscription content filter: "};
|
|
274
|
+
error_text += rcl_get_error_string().str;
|
|
275
|
+
rcl_reset_error();
|
|
276
|
+
Napi::Error::New(env, error_text).ThrowAsJavaScriptException();
|
|
277
|
+
return Napi::Boolean::New(env, false);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
return Napi::Boolean::New(env, true);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
Napi::Value ActionConfigureFeedbackSubFilterRemoveGoalId(
|
|
284
|
+
const Napi::CallbackInfo& info) {
|
|
285
|
+
Napi::Env env = info.Env();
|
|
286
|
+
|
|
287
|
+
RclHandle* action_client_handle =
|
|
288
|
+
RclHandle::Unwrap(info[0].As<Napi::Object>());
|
|
289
|
+
rcl_action_client_t* action_client =
|
|
290
|
+
reinterpret_cast<rcl_action_client_t*>(action_client_handle->ptr());
|
|
291
|
+
|
|
292
|
+
auto goal_id_buffer = info[1].As<Napi::Buffer<uint8_t>>();
|
|
293
|
+
const uint8_t* goal_id_array = goal_id_buffer.Data();
|
|
294
|
+
size_t goal_id_size = goal_id_buffer.Length();
|
|
295
|
+
|
|
296
|
+
rcl_ret_t ret =
|
|
297
|
+
rcl_action_client_configure_feedback_subscription_filter_remove_goal_id(
|
|
298
|
+
action_client, goal_id_array, goal_id_size);
|
|
299
|
+
|
|
300
|
+
if (RCL_RET_OK != ret) {
|
|
301
|
+
std::string error_text{
|
|
302
|
+
"Failed to remove goal id from feedback subscription content "
|
|
303
|
+
"filter: "};
|
|
304
|
+
error_text += rcl_get_error_string().str;
|
|
305
|
+
rcl_reset_error();
|
|
306
|
+
Napi::Error::New(env, error_text).ThrowAsJavaScriptException();
|
|
307
|
+
return Napi::Boolean::New(env, false);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
return Napi::Boolean::New(env, true);
|
|
311
|
+
}
|
|
312
|
+
#endif // ROS_VERSION >= 5000
|
|
313
|
+
|
|
253
314
|
#if ROS_VERSION >= 2505 // ROS2 >= Kilted
|
|
254
315
|
Napi::Value ConfigureActionClientIntrospection(const Napi::CallbackInfo& info) {
|
|
255
316
|
Napi::Env env = info.Env();
|
|
@@ -307,7 +368,15 @@ Napi::Object InitActionClientBindings(Napi::Env env, Napi::Object exports) {
|
|
|
307
368
|
#if ROS_VERSION >= 2505 // ROS2 >= Kilted
|
|
308
369
|
exports.Set("configureActionClientIntrospection",
|
|
309
370
|
Napi::Function::New(env, ConfigureActionClientIntrospection));
|
|
310
|
-
#endif
|
|
371
|
+
#endif // ROS_VERSION >= 2505
|
|
372
|
+
#if ROS_VERSION >= 5000 // ROS2 Rolling
|
|
373
|
+
exports.Set(
|
|
374
|
+
"actionConfigureFeedbackSubFilterAddGoalId",
|
|
375
|
+
Napi::Function::New(env, ActionConfigureFeedbackSubFilterAddGoalId));
|
|
376
|
+
exports.Set(
|
|
377
|
+
"actionConfigureFeedbackSubFilterRemoveGoalId",
|
|
378
|
+
Napi::Function::New(env, ActionConfigureFeedbackSubFilterRemoveGoalId));
|
|
379
|
+
#endif // ROS_VERSION >= 5000
|
|
311
380
|
return exports;
|
|
312
381
|
}
|
|
313
382
|
|
|
@@ -17,10 +17,10 @@
|
|
|
17
17
|
#include <rcl/logging.h>
|
|
18
18
|
#include <rcl/rcl.h>
|
|
19
19
|
|
|
20
|
-
#include <rcpputils/scope_exit.hpp>
|
|
21
|
-
// NOLINTNEXTLINE
|
|
22
20
|
#include <string>
|
|
23
21
|
|
|
22
|
+
#include <rcpputils/scope_exit.hpp>
|
|
23
|
+
|
|
24
24
|
#include "macros.h"
|
|
25
25
|
#include "rcl_handle.h"
|
|
26
26
|
#include "rcl_utilities.h"
|
|
@@ -54,6 +54,7 @@ Napi::Value Init(const Napi::CallbackInfo& info) {
|
|
|
54
54
|
Napi::Array jsArgv = info[1].As<Napi::Array>();
|
|
55
55
|
size_t argc = jsArgv.Length();
|
|
56
56
|
char** argv = AbstractArgsFromNapiArray(jsArgv);
|
|
57
|
+
RCPPUTILS_SCOPE_EXIT({ FreeArgs(argv, argc); });
|
|
57
58
|
|
|
58
59
|
// Set up the domain id.
|
|
59
60
|
size_t domain_id = RCL_DEFAULT_DOMAIN_ID;
|
|
@@ -82,7 +83,6 @@ Napi::Value Init(const Napi::CallbackInfo& info) {
|
|
|
82
83
|
RCL_RET_OK, rcl_logging_configure(&context->global_arguments, &allocator),
|
|
83
84
|
rcl_get_error_string().str);
|
|
84
85
|
|
|
85
|
-
RCPPUTILS_SCOPE_EXIT({ FreeArgs(argv, argc); });
|
|
86
86
|
return env.Undefined();
|
|
87
87
|
}
|
|
88
88
|
|
|
@@ -18,10 +18,10 @@
|
|
|
18
18
|
#include <rcl/graph.h>
|
|
19
19
|
#include <rcl/rcl.h>
|
|
20
20
|
|
|
21
|
-
#include <rcpputils/scope_exit.hpp>
|
|
22
|
-
// NOLINTNEXTLINE
|
|
23
21
|
#include <string>
|
|
24
22
|
|
|
23
|
+
#include <rcpputils/scope_exit.hpp>
|
|
24
|
+
|
|
25
25
|
#include "macros.h"
|
|
26
26
|
#include "rcl_handle.h"
|
|
27
27
|
#include "rcl_utilities.h"
|
|
@@ -16,13 +16,15 @@
|
|
|
16
16
|
|
|
17
17
|
#include <rcl/error_handling.h>
|
|
18
18
|
#include <rcl/rcl.h>
|
|
19
|
+
#include <rcl/subscription.h>
|
|
20
|
+
#include <rmw/types.h>
|
|
19
21
|
|
|
20
22
|
#include <cstdio>
|
|
21
23
|
#include <memory>
|
|
22
|
-
#include <rcpputils/scope_exit.hpp>
|
|
23
|
-
// NOLINTNEXTLINE
|
|
24
24
|
#include <string>
|
|
25
25
|
|
|
26
|
+
#include <rcpputils/scope_exit.hpp>
|
|
27
|
+
|
|
26
28
|
#include "macros.h"
|
|
27
29
|
#include "rcl_handle.h"
|
|
28
30
|
#include "rcl_utilities.h"
|
|
@@ -53,6 +55,53 @@ Napi::Value RclTake(const Napi::CallbackInfo& info) {
|
|
|
53
55
|
return env.Undefined();
|
|
54
56
|
}
|
|
55
57
|
|
|
58
|
+
Napi::Value RclTakeWithInfo(const Napi::CallbackInfo& info) {
|
|
59
|
+
Napi::Env env = info.Env();
|
|
60
|
+
|
|
61
|
+
RclHandle* subscription_handle =
|
|
62
|
+
RclHandle::Unwrap(info[0].As<Napi::Object>());
|
|
63
|
+
rcl_subscription_t* subscription =
|
|
64
|
+
reinterpret_cast<rcl_subscription_t*>(subscription_handle->ptr());
|
|
65
|
+
void* msg_taken = info[1].As<Napi::Buffer<char>>().Data();
|
|
66
|
+
|
|
67
|
+
rmw_message_info_t message_info = rmw_get_zero_initialized_message_info();
|
|
68
|
+
rcl_ret_t ret = rcl_take(subscription, msg_taken, &message_info, nullptr);
|
|
69
|
+
|
|
70
|
+
if (ret != RCL_RET_OK && ret != RCL_RET_SUBSCRIPTION_TAKE_FAILED) {
|
|
71
|
+
std::string error_string = rcl_get_error_string().str;
|
|
72
|
+
rcl_reset_error();
|
|
73
|
+
Napi::Error::New(env, error_string).ThrowAsJavaScriptException();
|
|
74
|
+
return env.Undefined();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (ret == RCL_RET_SUBSCRIPTION_TAKE_FAILED) {
|
|
78
|
+
return env.Undefined();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Build JS object with message info fields
|
|
82
|
+
Napi::Object js_info = Napi::Object::New(env);
|
|
83
|
+
js_info.Set("source_timestamp",
|
|
84
|
+
Napi::BigInt::New(env, message_info.source_timestamp));
|
|
85
|
+
js_info.Set("received_timestamp",
|
|
86
|
+
Napi::BigInt::New(env, message_info.received_timestamp));
|
|
87
|
+
js_info.Set(
|
|
88
|
+
"publication_sequence_number",
|
|
89
|
+
Napi::BigInt::New(
|
|
90
|
+
env, static_cast<int64_t>(message_info.publication_sequence_number)));
|
|
91
|
+
js_info.Set(
|
|
92
|
+
"reception_sequence_number",
|
|
93
|
+
Napi::BigInt::New(
|
|
94
|
+
env, static_cast<int64_t>(message_info.reception_sequence_number)));
|
|
95
|
+
|
|
96
|
+
// Publisher GID as Buffer
|
|
97
|
+
auto gid_buf =
|
|
98
|
+
Napi::Buffer<uint8_t>::Copy(env, message_info.publisher_gid.data,
|
|
99
|
+
sizeof(message_info.publisher_gid.data));
|
|
100
|
+
js_info.Set("publisher_gid", gid_buf);
|
|
101
|
+
|
|
102
|
+
return js_info;
|
|
103
|
+
}
|
|
104
|
+
|
|
56
105
|
Napi::Value CreateSubscription(const Napi::CallbackInfo& info) {
|
|
57
106
|
Napi::Env env = info.Env();
|
|
58
107
|
|
|
@@ -216,6 +265,20 @@ Napi::Value GetSubscriptionTopic(const Napi::CallbackInfo& info) {
|
|
|
216
265
|
return Napi::String::New(env, topic);
|
|
217
266
|
}
|
|
218
267
|
|
|
268
|
+
#if ROS_VERSION > 2505 // Rolling only
|
|
269
|
+
Napi::Value IsContentFilterSupported(const Napi::CallbackInfo& info) {
|
|
270
|
+
Napi::Env env = info.Env();
|
|
271
|
+
|
|
272
|
+
RclHandle* subscription_handle =
|
|
273
|
+
RclHandle::Unwrap(info[0].As<Napi::Object>());
|
|
274
|
+
rcl_subscription_t* subscription =
|
|
275
|
+
reinterpret_cast<rcl_subscription_t*>(subscription_handle->ptr());
|
|
276
|
+
|
|
277
|
+
bool is_supported = rcl_subscription_is_cft_supported(subscription);
|
|
278
|
+
return Napi::Boolean::New(env, is_supported);
|
|
279
|
+
}
|
|
280
|
+
#endif
|
|
281
|
+
|
|
219
282
|
Napi::Value HasContentFilter(const Napi::CallbackInfo& info) {
|
|
220
283
|
Napi::Env env = info.Env();
|
|
221
284
|
|
|
@@ -422,11 +485,16 @@ Napi::Value GetPublisherCount(const Napi::CallbackInfo& info) {
|
|
|
422
485
|
|
|
423
486
|
Napi::Object InitSubscriptionBindings(Napi::Env env, Napi::Object exports) {
|
|
424
487
|
exports.Set("rclTake", Napi::Function::New(env, RclTake));
|
|
488
|
+
exports.Set("rclTakeWithInfo", Napi::Function::New(env, RclTakeWithInfo));
|
|
425
489
|
exports.Set("createSubscription",
|
|
426
490
|
Napi::Function::New(env, CreateSubscription));
|
|
427
491
|
exports.Set("rclTakeRaw", Napi::Function::New(env, RclTakeRaw));
|
|
428
492
|
exports.Set("getSubscriptionTopic",
|
|
429
493
|
Napi::Function::New(env, GetSubscriptionTopic));
|
|
494
|
+
#if ROS_VERSION > 2505 // Rolling only
|
|
495
|
+
exports.Set("isContentFilterSupported",
|
|
496
|
+
Napi::Function::New(env, IsContentFilterSupported));
|
|
497
|
+
#endif
|
|
430
498
|
exports.Set("hasContentFilter", Napi::Function::New(env, HasContentFilter));
|
|
431
499
|
exports.Set("setContentFilter", Napi::Function::New(env, SetContentFilter));
|
|
432
500
|
exports.Set("getContentFilter", Napi::Function::New(env, GetContentFilter));
|
|
@@ -78,6 +78,15 @@ Napi::Value CreateTimer(const Napi::CallbackInfo& info) {
|
|
|
78
78
|
|
|
79
79
|
bool lossless;
|
|
80
80
|
int64_t period_nsec = info[2].As<Napi::BigInt>().Int64Value(&lossless);
|
|
81
|
+
bool autostart = true;
|
|
82
|
+
if (info.Length() > 3) {
|
|
83
|
+
if (!info[3].IsBoolean()) {
|
|
84
|
+
Napi::TypeError::New(env, "Timer autostart must be a boolean")
|
|
85
|
+
.ThrowAsJavaScriptException();
|
|
86
|
+
return env.Undefined();
|
|
87
|
+
}
|
|
88
|
+
autostart = info[3].As<Napi::Boolean>().Value();
|
|
89
|
+
}
|
|
81
90
|
rcl_timer_t* timer =
|
|
82
91
|
reinterpret_cast<rcl_timer_t*>(malloc(sizeof(rcl_timer_t)));
|
|
83
92
|
*timer = rcl_get_zero_initialized_timer();
|
|
@@ -85,8 +94,7 @@ Napi::Value CreateTimer(const Napi::CallbackInfo& info) {
|
|
|
85
94
|
#if ROS_VERSION > 2305 // After Iron.
|
|
86
95
|
{
|
|
87
96
|
rcl_ret_t ret = rcl_timer_init2(timer, clock, context, period_nsec, nullptr,
|
|
88
|
-
rcl_get_default_allocator(),
|
|
89
|
-
/*autostart=*/true);
|
|
97
|
+
rcl_get_default_allocator(), autostart);
|
|
90
98
|
if (RCL_RET_OK != ret) {
|
|
91
99
|
std::string error_msg = rcl_get_error_string().str;
|
|
92
100
|
rcl_reset_error();
|
|
@@ -106,6 +114,17 @@ Napi::Value CreateTimer(const Napi::CallbackInfo& info) {
|
|
|
106
114
|
Napi::Error::New(env, error_msg).ThrowAsJavaScriptException();
|
|
107
115
|
return env.Undefined();
|
|
108
116
|
}
|
|
117
|
+
if (!autostart) {
|
|
118
|
+
rcl_ret_t cancel_ret = rcl_timer_cancel(timer);
|
|
119
|
+
if (RCL_RET_OK != cancel_ret) {
|
|
120
|
+
std::string error_msg = rcl_get_error_string().str;
|
|
121
|
+
rcl_reset_error();
|
|
122
|
+
rcl_timer_fini(timer);
|
|
123
|
+
free(timer);
|
|
124
|
+
Napi::Error::New(env, error_msg).ThrowAsJavaScriptException();
|
|
125
|
+
return env.Undefined();
|
|
126
|
+
}
|
|
127
|
+
}
|
|
109
128
|
}
|
|
110
129
|
#endif
|
|
111
130
|
|
package/src/rcl_utilities.cpp
CHANGED
|
@@ -21,10 +21,10 @@
|
|
|
21
21
|
|
|
22
22
|
#include <cstdio>
|
|
23
23
|
#include <memory>
|
|
24
|
-
#include <rcpputils/scope_exit.hpp>
|
|
25
|
-
// NOLINTNEXTLINE
|
|
26
24
|
#include <string>
|
|
27
25
|
|
|
26
|
+
#include <rcpputils/scope_exit.hpp>
|
|
27
|
+
|
|
28
28
|
namespace {
|
|
29
29
|
|
|
30
30
|
const rmw_qos_profile_t* GetQoSProfileFromString(const std::string& profile) {
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# JSDoc Workflow
|
|
2
|
+
|
|
3
|
+
This directory contains the custom JSDoc template, the landing-page generator,
|
|
4
|
+
and the staging script used to prepare the docs content that is published to the
|
|
5
|
+
`gh-pages` branch.
|
|
6
|
+
|
|
7
|
+
## Commands
|
|
8
|
+
|
|
9
|
+
### `npm run docs`
|
|
10
|
+
|
|
11
|
+
Build local docs for the current workspace version.
|
|
12
|
+
|
|
13
|
+
Output:
|
|
14
|
+
|
|
15
|
+
- `docs/<current-version>/`
|
|
16
|
+
- `docs/index.html`
|
|
17
|
+
|
|
18
|
+
Use this to verify the docs for the version currently declared in
|
|
19
|
+
`package.json`.
|
|
20
|
+
|
|
21
|
+
### `npm run docs:gh-pages`
|
|
22
|
+
|
|
23
|
+
Stage the publishable docs tree under `build/gh-pages-docs/`.
|
|
24
|
+
|
|
25
|
+
Behavior:
|
|
26
|
+
|
|
27
|
+
- reads the currently published version set from `origin/gh-pages`
|
|
28
|
+
- preserves that published history
|
|
29
|
+
- regenerates docs for the current workspace version
|
|
30
|
+
- rebuilds the staged landing page index
|
|
31
|
+
|
|
32
|
+
This is the normal command to use for a new release.
|
|
33
|
+
|
|
34
|
+
If you delete `build/` and rerun `npm run docs:gh-pages`, the staged tree will
|
|
35
|
+
still contain all currently published versions. That command recreates
|
|
36
|
+
`build/gh-pages-docs/` by copying the published docs snapshot from
|
|
37
|
+
`origin/gh-pages`, then regenerating only the current workspace version.
|
|
38
|
+
|
|
39
|
+
### `npm run docs:gh-pages:full`
|
|
40
|
+
|
|
41
|
+
Fully rebuild the currently published docs history under
|
|
42
|
+
`build/gh-pages-docs/`.
|
|
43
|
+
|
|
44
|
+
Behavior:
|
|
45
|
+
|
|
46
|
+
- reads the published version set from `origin/gh-pages`
|
|
47
|
+
- rebuilds only those published versions from tags
|
|
48
|
+
- regenerates docs for the current workspace version
|
|
49
|
+
- rebuilds the staged landing page index
|
|
50
|
+
|
|
51
|
+
This does **not** rebuild docs for every historical `rclnodejs` tag. It only
|
|
52
|
+
rebuilds the subset that is actually published online.
|
|
53
|
+
|
|
54
|
+
## New Release Example
|
|
55
|
+
|
|
56
|
+
For a new release such as `1.9.0`:
|
|
57
|
+
|
|
58
|
+
1. Update `package.json` to `1.9.0`.
|
|
59
|
+
2. Run `npm run docs`.
|
|
60
|
+
3. Verify the local output in `docs/1.9.0/` and `docs/index.html`.
|
|
61
|
+
4. Run `npm run docs:gh-pages`.
|
|
62
|
+
5. Verify the staged output in:
|
|
63
|
+
- `build/gh-pages-docs/docs/1.9.0/`
|
|
64
|
+
- `build/gh-pages-docs/docs/index.html`
|
|
65
|
+
- `build/gh-pages-docs/.nojekyll`
|
|
66
|
+
6. Publish the contents of `build/gh-pages-docs/` to the `gh-pages` branch.
|
|
67
|
+
|
|
68
|
+
## Manual Landing Page Rebuild
|
|
69
|
+
|
|
70
|
+
If the staged docs tree already exists and you only want to rebuild
|
|
71
|
+
`build/gh-pages-docs/docs/index.html`, run `tools/jsdoc/build-index.js` against
|
|
72
|
+
that docs root and point it at the package metadata for the latest published
|
|
73
|
+
version.
|
|
74
|
+
|
|
75
|
+
Example for published version `1.8.0`:
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
mkdir -p build/gh-pages-docs/.tmp
|
|
79
|
+
git show 1.8.0:package.json > build/gh-pages-docs/.tmp/package-1.8.0.json
|
|
80
|
+
|
|
81
|
+
export RCLNODEJS_DOCS_ROOT="$PWD/build/gh-pages-docs/docs"
|
|
82
|
+
export RCLNODEJS_DOCS_INDEX_PATH="$PWD/build/gh-pages-docs/docs/index.html"
|
|
83
|
+
export RCLNODEJS_LOCAL_INDEX_PATH=''
|
|
84
|
+
export RCLNODEJS_PACKAGE_JSON_PATH="$PWD/build/gh-pages-docs/.tmp/package-1.8.0.json"
|
|
85
|
+
|
|
86
|
+
node tools/jsdoc/build-index.js
|
|
87
|
+
rm -rf build/gh-pages-docs/.tmp
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Notes
|
|
91
|
+
|
|
92
|
+
- The staged publish output keeps shared assets in `build/gh-pages-docs/docs/_static/`.
|
|
93
|
+
- `.nojekyll` must remain in the staged output because the published docs tree
|
|
94
|
+
uses an underscore-prefixed directory.
|
|
95
|
+
- The live docs index is the source of truth for which versions should remain
|
|
96
|
+
published.
|