rclnodejs 1.8.2 → 1.8.3
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/index.js +33 -23
- package/lib/action/client.js +6 -0
- package/lib/action/server.js +1 -3
- package/lib/distro.js +2 -1
- package/lib/lifecycle_publisher.js +2 -2
- package/lib/node.js +37 -11
- package/lib/parameter.js +5 -9
- package/lib/service.js +8 -4
- package/lib/time_source.js +3 -20
- package/package.json +2 -2
- 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_gen/generate_worker.js +3 -13
- package/rosidl_gen/idl_generator.js +210 -0
- package/rosidl_gen/index.js +3 -12
- package/rosidl_gen/packages.js +1 -3
- package/rosidl_gen/primitive_types.js +2 -2
- package/rosidl_parser/idl_parser.py +437 -0
- package/rosidl_parser/parser.py +2 -4
- package/rosidl_parser/rosidl_parser.js +27 -0
- package/src/executor.cpp +1 -0
- package/src/macros.h +2 -2
- package/src/rcl_action_client_bindings.cpp +18 -11
- package/src/rcl_action_server_bindings.cpp +24 -13
- package/src/rcl_client_bindings.cpp +13 -5
- package/src/rcl_context_bindings.cpp +7 -8
- package/src/rcl_guard_condition_bindings.cpp +12 -3
- package/src/rcl_lifecycle_bindings.cpp +34 -15
- package/src/rcl_node_bindings.cpp +11 -4
- package/src/rcl_publisher_bindings.cpp +12 -3
- package/src/rcl_service_bindings.cpp +12 -3
- package/src/rcl_subscription_bindings.cpp +24 -21
- package/src/rcl_timer_bindings.cpp +24 -9
- package/src/rcl_type_description_service_bindings.cpp +9 -1
- package/rosidl_convertor/README.md +0 -298
- package/rosidl_convertor/idl_convertor.js +0 -50
- package/rosidl_convertor/idl_convertor.py +0 -1250
|
@@ -0,0 +1,437 @@
|
|
|
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
|
+
"""
|
|
16
|
+
IDL file parser that uses rosidl_parser to directly parse .idl files and
|
|
17
|
+
produce the same JSON spec format as parser.py does for .msg files.
|
|
18
|
+
|
|
19
|
+
This eliminates the need for the intermediate .idl -> .msg conversion step.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
import sys
|
|
23
|
+
import json
|
|
24
|
+
|
|
25
|
+
from rosidl_parser.parser import parse_idl_string
|
|
26
|
+
from rosidl_parser.definition import (
|
|
27
|
+
AbstractNestedType,
|
|
28
|
+
Action,
|
|
29
|
+
Array,
|
|
30
|
+
BasicType,
|
|
31
|
+
BoundedSequence,
|
|
32
|
+
BoundedString,
|
|
33
|
+
BoundedWString,
|
|
34
|
+
Message,
|
|
35
|
+
NamedType,
|
|
36
|
+
NamespacedType,
|
|
37
|
+
Service,
|
|
38
|
+
UnboundedSequence,
|
|
39
|
+
UnboundedString,
|
|
40
|
+
UnboundedWString,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
# Mapping from IDL basic type names to ROS2 message type names
|
|
44
|
+
IDL_TYPE_TO_ROS2 = {
|
|
45
|
+
'boolean': 'bool',
|
|
46
|
+
'octet': 'byte',
|
|
47
|
+
'char': 'char',
|
|
48
|
+
'wchar': 'wchar',
|
|
49
|
+
'int8': 'int8',
|
|
50
|
+
'uint8': 'uint8',
|
|
51
|
+
'int16': 'int16',
|
|
52
|
+
'uint16': 'uint16',
|
|
53
|
+
'int32': 'int32',
|
|
54
|
+
'uint32': 'uint32',
|
|
55
|
+
'int64': 'int64',
|
|
56
|
+
'uint64': 'uint64',
|
|
57
|
+
'float': 'float32',
|
|
58
|
+
'double': 'float64',
|
|
59
|
+
'long double': 'float64',
|
|
60
|
+
'short': 'int16',
|
|
61
|
+
'long': 'int32',
|
|
62
|
+
'long long': 'int64',
|
|
63
|
+
'unsigned short': 'uint16',
|
|
64
|
+
'unsigned long': 'uint32',
|
|
65
|
+
'unsigned long long': 'uint64',
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _map_basic_type(typename):
|
|
70
|
+
"""Map an IDL basic type name to its ROS2 equivalent."""
|
|
71
|
+
return IDL_TYPE_TO_ROS2.get(typename, typename)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _get_nestable_type_info(type_obj):
|
|
75
|
+
"""
|
|
76
|
+
Extract type info from a nestable type (the inner type for arrays/sequences,
|
|
77
|
+
or the type itself for non-nested types).
|
|
78
|
+
|
|
79
|
+
Returns a dict with: pkgName, type, stringUpperBound, isPrimitiveType
|
|
80
|
+
"""
|
|
81
|
+
if isinstance(type_obj, BasicType):
|
|
82
|
+
ros2_type = _map_basic_type(type_obj.typename)
|
|
83
|
+
return {
|
|
84
|
+
'pkgName': None,
|
|
85
|
+
'type': ros2_type,
|
|
86
|
+
'stringUpperBound': None,
|
|
87
|
+
'isPrimitiveType': True,
|
|
88
|
+
}
|
|
89
|
+
elif isinstance(type_obj, (UnboundedString, BoundedString)):
|
|
90
|
+
string_upper_bound = None
|
|
91
|
+
if isinstance(type_obj, BoundedString):
|
|
92
|
+
string_upper_bound = type_obj.maximum_size
|
|
93
|
+
return {
|
|
94
|
+
'pkgName': None,
|
|
95
|
+
'type': 'string',
|
|
96
|
+
'stringUpperBound': string_upper_bound,
|
|
97
|
+
'isPrimitiveType': True,
|
|
98
|
+
}
|
|
99
|
+
elif isinstance(type_obj, (UnboundedWString, BoundedWString)):
|
|
100
|
+
string_upper_bound = None
|
|
101
|
+
if isinstance(type_obj, BoundedWString):
|
|
102
|
+
string_upper_bound = type_obj.maximum_size
|
|
103
|
+
return {
|
|
104
|
+
'pkgName': None,
|
|
105
|
+
'type': 'wstring',
|
|
106
|
+
'stringUpperBound': string_upper_bound,
|
|
107
|
+
'isPrimitiveType': True,
|
|
108
|
+
}
|
|
109
|
+
elif isinstance(type_obj, NamespacedType):
|
|
110
|
+
# Convert Token objects to plain strings if needed
|
|
111
|
+
namespaces = [str(ns) for ns in type_obj.namespaces]
|
|
112
|
+
pkg_name = namespaces[0] if namespaces else None
|
|
113
|
+
return {
|
|
114
|
+
'pkgName': pkg_name,
|
|
115
|
+
'type': str(type_obj.name),
|
|
116
|
+
'stringUpperBound': None,
|
|
117
|
+
'isPrimitiveType': False,
|
|
118
|
+
}
|
|
119
|
+
elif isinstance(type_obj, NamedType):
|
|
120
|
+
# NamedType is typically resolved by typedefs, but handle it just in case
|
|
121
|
+
return {
|
|
122
|
+
'pkgName': None,
|
|
123
|
+
'type': str(type_obj.name),
|
|
124
|
+
'stringUpperBound': None,
|
|
125
|
+
'isPrimitiveType': False,
|
|
126
|
+
}
|
|
127
|
+
else:
|
|
128
|
+
raise ValueError(f'Unexpected nestable type: {type(type_obj).__name__}')
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def _get_type_json(member_type):
|
|
132
|
+
"""
|
|
133
|
+
Convert a rosidl_parser type object to the JSON format expected by
|
|
134
|
+
the rclnodejs templates.
|
|
135
|
+
|
|
136
|
+
Returns a dict matching the spec.fields[].type format:
|
|
137
|
+
{isArray, arraySize, isUpperBound, isDynamicArray, isFixedSizeArray,
|
|
138
|
+
pkgName, type, stringUpperBound, isPrimitiveType}
|
|
139
|
+
"""
|
|
140
|
+
if isinstance(member_type, Array):
|
|
141
|
+
# Fixed-size array: e.g., float64[9]
|
|
142
|
+
inner_info = _get_nestable_type_info(member_type.value_type)
|
|
143
|
+
return {
|
|
144
|
+
'isArray': True,
|
|
145
|
+
'arraySize': member_type.size,
|
|
146
|
+
'isUpperBound': False,
|
|
147
|
+
'isDynamicArray': False,
|
|
148
|
+
'isFixedSizeArray': True,
|
|
149
|
+
**inner_info,
|
|
150
|
+
}
|
|
151
|
+
elif isinstance(member_type, BoundedSequence):
|
|
152
|
+
# Bounded dynamic array: e.g., sequence<float64, 3>
|
|
153
|
+
inner_info = _get_nestable_type_info(member_type.value_type)
|
|
154
|
+
return {
|
|
155
|
+
'isArray': True,
|
|
156
|
+
'arraySize': member_type.maximum_size,
|
|
157
|
+
'isUpperBound': True,
|
|
158
|
+
'isDynamicArray': True,
|
|
159
|
+
'isFixedSizeArray': False,
|
|
160
|
+
**inner_info,
|
|
161
|
+
}
|
|
162
|
+
elif isinstance(member_type, UnboundedSequence):
|
|
163
|
+
# Unbounded dynamic array: e.g., sequence<float64>
|
|
164
|
+
inner_info = _get_nestable_type_info(member_type.value_type)
|
|
165
|
+
return {
|
|
166
|
+
'isArray': True,
|
|
167
|
+
'arraySize': None,
|
|
168
|
+
'isUpperBound': False,
|
|
169
|
+
'isDynamicArray': True,
|
|
170
|
+
'isFixedSizeArray': False,
|
|
171
|
+
**inner_info,
|
|
172
|
+
}
|
|
173
|
+
else:
|
|
174
|
+
# Non-array/sequence type
|
|
175
|
+
inner_info = _get_nestable_type_info(member_type)
|
|
176
|
+
return {
|
|
177
|
+
'isArray': False,
|
|
178
|
+
'arraySize': None,
|
|
179
|
+
'isUpperBound': False,
|
|
180
|
+
'isDynamicArray': False,
|
|
181
|
+
'isFixedSizeArray': False,
|
|
182
|
+
**inner_info,
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def _get_default_value(member):
|
|
187
|
+
"""Extract default value from member annotations.
|
|
188
|
+
|
|
189
|
+
rosidl_parser returns array/sequence defaults as tuple-like strings,
|
|
190
|
+
e.g., "(0, 127, -128)" or "('', 'max value', 'min value')".
|
|
191
|
+
The .msg parser returns these as Python lists [0, 127, -128].
|
|
192
|
+
We need to convert the tuple-strings to proper lists.
|
|
193
|
+
"""
|
|
194
|
+
try:
|
|
195
|
+
default_annotation = member.get_annotation_value('default')
|
|
196
|
+
if isinstance(default_annotation, dict) and 'value' in default_annotation:
|
|
197
|
+
value = default_annotation['value']
|
|
198
|
+
else:
|
|
199
|
+
value = default_annotation
|
|
200
|
+
|
|
201
|
+
# If the value is a string that looks like a tuple "(a, b, c)",
|
|
202
|
+
# parse it into a Python list
|
|
203
|
+
if isinstance(value, str) and value.startswith('(') and value.endswith(')'):
|
|
204
|
+
value = _parse_tuple_string(value, member)
|
|
205
|
+
|
|
206
|
+
return value
|
|
207
|
+
except ValueError:
|
|
208
|
+
return None
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def _parse_tuple_string(s, member):
|
|
212
|
+
"""
|
|
213
|
+
Parse a tuple-like string from rosidl_parser into a proper Python list.
|
|
214
|
+
|
|
215
|
+
Handles formats like:
|
|
216
|
+
"(0, 127, -128)" -> [0, 127, -128]
|
|
217
|
+
"(1.125, 0.0, -1.125)" -> [1.125, 0.0, -1.125]
|
|
218
|
+
"(False, True, False)" -> [False, True, False]
|
|
219
|
+
"('', 'max value', 'min value')" -> ['', 'max value', 'min value']
|
|
220
|
+
"""
|
|
221
|
+
inner = s[1:-1] # Strip outer parentheses
|
|
222
|
+
|
|
223
|
+
# Determine the element type from the member's type
|
|
224
|
+
member_type = member.type
|
|
225
|
+
if isinstance(member_type, AbstractNestedType):
|
|
226
|
+
element_type = member_type.value_type
|
|
227
|
+
else:
|
|
228
|
+
element_type = member_type
|
|
229
|
+
|
|
230
|
+
# Parse string arrays specially (may contain commas in values)
|
|
231
|
+
if isinstance(element_type, (UnboundedString, BoundedString,
|
|
232
|
+
UnboundedWString, BoundedWString)):
|
|
233
|
+
return _parse_string_tuple(inner)
|
|
234
|
+
|
|
235
|
+
# For boolean arrays
|
|
236
|
+
if isinstance(element_type, BasicType) and element_type.typename == 'boolean':
|
|
237
|
+
parts = [p.strip() for p in inner.split(',')]
|
|
238
|
+
return [p == 'True' for p in parts]
|
|
239
|
+
|
|
240
|
+
# For numeric arrays
|
|
241
|
+
parts = [p.strip() for p in inner.split(',')]
|
|
242
|
+
result = []
|
|
243
|
+
for part in parts:
|
|
244
|
+
if not part:
|
|
245
|
+
continue
|
|
246
|
+
# Try int first, then float
|
|
247
|
+
try:
|
|
248
|
+
result.append(int(part))
|
|
249
|
+
except ValueError:
|
|
250
|
+
try:
|
|
251
|
+
result.append(float(part))
|
|
252
|
+
except ValueError:
|
|
253
|
+
result.append(part)
|
|
254
|
+
return result
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def _parse_string_tuple(inner):
|
|
258
|
+
"""Parse the inside of a tuple containing quoted strings.
|
|
259
|
+
|
|
260
|
+
e.g., "'', 'max value', 'min value'" -> ['', 'max value', 'min value']
|
|
261
|
+
"""
|
|
262
|
+
result = []
|
|
263
|
+
i = 0
|
|
264
|
+
while i < len(inner):
|
|
265
|
+
if inner[i] == "'":
|
|
266
|
+
# Find matching closing quote
|
|
267
|
+
j = i + 1
|
|
268
|
+
while j < len(inner):
|
|
269
|
+
if inner[j] == "'":
|
|
270
|
+
result.append(inner[i + 1:j])
|
|
271
|
+
i = j + 1
|
|
272
|
+
break
|
|
273
|
+
j += 1
|
|
274
|
+
else:
|
|
275
|
+
break
|
|
276
|
+
elif inner[i] == '"':
|
|
277
|
+
j = i + 1
|
|
278
|
+
while j < len(inner):
|
|
279
|
+
if inner[j] == '"':
|
|
280
|
+
result.append(inner[i + 1:j])
|
|
281
|
+
i = j + 1
|
|
282
|
+
break
|
|
283
|
+
j += 1
|
|
284
|
+
else:
|
|
285
|
+
break
|
|
286
|
+
else:
|
|
287
|
+
i += 1
|
|
288
|
+
return result
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
def _get_constant_type_str(constant):
|
|
292
|
+
"""Get the ROS2 type string for a constant."""
|
|
293
|
+
if isinstance(constant.type, BasicType):
|
|
294
|
+
return _map_basic_type(constant.type.typename)
|
|
295
|
+
elif isinstance(constant.type, (UnboundedString, BoundedString)):
|
|
296
|
+
return 'string'
|
|
297
|
+
elif isinstance(constant.type, (UnboundedWString, BoundedWString)):
|
|
298
|
+
return 'wstring'
|
|
299
|
+
return str(constant.type)
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
def get_json_from_message(msg, pkg_name=None):
|
|
303
|
+
"""
|
|
304
|
+
Convert a rosidl_parser Message object to JSON spec matching
|
|
305
|
+
what parser.py produces for .msg files.
|
|
306
|
+
"""
|
|
307
|
+
namespaced_type = msg.structure.namespaced_type
|
|
308
|
+
namespaces = [str(ns) for ns in namespaced_type.namespaces]
|
|
309
|
+
msg_name = str(namespaced_type.name)
|
|
310
|
+
|
|
311
|
+
if pkg_name is None:
|
|
312
|
+
pkg_name = namespaces[0] if namespaces else ''
|
|
313
|
+
|
|
314
|
+
# Build fields
|
|
315
|
+
fields = []
|
|
316
|
+
for member in msg.structure.members:
|
|
317
|
+
field = {
|
|
318
|
+
'name': member.name,
|
|
319
|
+
'type': _get_type_json(member.type),
|
|
320
|
+
'default_value': _get_default_value(member),
|
|
321
|
+
}
|
|
322
|
+
fields.append(field)
|
|
323
|
+
|
|
324
|
+
# Build constants
|
|
325
|
+
constants = []
|
|
326
|
+
for constant in msg.constants:
|
|
327
|
+
constants.append({
|
|
328
|
+
'type': _get_constant_type_str(constant),
|
|
329
|
+
'name': constant.name,
|
|
330
|
+
'value': constant.value,
|
|
331
|
+
})
|
|
332
|
+
|
|
333
|
+
return {
|
|
334
|
+
'constants': constants,
|
|
335
|
+
'fields': fields,
|
|
336
|
+
'baseType': {
|
|
337
|
+
'pkgName': pkg_name,
|
|
338
|
+
'type': msg_name,
|
|
339
|
+
'stringUpperBound': None,
|
|
340
|
+
'isPrimitiveType': False,
|
|
341
|
+
},
|
|
342
|
+
'msgName': msg_name,
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
def get_json_from_service(srv):
|
|
347
|
+
"""
|
|
348
|
+
Convert a rosidl_parser Service object to JSON spec matching
|
|
349
|
+
what parser.py produces for .srv files.
|
|
350
|
+
"""
|
|
351
|
+
namespaces = [str(ns) for ns in srv.namespaced_type.namespaces]
|
|
352
|
+
pkg_name = namespaces[0] if namespaces else ''
|
|
353
|
+
srv_name = str(srv.namespaced_type.name)
|
|
354
|
+
|
|
355
|
+
return {
|
|
356
|
+
'pkgName': pkg_name,
|
|
357
|
+
'srvName': srv_name,
|
|
358
|
+
'request': get_json_from_message(srv.request_message, pkg_name),
|
|
359
|
+
'response': get_json_from_message(srv.response_message, pkg_name),
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
def get_json_from_action(action):
|
|
364
|
+
"""
|
|
365
|
+
Convert a rosidl_parser Action object to JSON spec matching
|
|
366
|
+
what parser.py produces for .action files.
|
|
367
|
+
"""
|
|
368
|
+
namespaces = [str(ns) for ns in action.namespaced_type.namespaces]
|
|
369
|
+
pkg_name = namespaces[0] if namespaces else ''
|
|
370
|
+
action_name = str(action.namespaced_type.name)
|
|
371
|
+
|
|
372
|
+
return {
|
|
373
|
+
'pkgName': pkg_name,
|
|
374
|
+
'actionName': action_name,
|
|
375
|
+
'goal': get_json_from_message(action.goal, pkg_name),
|
|
376
|
+
'result': get_json_from_message(action.result, pkg_name),
|
|
377
|
+
'feedback': get_json_from_message(action.feedback, pkg_name),
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
def parse_idl_file(idl_file_path):
|
|
382
|
+
"""
|
|
383
|
+
Parse an .idl file and return a JSON object in the same format
|
|
384
|
+
as parser.py produces.
|
|
385
|
+
|
|
386
|
+
The return value depends on the interface type:
|
|
387
|
+
- Message: same format as parse_message_file
|
|
388
|
+
- Service: same format as parse_service_file
|
|
389
|
+
- Action: same format as parse_action_file
|
|
390
|
+
|
|
391
|
+
Returns a dict with 'type' indicating the interface kind and 'spec'
|
|
392
|
+
containing the JSON spec.
|
|
393
|
+
"""
|
|
394
|
+
with open(idl_file_path, 'r') as f:
|
|
395
|
+
content_str = f.read()
|
|
396
|
+
|
|
397
|
+
content = parse_idl_string(content_str)
|
|
398
|
+
|
|
399
|
+
# Find the main element (Message, Service, or Action)
|
|
400
|
+
messages = content.get_elements_of_type(Message)
|
|
401
|
+
services = content.get_elements_of_type(Service)
|
|
402
|
+
actions = content.get_elements_of_type(Action)
|
|
403
|
+
|
|
404
|
+
if actions:
|
|
405
|
+
action = actions[0]
|
|
406
|
+
return {
|
|
407
|
+
'type': 'action',
|
|
408
|
+
'spec': get_json_from_action(action),
|
|
409
|
+
}
|
|
410
|
+
elif services:
|
|
411
|
+
service = services[0]
|
|
412
|
+
return {
|
|
413
|
+
'type': 'service',
|
|
414
|
+
'spec': get_json_from_service(service),
|
|
415
|
+
}
|
|
416
|
+
elif messages:
|
|
417
|
+
message = messages[0]
|
|
418
|
+
return {
|
|
419
|
+
'type': 'message',
|
|
420
|
+
'spec': get_json_from_message(message),
|
|
421
|
+
}
|
|
422
|
+
else:
|
|
423
|
+
raise ValueError(f'No Message, Service, or Action found in {idl_file_path}')
|
|
424
|
+
|
|
425
|
+
|
|
426
|
+
if __name__ == '__main__':
|
|
427
|
+
if len(sys.argv) < 2:
|
|
428
|
+
print('Usage: idl_parser.py <idl_file_path>', file=sys.stderr)
|
|
429
|
+
sys.exit(1)
|
|
430
|
+
|
|
431
|
+
try:
|
|
432
|
+
result = parse_idl_file(sys.argv[1])
|
|
433
|
+
print(json.dumps(result))
|
|
434
|
+
sys.exit(0)
|
|
435
|
+
except Exception as e:
|
|
436
|
+
print(str(e), file=sys.stderr)
|
|
437
|
+
sys.exit(1)
|
package/rosidl_parser/parser.py
CHANGED
|
@@ -15,7 +15,6 @@
|
|
|
15
15
|
import sys
|
|
16
16
|
import json
|
|
17
17
|
|
|
18
|
-
import rosidl_parser
|
|
19
18
|
from rosidl_adapter import parser
|
|
20
19
|
|
|
21
20
|
def get_json_object_from_base_type_object(base_type_obj):
|
|
@@ -41,15 +40,14 @@ def get_json_object_from_msg_spec_object(msg_spec_object):
|
|
|
41
40
|
for constant in msg_spec_object.constants:
|
|
42
41
|
constants.append({'type': constant.type, 'name': constant.name, 'value': constant.value})
|
|
43
42
|
|
|
44
|
-
msg_name = {'msgName': msg_spec_object.msg_name}
|
|
45
43
|
json_obj = {'constants': constants, 'fields': fields, 'baseType': get_json_object_from_base_type_object(msg_spec_object.base_type),
|
|
46
44
|
'msgName': msg_spec_object.msg_name}
|
|
47
45
|
|
|
48
46
|
return json_obj
|
|
49
47
|
|
|
50
48
|
if __name__ == '__main__':
|
|
51
|
-
if len(sys.argv) <
|
|
52
|
-
print('
|
|
49
|
+
if len(sys.argv) < 4:
|
|
50
|
+
print('Usage: {} <command> <packageName> <filePath>'.format(sys.argv[0]), file=sys.stderr)
|
|
53
51
|
sys.exit(1)
|
|
54
52
|
try:
|
|
55
53
|
parser_method = getattr(parser, sys.argv[1])
|
|
@@ -41,6 +41,33 @@ const rosidlParser = {
|
|
|
41
41
|
return this._parseFile('parse_action_file', packageName, filePath);
|
|
42
42
|
},
|
|
43
43
|
|
|
44
|
+
/**
|
|
45
|
+
* Parse an .idl file directly using rosidl_parser (no .msg conversion needed).
|
|
46
|
+
* Returns an object with { type: 'message'|'service'|'action', spec: ... }
|
|
47
|
+
* where spec matches the same format as parseMessageFile/parseServiceFile/parseActionFile.
|
|
48
|
+
*/
|
|
49
|
+
parseIdlFile(filePath) {
|
|
50
|
+
return new Promise((resolve, reject) => {
|
|
51
|
+
const args = [path.join(__dirname, 'idl_parser.py'), filePath];
|
|
52
|
+
const [pythonExecutableFile, pythonExecutableArgs] = pythonExecutable;
|
|
53
|
+
execFile(
|
|
54
|
+
pythonExecutableFile,
|
|
55
|
+
pythonExecutableArgs.concat(args),
|
|
56
|
+
(err, stdout, stderr) => {
|
|
57
|
+
if (err) {
|
|
58
|
+
reject(
|
|
59
|
+
new Error(
|
|
60
|
+
`There was an error parsing IDL file "${filePath}": "${err}"; stderr was: ${stderr}`
|
|
61
|
+
)
|
|
62
|
+
);
|
|
63
|
+
} else {
|
|
64
|
+
resolve(this._parseJSONObject(stdout));
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
);
|
|
68
|
+
});
|
|
69
|
+
},
|
|
70
|
+
|
|
44
71
|
_parseJSONObject(str) {
|
|
45
72
|
// For nodejs >= `contextSupportedVersion`, we leverage context parameter to
|
|
46
73
|
// convert unsafe integer to string, otherwise, json-bigint is used.
|
package/src/executor.cpp
CHANGED
|
@@ -110,6 +110,7 @@ void Executor::Stop() {
|
|
|
110
110
|
|
|
111
111
|
if (uv_is_active(reinterpret_cast<uv_handle_t*>(async_))) {
|
|
112
112
|
static bool handle_closed = false;
|
|
113
|
+
handle_closed = false;
|
|
113
114
|
uv_close(reinterpret_cast<uv_handle_t*>(async_),
|
|
114
115
|
[](uv_handle_t* async) -> void {
|
|
115
116
|
// Important Notice:
|
package/src/macros.h
CHANGED
|
@@ -22,8 +22,8 @@
|
|
|
22
22
|
#define CHECK_OP_AND_THROW_ERROR_IF_NOT_TRUE(op, lhs, rhs, message) \
|
|
23
23
|
{ \
|
|
24
24
|
if (lhs op rhs) { \
|
|
25
|
-
rcl_reset_error(); \
|
|
26
25
|
Napi::Error::New(env, message).ThrowAsJavaScriptException(); \
|
|
26
|
+
rcl_reset_error(); \
|
|
27
27
|
return env.Undefined(); \
|
|
28
28
|
} \
|
|
29
29
|
}
|
|
@@ -31,8 +31,8 @@
|
|
|
31
31
|
#define CHECK_OP_AND_THROW_ERROR_IF_NOT_TRUE_NO_RETURN(op, lhs, rhs, message) \
|
|
32
32
|
{ \
|
|
33
33
|
if (lhs op rhs) { \
|
|
34
|
-
rcl_reset_error(); \
|
|
35
34
|
Napi::Error::New(env, message).ThrowAsJavaScriptException(); \
|
|
35
|
+
rcl_reset_error(); \
|
|
36
36
|
} \
|
|
37
37
|
}
|
|
38
38
|
|
|
@@ -70,10 +70,17 @@ Napi::Value ActionCreateClient(const Napi::CallbackInfo& info) {
|
|
|
70
70
|
malloc(sizeof(rcl_action_client_t)));
|
|
71
71
|
*action_client = rcl_action_get_zero_initialized_client();
|
|
72
72
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
73
|
+
{
|
|
74
|
+
rcl_ret_t ret = rcl_action_client_init(
|
|
75
|
+
action_client, node, ts, action_name.c_str(), &action_client_ops);
|
|
76
|
+
if (RCL_RET_OK != ret) {
|
|
77
|
+
std::string error_msg = rcl_get_error_string().str;
|
|
78
|
+
rcl_reset_error();
|
|
79
|
+
free(action_client);
|
|
80
|
+
Napi::Error::New(env, error_msg).ThrowAsJavaScriptException();
|
|
81
|
+
return env.Undefined();
|
|
82
|
+
}
|
|
83
|
+
}
|
|
77
84
|
auto js_obj = RclHandle::NewInstance(
|
|
78
85
|
env, action_client, node_handle, [node, env](void* ptr) {
|
|
79
86
|
rcl_action_client_t* action_client =
|
|
@@ -125,7 +132,7 @@ Napi::Value ActionSendGoalRequest(const Napi::CallbackInfo& info) {
|
|
|
125
132
|
rcl_action_send_goal_request(action_client, buffer, &sequence_number),
|
|
126
133
|
RCL_RET_OK, rcl_get_error_string().str);
|
|
127
134
|
|
|
128
|
-
return Napi::Number::New(env, static_cast<
|
|
135
|
+
return Napi::Number::New(env, static_cast<double>(sequence_number));
|
|
129
136
|
}
|
|
130
137
|
|
|
131
138
|
Napi::Value ActionSendResultRequest(const Napi::CallbackInfo& info) {
|
|
@@ -142,7 +149,7 @@ Napi::Value ActionSendResultRequest(const Napi::CallbackInfo& info) {
|
|
|
142
149
|
rcl_action_send_result_request(action_client, buffer, &sequence_number),
|
|
143
150
|
RCL_RET_OK, rcl_get_error_string().str);
|
|
144
151
|
|
|
145
|
-
return Napi::Number::New(env, static_cast<
|
|
152
|
+
return Napi::Number::New(env, static_cast<double>(sequence_number));
|
|
146
153
|
}
|
|
147
154
|
|
|
148
155
|
Napi::Value ActionTakeFeedback(const Napi::CallbackInfo& info) {
|
|
@@ -156,9 +163,9 @@ Napi::Value ActionTakeFeedback(const Napi::CallbackInfo& info) {
|
|
|
156
163
|
|
|
157
164
|
rcl_ret_t ret = rcl_action_take_feedback(action_client, buffer);
|
|
158
165
|
if (ret != RCL_RET_OK && ret != RCL_RET_ACTION_CLIENT_TAKE_FAILED) {
|
|
159
|
-
|
|
160
|
-
.ThrowAsJavaScriptException();
|
|
166
|
+
std::string error_msg = rcl_get_error_string().str;
|
|
161
167
|
rcl_reset_error();
|
|
168
|
+
Napi::Error::New(env, error_msg).ThrowAsJavaScriptException();
|
|
162
169
|
return Napi::Boolean::New(env, false);
|
|
163
170
|
}
|
|
164
171
|
|
|
@@ -179,9 +186,9 @@ Napi::Value ActionTakeStatus(const Napi::CallbackInfo& info) {
|
|
|
179
186
|
|
|
180
187
|
rcl_ret_t ret = rcl_action_take_status(action_client, buffer);
|
|
181
188
|
if (ret != RCL_RET_OK && ret != RCL_RET_ACTION_CLIENT_TAKE_FAILED) {
|
|
189
|
+
std::string error_msg = rcl_get_error_string().str;
|
|
182
190
|
rcl_reset_error();
|
|
183
|
-
Napi::Error::New(env,
|
|
184
|
-
.ThrowAsJavaScriptException();
|
|
191
|
+
Napi::Error::New(env, error_msg).ThrowAsJavaScriptException();
|
|
185
192
|
return Napi::Boolean::New(env, false);
|
|
186
193
|
}
|
|
187
194
|
|
|
@@ -240,7 +247,7 @@ Napi::Value ActionSendCancelRequest(const Napi::CallbackInfo& info) {
|
|
|
240
247
|
rcl_action_send_cancel_request(action_client, buffer, &sequence_number),
|
|
241
248
|
RCL_RET_OK, rcl_get_error_string().str);
|
|
242
249
|
|
|
243
|
-
return Napi::Number::New(env, static_cast<
|
|
250
|
+
return Napi::Number::New(env, static_cast<double>(sequence_number));
|
|
244
251
|
}
|
|
245
252
|
|
|
246
253
|
#if ROS_VERSION >= 2505 // ROS2 >= Kilted
|