rclnodejs 0.32.4 → 0.33.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 CHANGED
@@ -45,7 +45,7 @@ npm i rclnodejs@x.y.z
45
45
 
46
46
  | RCLNODEJS Version | Compatible ROS 2 LTS |
47
47
  | :------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------: |
48
- | latest version (currently [v0.32.4](https://github.com/RobotWebTools/rclnodejs/tree/0.32.4)) | [Humble](https://github.com/RobotWebTools/rclnodejs/tree/humble-hawksbill)<br>[Jazzy](https://github.com/RobotWebTools/rclnodejs/tree/jazzy) |
48
+ | latest version (currently [v0.33.0](https://github.com/RobotWebTools/rclnodejs/tree/0.33.0)) | [Humble](https://github.com/RobotWebTools/rclnodejs/tree/humble-hawksbill)<br>[Jazzy](https://github.com/RobotWebTools/rclnodejs/tree/jazzy) |
49
49
 
50
50
  ## Documentation
51
51
 
package/binding.gyp CHANGED
@@ -12,6 +12,7 @@
12
12
  'runtime%': 'node',
13
13
  'ros_lib_dir': "<!(node -p \"require('./scripts/config.js').getROSLibPath()\")",
14
14
  'ros_include_root': "<!(node -p \"require('./scripts/config.js').getROSIncludeRootPath()\")",
15
+ 'node_major_version': '<!(node -p \"process.versions.node.split(\'.\')[0]\")',
15
16
  },
16
17
  'targets': [
17
18
  {
@@ -62,8 +63,21 @@
62
63
  'defines': [
63
64
  'OS_LINUX'
64
65
  ],
65
- 'cflags_cc': [
66
- '-std=c++20'
66
+ 'conditions': [
67
+ [
68
+ 'node_major_version >= 23', {
69
+ 'cflags_cc': [
70
+ '-std=c++20'
71
+ ]
72
+ }
73
+ ],
74
+ [
75
+ 'node_major_version < 23', {
76
+ 'cflags_cc': [
77
+ '-std=c++17'
78
+ ]
79
+ }
80
+ ]
67
81
  ]
68
82
  }
69
83
  ],
@@ -72,8 +86,21 @@
72
86
  'defines': [
73
87
  'OS_WINDOWS'
74
88
  ],
75
- 'cflags_cc': [
76
- '-std=c++20'
89
+ 'conditions': [
90
+ [
91
+ 'node_major_version >= 23', {
92
+ 'cflags_cc': [
93
+ '-std=c++20'
94
+ ]
95
+ }
96
+ ],
97
+ [
98
+ 'node_major_version < 23', {
99
+ 'cflags_cc': [
100
+ '-std=c++17'
101
+ ]
102
+ }
103
+ ]
77
104
  ],
78
105
  'include_dirs': [
79
106
  './src/third_party/dlfcn-win32/',
package/lib/node.js CHANGED
@@ -969,7 +969,7 @@ class Node extends rclnodejs.ShadowNode {
969
969
  }
970
970
 
971
971
  /**
972
- * Get the list of service topics discovered by the provided node for the remote node name.
972
+ * Get service names and types for which a remote node has servers.
973
973
  * @param {string} nodeName - The name of the node.
974
974
  * @param {string} namespace - The name of the namespace.
975
975
  * @return {Array<{name: string, types: Array<string>}>} - An array of the names and types.
@@ -982,6 +982,20 @@ class Node extends rclnodejs.ShadowNode {
982
982
  );
983
983
  }
984
984
 
985
+ /**
986
+ * Get service names and types for which a remote node has clients.
987
+ * @param {string} nodeName - The name of the node.
988
+ * @param {string} namespace - The name of the namespace.
989
+ * @return {Array<{name: string, types: Array<string>}>} - An array of the names and types.
990
+ */
991
+ getClientNamesAndTypesByNode(nodeName, namespace) {
992
+ return rclnodejs.getClientNamesAndTypesByNode(
993
+ this.handle,
994
+ nodeName,
995
+ namespace
996
+ );
997
+ }
998
+
985
999
  /**
986
1000
  * Get the list of topics discovered by the provided node.
987
1001
  * @param {boolean} noDemangle - If true topic names and types returned will not be demangled, default: false.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rclnodejs",
3
- "version": "0.32.4",
3
+ "version": "0.33.0",
4
4
  "description": "ROS2.0 JavaScript client with Node.js",
5
5
  "main": "index.js",
6
6
  "types": "types/index.d.ts",
@@ -31,6 +31,8 @@ const fs = require('fs');
31
31
  const loader = require('../lib/interface_loader.js');
32
32
  const pkgFilters = require('../rosidl_gen/filter.js');
33
33
 
34
+ const descriptorInterfaceNamespace = 'descriptor';
35
+
34
36
  async function generateAll() {
35
37
  // load pkg and interface info (msgs and srvs)
36
38
  const generatedPath = path.join(__dirname, '../generated/');
@@ -119,47 +121,30 @@ function savePkgInfoAsTSD(pkgInfos, fd) {
119
121
  for (const subfolder of pkgInfo.subfolders.keys()) {
120
122
  fs.writeSync(fd, ` namespace ${subfolder} {\n`);
121
123
 
122
- for (const rosInterface of pkgInfo.subfolders.get(subfolder)) {
123
- const type = rosInterface.type();
124
- const fullInterfaceName = `${type.pkgName}/${type.subFolder}/${type.interfaceName}`;
125
- const fullInterfacePath = `${type.pkgName}.${type.subFolder}.${type.interfaceName}`;
126
- const fullInterfaceConstructor = fullInterfacePath + 'Constructor';
127
-
128
- if (isMsgInterface(rosInterface)) {
129
- // create message interface
130
- saveMsgAsTSD(rosInterface, fd);
131
- saveMsgConstructorAsTSD(rosInterface, fd);
132
- messagesMap[fullInterfaceName] = fullInterfacePath;
133
- } else if (isSrvInterface(rosInterface)) {
134
- if (
135
- !isValidService(rosInterface, pkgInfo.subfolders.get(subfolder))
136
- ) {
137
- let type = rosInterface.type();
138
- console.log(
139
- `Incomplete service: ${type.pkgName}.${type.subFolder}.${type.interfaceName}.`
140
- );
141
- continue;
142
- }
143
-
144
- // create service interface
145
- saveSrvAsTSD(rosInterface, fd);
146
- if (!isInternalActionSrvInterface(rosInterface)) {
147
- servicesMap[fullInterfaceName] = fullInterfaceConstructor;
148
- }
149
- } else if (isActionInterface(rosInterface)) {
150
- if (!isValidAction(rosInterface, pkgInfo.subfolders.get(subfolder))) {
151
- let type = rosInterface.type();
152
- console.log(
153
- `Incomplete action: ${type.pkgName}.${type.subFolder}.${type.interfaceName}.`
154
- );
155
- continue;
156
- }
157
-
158
- // create action interface
159
- saveActionAsTSD(rosInterface, fd);
160
- actionsMap[fullInterfaceName] = fullInterfaceConstructor;
161
- }
162
- }
124
+ // generate real msg/srv/action interfaces
125
+ generateRosMsgInterfaces(
126
+ pkgInfo,
127
+ subfolder,
128
+ messagesMap,
129
+ servicesMap,
130
+ actionsMap,
131
+ fd
132
+ );
133
+
134
+ // generate descriptor msg/srv/action interfaces
135
+ fs.writeSync(fd, ` namespace ${descriptorInterfaceNamespace} {\n`);
136
+ const willGenerateDescriptorInterface = true;
137
+ generateRosMsgInterfaces(
138
+ pkgInfo,
139
+ subfolder,
140
+ messagesMap,
141
+ servicesMap,
142
+ actionsMap,
143
+ fd,
144
+ willGenerateDescriptorInterface
145
+ );
146
+ // close namespace descriptor declare
147
+ fs.writeSync(fd, ' }\n');
163
148
 
164
149
  // close namespace declare
165
150
  fs.writeSync(fd, ' }\n');
@@ -238,16 +223,96 @@ function savePkgInfoAsTSD(pkgInfos, fd) {
238
223
  fs.writeSync(fd, '}\n');
239
224
  }
240
225
 
241
- function saveMsgAsTSD(rosMsgInterface, fd) {
242
- fs.writeSync(
243
- fd,
244
- ` export interface ${rosMsgInterface.type().interfaceName} {\n`
226
+ function generateRosMsgInterfaces(
227
+ pkgInfo,
228
+ subfolder,
229
+ messagesMap,
230
+ servicesMap,
231
+ actionsMap,
232
+ fd,
233
+ willGenerateDescriptorInterface = false
234
+ ) {
235
+ const descriptorNamespaceName = willGenerateDescriptorInterface
236
+ ? `${descriptorInterfaceNamespace}/`
237
+ : '';
238
+ const descriptorNamespacePath = willGenerateDescriptorInterface
239
+ ? `${descriptorInterfaceNamespace}.`
240
+ : '';
241
+ for (const rosInterface of pkgInfo.subfolders.get(subfolder)) {
242
+ const type = rosInterface.type();
243
+ const fullInterfaceName = `${type.pkgName}/${type.subFolder}/${descriptorNamespaceName}${type.interfaceName}`;
244
+ const fullInterfacePath = `${type.pkgName}.${type.subFolder}.${descriptorNamespacePath}${type.interfaceName}`;
245
+ const fullInterfaceConstructor = fullInterfacePath + 'Constructor';
246
+
247
+ const indentStartLevel = willGenerateDescriptorInterface ? 4 : 3;
248
+ if (isMsgInterface(rosInterface)) {
249
+ // create message interface
250
+ saveMsgAsTSD(
251
+ rosInterface,
252
+ fd,
253
+ indentStartLevel,
254
+ willGenerateDescriptorInterface
255
+ );
256
+ saveMsgConstructorAsTSD(rosInterface, fd, indentStartLevel);
257
+ messagesMap[fullInterfaceName] = fullInterfacePath;
258
+ } else if (isSrvInterface(rosInterface)) {
259
+ if (!isValidService(rosInterface, pkgInfo.subfolders.get(subfolder))) {
260
+ let type = rosInterface.type();
261
+ console.log(
262
+ `Incomplete service: ${type.pkgName}.${type.subFolder}.${type.interfaceName}.`
263
+ );
264
+ continue;
265
+ }
266
+
267
+ // create service interface
268
+ saveSrvAsTSD(rosInterface, fd, indentStartLevel);
269
+ if (!isInternalActionSrvInterface(rosInterface)) {
270
+ servicesMap[fullInterfaceName] = fullInterfaceConstructor;
271
+ }
272
+ } else if (isActionInterface(rosInterface)) {
273
+ if (!isValidAction(rosInterface, pkgInfo.subfolders.get(subfolder))) {
274
+ let type = rosInterface.type();
275
+ console.log(
276
+ `Incomplete action: ${type.pkgName}.${type.subFolder}.${type.interfaceName}.`
277
+ );
278
+ continue;
279
+ }
280
+
281
+ // create action interface
282
+ saveActionAsTSD(rosInterface, fd, indentStartLevel);
283
+ actionsMap[fullInterfaceName] = fullInterfaceConstructor;
284
+ }
285
+ }
286
+ }
287
+
288
+ function saveMsgAsTSD(
289
+ rosMsgInterface,
290
+ fd,
291
+ indentLevel = 3,
292
+ willGenerateDescriptorInterface = false
293
+ ) {
294
+ const outerIndentSpacing = getIndentSpacing(indentLevel);
295
+ const tmpl = indentString(
296
+ `export interface ${rosMsgInterface.type().interfaceName} {\n`,
297
+ outerIndentSpacing
245
298
  );
299
+ fs.writeSync(fd, tmpl);
246
300
  const useSamePkg =
247
301
  isInternalActionMsgInterface(rosMsgInterface) ||
248
302
  isInternalServiceEventMsgInterface(rosMsgInterface);
249
- saveMsgFieldsAsTSD(rosMsgInterface, fd, 8, ';', '', useSamePkg);
250
- fs.writeSync(fd, ' }\n');
303
+ const innerIndentLevel = indentLevel + 1;
304
+ const innerIndentSpacing = getIndentSpacing(innerIndentLevel);
305
+ saveMsgFieldsAsTSD(
306
+ rosMsgInterface,
307
+ fd,
308
+ innerIndentSpacing,
309
+ ';',
310
+ '',
311
+ useSamePkg,
312
+ willGenerateDescriptorInterface
313
+ );
314
+ const tmplEnd = indentString('}\n', outerIndentSpacing);
315
+ fs.writeSync(fd, tmplEnd);
251
316
  }
252
317
 
253
318
  /**
@@ -261,6 +326,7 @@ function saveMsgAsTSD(rosMsgInterface, fd) {
261
326
  * @param {string} typePrefix The prefix to put before the type name for
262
327
  * non-primitive types
263
328
  * @param {boolean} useSamePackageSubFolder Indicates if the sub folder name should be taken from the message
329
+ * @param {boolean} willGenerateDescriptorInterface Indicates if descriptor interface is being generated
264
330
  * when the field type comes from the same package. This is needed for action interfaces. Defaults to false.
265
331
  * @returns {undefined}
266
332
  */
@@ -270,7 +336,8 @@ function saveMsgFieldsAsTSD(
270
336
  indent = 0,
271
337
  lineEnd = ',',
272
338
  typePrefix = '',
273
- useSamePackageSubFolder = false
339
+ useSamePackageSubFolder = false,
340
+ willGenerateDescriptorInterface = false
274
341
  ) {
275
342
  let type = rosMsgInterface.type();
276
343
  let fields = rosMsgInterface.ROSMessageDef.fields;
@@ -280,49 +347,62 @@ function saveMsgFieldsAsTSD(
280
347
  useSamePackageSubFolder && field.type.pkgName === type.pkgName
281
348
  ? type.subFolder
282
349
  : 'msg';
283
- let fieldType = fieldType2JSName(field, subFolder);
350
+ let fieldType = fieldType2JSName(
351
+ field,
352
+ subFolder,
353
+ willGenerateDescriptorInterface
354
+ );
284
355
  let tp = field.type.isPrimitiveType ? '' : typePrefix;
285
356
  if (typePrefix === 'rclnodejs.') {
286
357
  fieldType = 'any';
287
358
  tp = '';
288
359
  }
289
360
 
290
- const tmpl = indentString(`${field.name}: ${tp}${fieldType}`, indent);
291
- fs.writeSync(fd, tmpl);
361
+ let arrayString = '';
292
362
  if (field.type.isArray) {
293
- fs.writeSync(fd, '[]');
363
+ arrayString = '[]';
364
+
365
+ if (field.type.isFixedSizeArray && willGenerateDescriptorInterface) {
366
+ arrayString = `[${field.type.arraySize}]`;
367
+ }
294
368
 
295
- if (fieldType === 'number') {
369
+ if (fieldType === 'number' && !willGenerateDescriptorInterface) {
296
370
  // for number[] include alternate typed-array types, e.g., number[] | uint8[]
297
371
  let jsTypedArrayName = fieldTypeArray2JSTypedArrayName(field.type.type);
298
372
 
299
373
  if (jsTypedArrayName) {
300
- fs.writeSync(fd, ` | ${jsTypedArrayName}`);
374
+ arrayString += ` | ${jsTypedArrayName}`;
301
375
  }
302
376
  }
303
377
  }
378
+ const fieldString = willGenerateDescriptorInterface
379
+ ? `${field.name}: '${tp}${fieldType}${arrayString}'`
380
+ : `${field.name}: ${tp}${fieldType}${arrayString}`;
381
+ const tmpl = indentString(fieldString, indent);
382
+ fs.writeSync(fd, tmpl);
304
383
 
305
384
  fs.writeSync(fd, lineEnd);
306
385
  fs.writeSync(fd, '\n');
307
386
  }
308
387
  }
309
388
 
310
- function saveMsgConstructorAsTSD(rosMsgInterface, fd) {
389
+ function saveMsgConstructorAsTSD(rosMsgInterface, fd, indentLevel = 3) {
311
390
  const type = rosMsgInterface.type();
312
391
  const msgName = type.interfaceName;
313
-
314
- fs.writeSync(fd, ` export interface ${msgName}Constructor {\n`);
392
+ let interfaceTmpl = [`export interface ${msgName}Constructor {`];
315
393
 
316
394
  for (const constant of rosMsgInterface.ROSMessageDef.constants) {
317
395
  const constantType = primitiveType2JSName(constant.type);
318
- fs.writeSync(fd, ` readonly ${constant.name}: ${constantType};\n`);
396
+ interfaceTmpl.push(` readonly ${constant.name}: ${constantType};`);
319
397
  }
320
-
321
- fs.writeSync(fd, ` new(other?: ${msgName}): ${msgName};\n`);
322
- fs.writeSync(fd, ' }\n');
398
+ interfaceTmpl.push(` new(other?: ${msgName}): ${msgName};`);
399
+ interfaceTmpl.push('}');
400
+ interfaceTmpl.push('');
401
+ const indentSpacing = getIndentSpacing(indentLevel);
402
+ fs.writeSync(fd, indentLines(interfaceTmpl, indentSpacing).join('\n'));
323
403
  }
324
404
 
325
- function saveSrvAsTSD(rosSrvInterface, fd) {
405
+ function saveSrvAsTSD(rosSrvInterface, fd, indentLevel = 3) {
326
406
  const serviceName = rosSrvInterface.type().interfaceName;
327
407
 
328
408
  const interfaceTemplate = [
@@ -332,11 +412,11 @@ function saveSrvAsTSD(rosSrvInterface, fd) {
332
412
  '}',
333
413
  '',
334
414
  ];
335
-
336
- fs.writeSync(fd, indentLines(interfaceTemplate, 6).join('\n'));
415
+ const indentSpacing = getIndentSpacing(indentLevel);
416
+ fs.writeSync(fd, indentLines(interfaceTemplate, indentSpacing).join('\n'));
337
417
  }
338
418
 
339
- function saveActionAsTSD(rosActionInterface, fd) {
419
+ function saveActionAsTSD(rosActionInterface, fd, indentLevel = 3) {
340
420
  const actionName = rosActionInterface.type().interfaceName;
341
421
 
342
422
  const interfaceTemplate = [
@@ -347,8 +427,19 @@ function saveActionAsTSD(rosActionInterface, fd) {
347
427
  '}',
348
428
  '',
349
429
  ];
430
+ const indentSpacing = getIndentSpacing(indentLevel);
431
+ fs.writeSync(fd, indentLines(interfaceTemplate, indentSpacing).join('\n'));
432
+ }
350
433
 
351
- fs.writeSync(fd, indentLines(interfaceTemplate, 6).join('\n'));
434
+ /**
435
+ * Get number of indent spaces for given level
436
+ *
437
+ * @param {*} indentLevel Indention level
438
+ * @param {*} spacesPerLevel Number of spaces per level
439
+ * @returns Total number of space
440
+ */
441
+ function getIndentSpacing(indentLevel, spacesPerLevel = 2) {
442
+ return indentLevel * spacesPerLevel;
352
443
  }
353
444
 
354
445
  function isMsgInterface(rosInterface) {
@@ -451,7 +542,16 @@ function isValidAction(rosActionInterface, infos) {
451
542
  return matches === SUCCESS_MATCH_COUNT;
452
543
  }
453
544
 
454
- function fieldType2JSName(fieldInfo, subFolder = 'msg') {
545
+ function fieldType2JSName(
546
+ fieldInfo,
547
+ subFolder = 'msg',
548
+ willGenerateDescriptorInterface = false
549
+ ) {
550
+ if (willGenerateDescriptorInterface) {
551
+ return fieldInfo.type.isPrimitiveType
552
+ ? `${fieldInfo.type.type}`
553
+ : `${fieldInfo.type.pkgName}/${subFolder}/${fieldInfo.type.type}`;
554
+ }
455
555
  return fieldInfo.type.isPrimitiveType
456
556
  ? primitiveType2JSName(fieldInfo.type.type)
457
557
  : `${fieldInfo.type.pkgName}.${subFolder}.${fieldInfo.type.type}`;
@@ -45,7 +45,7 @@ npm i rclnodejs@x.y.z
45
45
 
46
46
  | RCLNODEJS Version | Compatible ROS 2 LTS |
47
47
  | :------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------: |
48
- | latest version (currently [v0.32.4](https://github.com/RobotWebTools/rclnodejs/tree/0.32.4)) | [Humble](https://github.com/RobotWebTools/rclnodejs/tree/humble-hawksbill)<br>[Jazzy](https://github.com/RobotWebTools/rclnodejs/tree/jazzy) |
48
+ | latest version (currently [v0.33.0](https://github.com/RobotWebTools/rclnodejs/tree/0.33.0)) | [Humble](https://github.com/RobotWebTools/rclnodejs/tree/humble-hawksbill)<br>[Jazzy](https://github.com/RobotWebTools/rclnodejs/tree/jazzy) |
49
49
 
50
50
  ## Documentation
51
51
 
@@ -1738,15 +1738,45 @@ NAN_METHOD(GetServiceNamesAndTypesByNode) {
1738
1738
  rcl_get_service_names_and_types_by_node(
1739
1739
  node, &allocator, node_name.c_str(), node_namespace.c_str(),
1740
1740
  &service_names_and_types),
1741
- "Failed to get_publisher_names_and_types.");
1741
+ "Failed to get_service_names_and_types.");
1742
1742
 
1743
1743
  v8::Local<v8::Array> result_list =
1744
1744
  Nan::New<v8::Array>(service_names_and_types.names.size);
1745
1745
  ExtractNamesAndTypes(service_names_and_types, &result_list);
1746
1746
 
1747
+ THROW_ERROR_IF_NOT_EQUAL(
1748
+ RCL_RET_OK, rcl_names_and_types_fini(&service_names_and_types),
1749
+ "Failed to destroy rcl_get_zero_initialized_names_and_types");
1750
+
1751
+ info.GetReturnValue().Set(result_list);
1752
+ }
1753
+
1754
+ NAN_METHOD(GetClientNamesAndTypesByNode) {
1755
+ v8::Local<v8::Context> currentContent = Nan::GetCurrentContext();
1756
+ RclHandle* node_handle = RclHandle::Unwrap<RclHandle>(
1757
+ Nan::To<v8::Object>(info[0]).ToLocalChecked());
1758
+ rcl_node_t* node = reinterpret_cast<rcl_node_t*>(node_handle->ptr());
1759
+ std::string node_name =
1760
+ *Nan::Utf8String(info[1]->ToString(currentContent).ToLocalChecked());
1761
+ std::string node_namespace =
1762
+ *Nan::Utf8String(info[2]->ToString(currentContent).ToLocalChecked());
1763
+
1764
+ rcl_names_and_types_t client_names_and_types =
1765
+ rcl_get_zero_initialized_names_and_types();
1766
+ rcl_allocator_t allocator = rcl_get_default_allocator();
1747
1767
  THROW_ERROR_IF_NOT_EQUAL(RCL_RET_OK,
1748
- rcl_names_and_types_fini(&service_names_and_types),
1749
- "Failed to destroy topic_names_and_types");
1768
+ rcl_get_client_names_and_types_by_node(
1769
+ node, &allocator, node_name.c_str(),
1770
+ node_namespace.c_str(), &client_names_and_types),
1771
+ "Failed to get_client_names_and_types.");
1772
+
1773
+ v8::Local<v8::Array> result_list =
1774
+ Nan::New<v8::Array>(client_names_and_types.names.size);
1775
+ ExtractNamesAndTypes(client_names_and_types, &result_list);
1776
+
1777
+ THROW_ERROR_IF_NOT_EQUAL(
1778
+ RCL_RET_OK, rcl_names_and_types_fini(&client_names_and_types),
1779
+ "Failed to destroy rcl_get_zero_initialized_names_and_types");
1750
1780
 
1751
1781
  info.GetReturnValue().Set(result_list);
1752
1782
  }
@@ -2033,6 +2063,7 @@ std::vector<BindingMethod> binding_methods = {
2033
2063
  {"getPublisherNamesAndTypesByNode", GetPublisherNamesAndTypesByNode},
2034
2064
  {"getSubscriptionNamesAndTypesByNode", GetSubscriptionNamesAndTypesByNode},
2035
2065
  {"getServiceNamesAndTypesByNode", GetServiceNamesAndTypesByNode},
2066
+ {"getClientNamesAndTypesByNode", GetClientNamesAndTypesByNode},
2036
2067
  {"getTopicNamesAndTypes", GetTopicNamesAndTypes},
2037
2068
  {"getServiceNamesAndTypes", GetServiceNamesAndTypes},
2038
2069
  {"getNodeNames", GetNodeNames},