rclnodejs 1.5.1 → 1.6.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 +25 -2
- package/binding.gyp +2 -0
- package/index.js +21 -4
- package/lib/action/client.js +1 -1
- package/lib/action/graph.js +1 -1
- package/lib/action/server.js +1 -1
- package/lib/action/server_goal_handle.js +1 -1
- package/lib/client.js +1 -1
- package/lib/clock.js +1 -1
- package/lib/context.js +1 -1
- package/lib/duration.js +1 -1
- package/lib/event_handler.js +1 -1
- package/lib/guard_condition.js +1 -1
- package/lib/lifecycle.js +1 -1
- package/lib/lifecycle_publisher.js +1 -1
- package/lib/logging.js +1 -1
- package/lib/message_serialization.js +171 -0
- package/lib/native_loader.js +173 -0
- package/lib/node.js +16 -1
- package/lib/parameter.js +4 -10
- package/lib/publisher.js +1 -1
- package/lib/serialization.js +1 -1
- package/lib/service.js +1 -1
- package/lib/subscription.js +17 -2
- package/lib/time.js +1 -1
- package/lib/time_source.js +1 -1
- package/lib/timer.js +1 -1
- package/lib/type_description_service.js +1 -1
- package/lib/utils.js +324 -0
- package/lib/validator.js +1 -1
- package/package.json +14 -18
- 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/rosidl_convertor/idl_convertor.js +3 -2
- package/rosidl_gen/deallocator.js +1 -1
- package/rosidl_gen/generate_worker.js +1 -1
- package/rosidl_gen/generator.json +1 -1
- package/rosidl_gen/idl_generator.js +11 -24
- package/rosidl_gen/index.js +1 -1
- package/rosidl_gen/primitive_types.js +2 -2
- package/rosidl_gen/templates/action-template.js +68 -0
- package/rosidl_gen/templates/message-template.js +1113 -0
- package/rosidl_gen/templates/service-event-template.js +31 -0
- package/rosidl_gen/templates/service-template.js +44 -0
- package/rosidl_parser/rosidl_parser.js +2 -2
- package/scripts/install.js +113 -0
- package/scripts/tag_prebuilds.js +70 -0
- package/src/addon.cpp +3 -0
- package/third_party/ref-napi/index.js +15 -0
- package/third_party/ref-napi/lib/ref.js +1696 -0
- package/third_party/ref-napi/src/ref_napi_bindings.cpp +736 -0
- package/third_party/ref-napi/src/ref_napi_bindings.h +26 -0
- package/types/index.d.ts +17 -0
- package/types/node.d.ts +16 -1
- package/rosidl_gen/templates/CMakeLists.dot +0 -40
- package/rosidl_gen/templates/action.dot +0 -50
- package/rosidl_gen/templates/message.dot +0 -851
- package/rosidl_gen/templates/package.dot +0 -16
- package/rosidl_gen/templates/service.dot +0 -26
- package/rosidl_gen/templates/service_event.dot +0 -10
package/README.md
CHANGED
|
@@ -31,6 +31,26 @@ npm i rclnodejs
|
|
|
31
31
|
|
|
32
32
|
- **Note:** to install rclnodejs from GitHub: add `"rclnodejs":"RobotWebTools/rclnodejs#<branch>"` to your `package.json` dependency section.
|
|
33
33
|
|
|
34
|
+
### Prebuilt Binaries
|
|
35
|
+
|
|
36
|
+
rclnodejs ships with prebuilt native binaries for common Linux configurations since `v1.5.2`, eliminating the need for compilation during installation. This significantly speeds up installation and reduces dependencies.
|
|
37
|
+
|
|
38
|
+
**Supported Platforms:**
|
|
39
|
+
|
|
40
|
+
- **Ubuntu 22.04 (Jammy)** - ROS 2 Humble
|
|
41
|
+
- **Ubuntu 24.04 (Noble)** - ROS 2 Jazzy, Kilted
|
|
42
|
+
- **Architectures:** x64, arm64
|
|
43
|
+
- **Node.js:** >= 16.20.2 (N-API compatible)
|
|
44
|
+
|
|
45
|
+
**Force Building from Source:**
|
|
46
|
+
|
|
47
|
+
If you need to build from source even when a prebuilt binary is available, set the environment variable:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
export RCLNODEJS_FORCE_BUILD=1
|
|
51
|
+
npm install rclnodejs
|
|
52
|
+
```
|
|
53
|
+
|
|
34
54
|
## Documentation
|
|
35
55
|
|
|
36
56
|
API [documentation](https://robotwebtools.github.io/rclnodejs/docs/index.html) is available online.
|
|
@@ -73,9 +93,12 @@ See [TypeScript demos](https://github.com/RobotWebTools/rclnodejs/tree/develop/t
|
|
|
73
93
|
|
|
74
94
|
Create rich, interactive desktop applications using Electron and web technologies like Three.js. Build 3D visualizations, monitoring dashboards, and control interfaces that run on Windows, macOS, and Linux.
|
|
75
95
|
|
|
76
|
-
|
|
96
|
+
| Demo | Description | Screenshot |
|
|
97
|
+
| :-----------------------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------------------------------------: |
|
|
98
|
+
| **🐢 [turtle_tf2](https://github.com/RobotWebTools/rclnodejs/tree/develop/electron_demo/turtle_tf2)** | Real-time coordinate frame visualization with turtle control. Features TF2 transforms, keyboard control, and dynamic frame updates. |  |
|
|
99
|
+
| **🦾 [manipulator](https://github.com/RobotWebTools/rclnodejs/tree/develop/electron_demo/manipulator)** | Interactive two-joint robotic arm simulation. Features 3D joint visualization, manual/automatic control, and visual movement markers. |  |
|
|
77
100
|
|
|
78
|
-
|
|
101
|
+
Explore more examples in [electron_demo](https://github.com/RobotWebTools/rclnodejs/tree/develop/electron_demo).
|
|
79
102
|
|
|
80
103
|
## License
|
|
81
104
|
|
package/binding.gyp
CHANGED
|
@@ -42,9 +42,11 @@
|
|
|
42
42
|
'./src/rcl_timer_bindings.cpp',
|
|
43
43
|
'./src/rcl_utilities.cpp',
|
|
44
44
|
'./src/shadow_node.cpp',
|
|
45
|
+
'./third_party/ref-napi/src/ref_napi_bindings.cpp',
|
|
45
46
|
],
|
|
46
47
|
'include_dirs': [
|
|
47
48
|
'.',
|
|
49
|
+
'./third_party/ref-napi/src',
|
|
48
50
|
'<(ros_include_root)',
|
|
49
51
|
"<!@(node -p \"require('node-addon-api').include\")",
|
|
50
52
|
],
|
package/index.js
CHANGED
|
@@ -18,7 +18,7 @@ const DistroUtils = require('./lib/distro.js');
|
|
|
18
18
|
const RMWUtils = require('./lib/rmw.js');
|
|
19
19
|
const { Clock, ROSClock } = require('./lib/clock.js');
|
|
20
20
|
const ClockType = require('./lib/clock_type.js');
|
|
21
|
-
const compareVersions = require('
|
|
21
|
+
const { compareVersions } = require('./lib/utils.js');
|
|
22
22
|
const Context = require('./lib/context.js');
|
|
23
23
|
const debug = require('debug')('rclnodejs');
|
|
24
24
|
const Duration = require('./lib/duration.js');
|
|
@@ -37,7 +37,7 @@ const {
|
|
|
37
37
|
} = require('./lib/parameter.js');
|
|
38
38
|
const path = require('path');
|
|
39
39
|
const QoS = require('./lib/qos.js');
|
|
40
|
-
const rclnodejs = require('
|
|
40
|
+
const rclnodejs = require('./lib/native_loader.js');
|
|
41
41
|
const tsdGenerator = require('./rostsd_gen/index.js');
|
|
42
42
|
const validator = require('./lib/validator.js');
|
|
43
43
|
const Time = require('./lib/time.js');
|
|
@@ -47,6 +47,7 @@ const ActionUuid = require('./lib/action/uuid.js');
|
|
|
47
47
|
const ClientGoalHandle = require('./lib/action/client_goal_handle.js');
|
|
48
48
|
const { CancelResponse, GoalResponse } = require('./lib/action/response.js');
|
|
49
49
|
const ServerGoalHandle = require('./lib/action/server_goal_handle.js');
|
|
50
|
+
const { toJSONSafe, toJSONString } = require('./lib/message_serialization.js');
|
|
50
51
|
const {
|
|
51
52
|
getActionClientNamesAndTypesByNode,
|
|
52
53
|
getActionServerNamesAndTypesByNode,
|
|
@@ -364,8 +365,7 @@ let rcl = {
|
|
|
364
365
|
|
|
365
366
|
const version = await getCurrentGeneratorVersion();
|
|
366
367
|
const forced =
|
|
367
|
-
version === null ||
|
|
368
|
-
compareVersions.compare(version, generator.version(), '<');
|
|
368
|
+
version === null || compareVersions(version, generator.version(), '<');
|
|
369
369
|
if (forced) {
|
|
370
370
|
debug(
|
|
371
371
|
'The generator will begin to create JavaScript code from ROS IDL files...'
|
|
@@ -539,6 +539,23 @@ let rcl = {
|
|
|
539
539
|
* @return {Promise<{process: ChildProcess}>} A Promise that resolves with the process.
|
|
540
540
|
*/
|
|
541
541
|
ros2Launch: ros2Launch,
|
|
542
|
+
|
|
543
|
+
/**
|
|
544
|
+
* Convert a message object to be JSON-safe by converting TypedArrays to regular arrays
|
|
545
|
+
* and handling BigInt, Infinity, NaN, etc. for JSON serialization.
|
|
546
|
+
* @param {*} obj - The message object to convert
|
|
547
|
+
* @returns {*} A JSON-safe version of the object
|
|
548
|
+
*/
|
|
549
|
+
toJSONSafe: toJSONSafe,
|
|
550
|
+
|
|
551
|
+
/**
|
|
552
|
+
* Convert a message object to a JSON string with proper handling of TypedArrays,
|
|
553
|
+
* BigInt, and other non-JSON-serializable values.
|
|
554
|
+
* @param {*} obj - The message object to convert
|
|
555
|
+
* @param {number} [space] - Space parameter for JSON.stringify formatting
|
|
556
|
+
* @returns {string} The JSON string representation
|
|
557
|
+
*/
|
|
558
|
+
toJSONString: toJSONString,
|
|
542
559
|
};
|
|
543
560
|
|
|
544
561
|
const _sigHandler = () => {
|
package/lib/action/client.js
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
|
|
15
15
|
'use strict';
|
|
16
16
|
|
|
17
|
-
const rclnodejs = require('
|
|
17
|
+
const rclnodejs = require('../native_loader.js');
|
|
18
18
|
const ActionInterfaces = require('./interfaces.js');
|
|
19
19
|
const ActionUuid = require('./uuid.js');
|
|
20
20
|
const ClientGoalHandle = require('./client_goal_handle.js');
|
package/lib/action/graph.js
CHANGED
package/lib/action/server.js
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
|
|
15
15
|
'use strict';
|
|
16
16
|
|
|
17
|
-
const rclnodejs = require('
|
|
17
|
+
const rclnodejs = require('../native_loader.js');
|
|
18
18
|
const ActionInterfaces = require('./interfaces.js');
|
|
19
19
|
const ActionUuid = require('./uuid.js');
|
|
20
20
|
const DistroUtils = require('../distro.js');
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
|
|
15
15
|
'use strict';
|
|
16
16
|
|
|
17
|
-
const rclnodejs = require('
|
|
17
|
+
const rclnodejs = require('../native_loader.js');
|
|
18
18
|
const ActionInterfaces = require('./interfaces.js');
|
|
19
19
|
const Deferred = require('./deferred.js');
|
|
20
20
|
const { GoalEvent } = require('./response.js');
|
package/lib/client.js
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
|
|
15
15
|
'use strict';
|
|
16
16
|
|
|
17
|
-
const rclnodejs = require('
|
|
17
|
+
const rclnodejs = require('./native_loader.js');
|
|
18
18
|
const DistroUtils = require('./distro.js');
|
|
19
19
|
const Entity = require('./entity.js');
|
|
20
20
|
const debug = require('debug')('rclnodejs:client');
|
package/lib/clock.js
CHANGED
package/lib/context.js
CHANGED
package/lib/duration.js
CHANGED
package/lib/event_handler.js
CHANGED
package/lib/guard_condition.js
CHANGED
package/lib/lifecycle.js
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
|
|
15
15
|
'use strict';
|
|
16
16
|
|
|
17
|
-
const rclnodejs = require('
|
|
17
|
+
const rclnodejs = require('./native_loader.js');
|
|
18
18
|
const LifecyclePublisher = require('./lifecycle_publisher.js');
|
|
19
19
|
const loader = require('./interface_loader.js');
|
|
20
20
|
const Context = require('./context.js');
|
package/lib/logging.js
CHANGED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
// Copyright (c) 2025 Mahmoud Alghalayini. All rights reserved.
|
|
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
|
+
'use strict';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Check if a value is a TypedArray
|
|
19
|
+
* @param {*} value - The value to check
|
|
20
|
+
* @returns {boolean} True if the value is a TypedArray
|
|
21
|
+
*/
|
|
22
|
+
function isTypedArray(value) {
|
|
23
|
+
return ArrayBuffer.isView(value) && !(value instanceof DataView);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Check if a value needs JSON conversion (BigInt, functions, etc.)
|
|
28
|
+
* @param {*} value - The value to check
|
|
29
|
+
* @returns {boolean} True if the value needs special JSON handling
|
|
30
|
+
*/
|
|
31
|
+
function needsJSONConversion(value) {
|
|
32
|
+
return (
|
|
33
|
+
typeof value === 'bigint' ||
|
|
34
|
+
typeof value === 'function' ||
|
|
35
|
+
typeof value === 'undefined' ||
|
|
36
|
+
value === Infinity ||
|
|
37
|
+
value === -Infinity ||
|
|
38
|
+
(typeof value === 'number' && isNaN(value))
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Convert a message to plain arrays (TypedArray -> regular Array)
|
|
44
|
+
* @param {*} obj - The object to convert
|
|
45
|
+
* @returns {*} The converted object with plain arrays
|
|
46
|
+
*/
|
|
47
|
+
function toPlainArrays(obj) {
|
|
48
|
+
if (obj === null || obj === undefined) {
|
|
49
|
+
return obj;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (isTypedArray(obj)) {
|
|
53
|
+
return Array.from(obj);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (Array.isArray(obj)) {
|
|
57
|
+
return obj.map((item) => toPlainArrays(item));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (typeof obj === 'object' && obj !== null) {
|
|
61
|
+
const result = {};
|
|
62
|
+
for (const key in obj) {
|
|
63
|
+
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
|
64
|
+
result[key] = toPlainArrays(obj[key]);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return result;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return obj;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Convert a message to be fully JSON-safe
|
|
75
|
+
* @param {*} obj - The object to convert
|
|
76
|
+
* @returns {*} The JSON-safe converted object
|
|
77
|
+
*/
|
|
78
|
+
function toJSONSafe(obj) {
|
|
79
|
+
if (obj === null || obj === undefined) {
|
|
80
|
+
return obj;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (isTypedArray(obj)) {
|
|
84
|
+
return Array.from(obj).map((item) => toJSONSafe(item));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (needsJSONConversion(obj)) {
|
|
88
|
+
if (typeof obj === 'bigint') {
|
|
89
|
+
// Convert BigInt to string with 'n' suffix to indicate it was a BigInt
|
|
90
|
+
return obj.toString() + 'n';
|
|
91
|
+
}
|
|
92
|
+
if (obj === Infinity) return 'Infinity';
|
|
93
|
+
if (obj === -Infinity) return '-Infinity';
|
|
94
|
+
if (typeof obj === 'number' && isNaN(obj)) return 'NaN';
|
|
95
|
+
if (typeof obj === 'undefined') return null;
|
|
96
|
+
if (typeof obj === 'function') return '[Function]';
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (Array.isArray(obj)) {
|
|
100
|
+
return obj.map((item) => toJSONSafe(item));
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (typeof obj === 'object' && obj !== null) {
|
|
104
|
+
const result = {};
|
|
105
|
+
for (const key in obj) {
|
|
106
|
+
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
|
107
|
+
result[key] = toJSONSafe(obj[key]);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return result;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return obj;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Convert a message to a JSON string
|
|
118
|
+
* @param {*} obj - The object to convert
|
|
119
|
+
* @param {number} [space] - Space parameter for JSON.stringify formatting
|
|
120
|
+
* @returns {string} The JSON string representation
|
|
121
|
+
*/
|
|
122
|
+
function toJSONString(obj, space) {
|
|
123
|
+
const jsonSafeObj = toJSONSafe(obj);
|
|
124
|
+
return JSON.stringify(jsonSafeObj, null, space);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Apply serialization mode conversion to a message object
|
|
129
|
+
* @param {*} message - The message object to convert
|
|
130
|
+
* @param {string} serializationMode - The serialization mode ('default', 'plain', 'json')
|
|
131
|
+
* @returns {*} The converted message
|
|
132
|
+
*/
|
|
133
|
+
function applySerializationMode(message, serializationMode) {
|
|
134
|
+
switch (serializationMode) {
|
|
135
|
+
case 'default':
|
|
136
|
+
// No conversion needed - use native rclnodejs behavior
|
|
137
|
+
return message;
|
|
138
|
+
|
|
139
|
+
case 'plain':
|
|
140
|
+
// Convert TypedArrays to regular arrays
|
|
141
|
+
return toPlainArrays(message);
|
|
142
|
+
|
|
143
|
+
case 'json':
|
|
144
|
+
// Convert to fully JSON-safe format
|
|
145
|
+
return toJSONSafe(message);
|
|
146
|
+
|
|
147
|
+
default:
|
|
148
|
+
throw new TypeError(
|
|
149
|
+
`Invalid serializationMode: ${serializationMode}. Valid modes are: 'default', 'plain', 'json'`
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Validate serialization mode
|
|
156
|
+
* @param {string} mode - The serialization mode to validate
|
|
157
|
+
* @returns {boolean} True if valid
|
|
158
|
+
*/
|
|
159
|
+
function isValidSerializationMode(mode) {
|
|
160
|
+
return ['default', 'plain', 'json'].includes(mode);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
module.exports = {
|
|
164
|
+
isTypedArray,
|
|
165
|
+
needsJSONConversion,
|
|
166
|
+
toPlainArrays,
|
|
167
|
+
toJSONSafe,
|
|
168
|
+
toJSONString,
|
|
169
|
+
applySerializationMode,
|
|
170
|
+
isValidSerializationMode,
|
|
171
|
+
};
|
|
@@ -0,0 +1,173 @@
|
|
|
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
|
+
'use strict';
|
|
16
|
+
|
|
17
|
+
const fs = require('fs');
|
|
18
|
+
const path = require('path');
|
|
19
|
+
const { execSync } = require('child_process');
|
|
20
|
+
const bindings = require('bindings');
|
|
21
|
+
const debug = require('debug')('rclnodejs');
|
|
22
|
+
const { detectUbuntuCodename } = require('./utils');
|
|
23
|
+
|
|
24
|
+
let nativeModule = null;
|
|
25
|
+
|
|
26
|
+
// Simplified loader: only use prebuilt binaries with exact Ubuntu/ROS2/arch match
|
|
27
|
+
// Note: Prebuilt binaries are only supported on Linux (Ubuntu) platform
|
|
28
|
+
function customFallbackLoader() {
|
|
29
|
+
// Prebuilt binaries are only for Linux platform
|
|
30
|
+
if (process.platform !== 'linux') {
|
|
31
|
+
debug('Prebuilt binaries are only supported on Linux platform');
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const rosDistro = process.env.ROS_DISTRO;
|
|
36
|
+
const arch = process.arch;
|
|
37
|
+
const ubuntuCodename = detectUbuntuCodename();
|
|
38
|
+
|
|
39
|
+
// Require all three components for exact match
|
|
40
|
+
if (!rosDistro || !ubuntuCodename || !arch) {
|
|
41
|
+
debug(
|
|
42
|
+
`Missing environment info - ROS: ${rosDistro}, Ubuntu: ${ubuntuCodename}, Arch: ${arch}`
|
|
43
|
+
);
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const prebuildDir = path.join(
|
|
48
|
+
__dirname,
|
|
49
|
+
'..',
|
|
50
|
+
'prebuilds',
|
|
51
|
+
`${process.platform}-${arch}`
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
if (!fs.existsSync(prebuildDir)) {
|
|
55
|
+
debug('No prebuilds directory found');
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
try {
|
|
60
|
+
// Look for exact match binary: {ros_distro}-{ubuntu_codename}-{arch}-rclnodejs.node
|
|
61
|
+
const exactMatchFilename = `${rosDistro}-${ubuntuCodename}-${arch}-rclnodejs.node`;
|
|
62
|
+
const exactMatchPath = path.join(prebuildDir, exactMatchFilename);
|
|
63
|
+
|
|
64
|
+
if (fs.existsSync(exactMatchPath)) {
|
|
65
|
+
debug(`Found exact match binary: ${exactMatchFilename}`);
|
|
66
|
+
return require(exactMatchPath);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
debug(`No exact match found for: ${exactMatchFilename}`);
|
|
70
|
+
return null;
|
|
71
|
+
} catch (e) {
|
|
72
|
+
debug('Error in simplified prebuilt loader:', e.message);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Simplified prebuilt binary loader: exact match or build from source
|
|
79
|
+
function loadNativeAddon() {
|
|
80
|
+
if (nativeModule) {
|
|
81
|
+
return nativeModule;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Environment variable to force building from source
|
|
85
|
+
if (process.env.RCLNODEJS_FORCE_BUILD === '1') {
|
|
86
|
+
debug('Forcing build from source (RCLNODEJS_FORCE_BUILD=1)');
|
|
87
|
+
|
|
88
|
+
// Trigger actual compilation
|
|
89
|
+
try {
|
|
90
|
+
debug('Running forced node-gyp rebuild...');
|
|
91
|
+
execSync('npm run rebuild', {
|
|
92
|
+
stdio: 'inherit',
|
|
93
|
+
cwd: path.join(__dirname, '..'),
|
|
94
|
+
timeout: 300000, // 5 minute timeout
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// Load the newly built binary
|
|
98
|
+
nativeModule = bindings('rclnodejs');
|
|
99
|
+
debug('Successfully force compiled and loaded from source');
|
|
100
|
+
return nativeModule;
|
|
101
|
+
} catch (compileError) {
|
|
102
|
+
debug('Forced compilation failed:', compileError.message);
|
|
103
|
+
throw new Error(
|
|
104
|
+
`Failed to force build rclnodejs from source: ${compileError.message}`
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const rosDistro = process.env.ROS_DISTRO;
|
|
110
|
+
const ubuntuCodename = detectUbuntuCodename();
|
|
111
|
+
|
|
112
|
+
debug(
|
|
113
|
+
`Platform: ${process.platform}, Arch: ${process.arch}, Ubuntu: ${ubuntuCodename || 'unknown'}, ROS: ${rosDistro || 'unknown'}`
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
// Prebuilt binaries are only supported on Linux (Ubuntu)
|
|
117
|
+
if (process.platform === 'linux') {
|
|
118
|
+
// Try exact match prebuilt binary first
|
|
119
|
+
try {
|
|
120
|
+
const prebuiltModule = customFallbackLoader();
|
|
121
|
+
if (prebuiltModule) {
|
|
122
|
+
nativeModule = prebuiltModule;
|
|
123
|
+
return nativeModule;
|
|
124
|
+
}
|
|
125
|
+
} catch (e) {
|
|
126
|
+
debug('Exact match prebuilt loading failed:', e.message);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
debug(
|
|
130
|
+
'No exact match prebuilt binary found, falling back to build from source'
|
|
131
|
+
);
|
|
132
|
+
} else {
|
|
133
|
+
debug(
|
|
134
|
+
`Platform ${process.platform} does not support prebuilt binaries, will try existing build or compile from source`
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
try {
|
|
139
|
+
// Try to find existing built binary first (works on all platforms)
|
|
140
|
+
// The 'bindings' module will search standard locations like:
|
|
141
|
+
// - build/Release/rclnodejs.node
|
|
142
|
+
// - build/Debug/rclnodejs.node
|
|
143
|
+
// - compiled/{node_version}/{platform}/{arch}/rclnodejs.node
|
|
144
|
+
// etc.
|
|
145
|
+
nativeModule = bindings('rclnodejs');
|
|
146
|
+
debug('Found and loaded existing built binary');
|
|
147
|
+
return nativeModule;
|
|
148
|
+
} catch {
|
|
149
|
+
debug('No existing built binary found, triggering compilation...');
|
|
150
|
+
|
|
151
|
+
// Trigger actual compilation
|
|
152
|
+
try {
|
|
153
|
+
debug('Running node-gyp rebuild...');
|
|
154
|
+
execSync('npm run rebuild', {
|
|
155
|
+
stdio: 'inherit',
|
|
156
|
+
cwd: path.join(__dirname, '..'),
|
|
157
|
+
timeout: 300000, // 5 minute timeout
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
// Try to load the newly built binary
|
|
161
|
+
nativeModule = bindings('rclnodejs');
|
|
162
|
+
debug('Successfully compiled and loaded from source');
|
|
163
|
+
return nativeModule;
|
|
164
|
+
} catch (compileError) {
|
|
165
|
+
debug('Compilation failed:', compileError.message);
|
|
166
|
+
throw new Error(
|
|
167
|
+
`Failed to build rclnodejs from source: ${compileError.message}`
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
module.exports = loadNativeAddon();
|
package/lib/node.js
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
|
|
15
15
|
'use strict';
|
|
16
16
|
|
|
17
|
-
const rclnodejs = require('
|
|
17
|
+
const rclnodejs = require('./native_loader.js');
|
|
18
18
|
|
|
19
19
|
const ActionInterfaces = require('./action/interfaces.js');
|
|
20
20
|
const Client = require('./client.js');
|
|
@@ -31,6 +31,7 @@ const {
|
|
|
31
31
|
Parameter,
|
|
32
32
|
ParameterDescriptor,
|
|
33
33
|
} = require('./parameter.js');
|
|
34
|
+
const { isValidSerializationMode } = require('./message_serialization.js');
|
|
34
35
|
const ParameterService = require('./parameter_service.js');
|
|
35
36
|
const Publisher = require('./publisher.js');
|
|
36
37
|
const QoS = require('./qos.js');
|
|
@@ -532,6 +533,14 @@ class Node extends rclnodejs.ShadowNode {
|
|
|
532
533
|
options = Object.assign(options, { isRaw: false });
|
|
533
534
|
}
|
|
534
535
|
|
|
536
|
+
if (options.serializationMode === undefined) {
|
|
537
|
+
options = Object.assign(options, { serializationMode: 'default' });
|
|
538
|
+
} else if (!isValidSerializationMode(options.serializationMode)) {
|
|
539
|
+
throw new TypeError(
|
|
540
|
+
`Invalid serializationMode: ${options.serializationMode}. Valid modes are: 'default', 'plain', 'json'`
|
|
541
|
+
);
|
|
542
|
+
}
|
|
543
|
+
|
|
535
544
|
return options;
|
|
536
545
|
}
|
|
537
546
|
|
|
@@ -666,6 +675,10 @@ class Node extends rclnodejs.ShadowNode {
|
|
|
666
675
|
* @param {boolean} options.enableTypedArray - The topic will use TypedArray if necessary, default: true.
|
|
667
676
|
* @param {QoS} options.qos - ROS Middleware "quality of service" settings for the subscription, default: QoS.profileDefault.
|
|
668
677
|
* @param {boolean} options.isRaw - The topic is serialized when true, default: false.
|
|
678
|
+
* @param {string} [options.serializationMode='default'] - Controls message serialization format:
|
|
679
|
+
* 'default': Use native rclnodejs behavior (respects enableTypedArray setting),
|
|
680
|
+
* 'plain': Convert TypedArrays to regular arrays,
|
|
681
|
+
* 'json': Fully JSON-safe (handles TypedArrays, BigInt, etc.).
|
|
669
682
|
* @param {object} [options.contentFilter=undefined] - The content-filter, default: undefined.
|
|
670
683
|
* Confirm that your RMW supports content-filtered topics before use.
|
|
671
684
|
* @param {string} options.contentFilter.expression - Specifies the criteria to select the data samples of
|
|
@@ -1920,6 +1933,7 @@ class Node extends rclnodejs.ShadowNode {
|
|
|
1920
1933
|
* isRaw: false,
|
|
1921
1934
|
* qos: QoS.profileDefault,
|
|
1922
1935
|
* contentFilter: undefined,
|
|
1936
|
+
* serializationMode: 'default',
|
|
1923
1937
|
* }
|
|
1924
1938
|
*/
|
|
1925
1939
|
Node.getDefaultOptions = function () {
|
|
@@ -1928,6 +1942,7 @@ Node.getDefaultOptions = function () {
|
|
|
1928
1942
|
isRaw: false,
|
|
1929
1943
|
qos: QoS.profileDefault,
|
|
1930
1944
|
contentFilter: undefined,
|
|
1945
|
+
serializationMode: 'default',
|
|
1931
1946
|
};
|
|
1932
1947
|
};
|
|
1933
1948
|
|