rclnodejs 0.29.0 → 0.31.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/lib/time.js CHANGED
@@ -17,7 +17,7 @@
17
17
  const rclnodejs = require('bindings')('rclnodejs');
18
18
  const Duration = require('./duration.js');
19
19
  const ClockType = require('./clock_type.js');
20
- const int64 = require('int64-napi');
20
+ const S_TO_NS = 10n ** 9n;
21
21
 
22
22
  /**
23
23
  * @class - Class representing a Time in ROS
@@ -26,16 +26,20 @@ const int64 = require('int64-napi');
26
26
  class Time {
27
27
  /**
28
28
  * Create a Time.
29
- * @param {number|string} [seconds=0] - The second part of the time.
30
- * @param {number|string} [nanoseconds=0] - The nanosecond part of the time.
29
+ * @param {bigint} [seconds=0] - The second part of the time.
30
+ * @param {bigint} [nanoseconds=0] - The nanosecond part of the time.
31
31
  * @param {ClockType} [clockType=Clock.ClockType.SYSTEM_TIME] - The clock type.
32
32
  */
33
- constructor(seconds = 0, nanoseconds = 0, clockType = ClockType.SYSTEM_TIME) {
34
- if (typeof seconds !== 'number' && typeof seconds !== 'string') {
33
+ constructor(
34
+ seconds = 0n,
35
+ nanoseconds = 0n,
36
+ clockType = ClockType.SYSTEM_TIME
37
+ ) {
38
+ if (typeof seconds !== 'bigint') {
35
39
  throw new TypeError('Invalid argument of seconds');
36
40
  }
37
41
 
38
- if (typeof nanoseconds !== 'number' && typeof nanoseconds !== 'string') {
42
+ if (typeof nanoseconds !== 'bigint') {
39
43
  throw new TypeError('Invalid argument of nanoseconds');
40
44
  }
41
45
 
@@ -43,25 +47,22 @@ class Time {
43
47
  throw new TypeError('Invalid argument of clockType');
44
48
  }
45
49
 
46
- if (
47
- int64.lt(seconds, 0) ||
48
- (typeof seconds === 'string' && seconds.startsWith('-'))
49
- ) {
50
+ if (seconds < 0n) {
50
51
  throw new RangeError('seconds value must not be negative');
51
52
  }
52
53
 
53
- if (
54
- int64.lt(nanoseconds, 0) ||
55
- (typeof nanoseconds === 'string' && nanoseconds.startsWith('-'))
56
- ) {
54
+ if (nanoseconds < 0n) {
57
55
  throw new RangeError('nanoseconds value must not be negative');
58
56
  }
59
57
 
60
- this._nanoseconds = int64.from(seconds).multiply(1e9).add(nanoseconds);
61
- this._handle = rclnodejs.createTimePoint(
62
- this._nanoseconds.toString(),
63
- clockType
64
- );
58
+ const total = seconds * S_TO_NS + nanoseconds;
59
+ if (total >= 2n ** 63n) {
60
+ throw new RangeError(
61
+ 'Total nanoseconds value is too large to store in C time point.'
62
+ );
63
+ }
64
+ this._nanoseconds = total;
65
+ this._handle = rclnodejs.createTimePoint(this._nanoseconds, clockType);
65
66
  this._clockType = clockType;
66
67
  }
67
68
 
@@ -80,22 +81,11 @@ class Time {
80
81
  * Get the nanosecond part of the time.
81
82
  * @name Time#get:nanoseconds
82
83
  * @function
83
- * @return {number|string} - value in nanosecond, if the value is greater than Number.MAX_SAFE_INTEGER (2^53-1), will be presented in string of decimal format.
84
+ * @return {bigint} - value in nanosecond.
84
85
  */
85
86
 
86
87
  get nanoseconds() {
87
- let str = rclnodejs.getNanoseconds(this._handle);
88
- let nano;
89
-
90
- if (str.startsWith('-')) {
91
- nano = int64.negative(int64.from(str));
92
- } else {
93
- nano = int64.from(str);
94
- }
95
- if (Number.isFinite(nano.toNumber())) {
96
- return nano.toNumber();
97
- }
98
- return nano.toString();
88
+ return rclnodejs.getNanoseconds(this._handle);
99
89
  }
100
90
 
101
91
  /**
@@ -106,9 +96,11 @@ class Time {
106
96
  */
107
97
 
108
98
  get secondsAndNanoseconds() {
109
- const seconds = int64.from(this._nanoseconds).divide(1e9).toNumber();
110
- const nanoseconds = int64.from(this._nanoseconds).mod(1e9).toNumber();
111
- return { seconds, nanoseconds };
99
+ const nanoseconds = this._nanoseconds;
100
+ return {
101
+ seconds: nanoseconds / S_TO_NS,
102
+ nanoseconds: nanoseconds % S_TO_NS,
103
+ };
112
104
  }
113
105
 
114
106
  /**
@@ -119,8 +111,8 @@ class Time {
119
111
  add(other) {
120
112
  if (other instanceof Duration) {
121
113
  return new Time(
122
- 0,
123
- int64.add(this._nanoseconds, other.nanoseconds).toString(),
114
+ 0n,
115
+ this._nanoseconds + other.nanoseconds,
124
116
  this._clockType
125
117
  );
126
118
  }
@@ -137,14 +129,11 @@ class Time {
137
129
  if (other._clockType !== this._clockType) {
138
130
  throw new TypeError("Can't subtract times with different clock types");
139
131
  }
140
- return new Duration(
141
- 0,
142
- int64.subtract(this._nanoseconds, other._nanoseconds).toString()
143
- );
132
+ return new Duration(0n, this._nanoseconds - other._nanoseconds);
144
133
  } else if (other instanceof Duration) {
145
134
  return new Time(
146
- 0,
147
- int64.subtract(this._nanoseconds, other._nanoseconds).toString(),
135
+ 0n,
136
+ this._nanoseconds - other._nanoseconds,
148
137
  this._clockType
149
138
  );
150
139
  }
@@ -161,7 +150,7 @@ class Time {
161
150
  if (other._clockType !== this._clockType) {
162
151
  throw new TypeError("Can't compare times with different clock types");
163
152
  }
164
- return this._nanoseconds.eq(other.nanoseconds);
153
+ return this._nanoseconds === other.nanoseconds;
165
154
  }
166
155
  throw new TypeError('Invalid argument');
167
156
  }
@@ -176,7 +165,7 @@ class Time {
176
165
  if (other._clockType !== this._clockType) {
177
166
  throw new TypeError("Can't compare times with different clock types");
178
167
  }
179
- return this._nanoseconds.ne(other.nanoseconds);
168
+ return this._nanoseconds !== other.nanoseconds;
180
169
  }
181
170
  }
182
171
 
@@ -190,7 +179,7 @@ class Time {
190
179
  if (other._clockType !== this._clockType) {
191
180
  throw new TypeError("Can't compare times with different clock types");
192
181
  }
193
- return this._nanoseconds.lt(other.nanoseconds);
182
+ return this._nanoseconds < other.nanoseconds;
194
183
  }
195
184
  throw new TypeError('Invalid argument');
196
185
  }
@@ -205,7 +194,7 @@ class Time {
205
194
  if (other._clockType !== this._clockType) {
206
195
  throw new TypeError("Can't compare times with different clock types");
207
196
  }
208
- return this._nanoseconds.lte(other.nanoseconds);
197
+ return this._nanoseconds <= other.nanoseconds;
209
198
  }
210
199
  throw new TypeError('Invalid argument');
211
200
  }
@@ -220,7 +209,7 @@ class Time {
220
209
  if (other._clockType !== this._clockType) {
221
210
  throw new TypeError("Can't compare times with different clock types");
222
211
  }
223
- return this._nanoseconds.gt(other.nanoseconds);
212
+ return this._nanoseconds > other.nanoseconds;
224
213
  }
225
214
  throw new TypeError('Invalid argument');
226
215
  }
@@ -235,7 +224,7 @@ class Time {
235
224
  if (other._clockType !== this._clockType) {
236
225
  throw new TypeError("Can't compare times with different clock types");
237
226
  }
238
- return this._nanoseconds.gte(other.nanoseconds);
227
+ return this._nanoseconds >= other.nanoseconds;
239
228
  }
240
229
  throw new TypeError('Invalid argument');
241
230
  }
@@ -261,7 +250,7 @@ class Time {
261
250
  * @return {Time} Return the created Time object.
262
251
  */
263
252
  static fromMsg(msg, clockType = ClockType.ROS_TIME) {
264
- return new Time(msg.sec, msg.nanosec, clockType);
253
+ return new Time(BigInt(msg.sec), BigInt(msg.nanosec), clockType);
265
254
  }
266
255
  }
267
256
 
@@ -37,7 +37,7 @@ class TimeSource {
37
37
  this._node = node;
38
38
  this._associatedClocks = [];
39
39
  this._clockSubscription = undefined;
40
- this._lastTimeSet = new Time(0, 0, ClockType.ROS_TIME);
40
+ this._lastTimeSet = new Time(0n, 0n, ClockType.ROS_TIME);
41
41
  this._isRosTimeActive = false;
42
42
 
43
43
  if (this._node) {
package/lib/timer.js CHANGED
@@ -29,7 +29,7 @@ class Timer {
29
29
  }
30
30
 
31
31
  /**
32
- * @type {number}
32
+ * @type {bigint} - The period of the timer in nanoseconds.
33
33
  */
34
34
  get period() {
35
35
  return this._period;
@@ -73,18 +73,18 @@ class Timer {
73
73
 
74
74
  /**
75
75
  * Get the interval since the last call of this timer.
76
- * @return {number} - the interval value - ms.
76
+ * @return {bigint} - the interval value in nanoseconds.
77
77
  */
78
78
  timeSinceLastCall() {
79
- return parseInt(rclnodejs.timerGetTimeSinceLastCall(this._handle), 10);
79
+ return rclnodejs.timerGetTimeSinceLastCall(this._handle);
80
80
  }
81
81
 
82
82
  /**
83
83
  * Get the interval until the next call will happen.
84
- * @return {number} - the interval value - ms.
84
+ * @return {bigint} - the interval value in nanoseconds.
85
85
  */
86
86
  timeUntilNextCall() {
87
- return parseInt(rclnodejs.timerGetTimeUntilNextCall(this._handle), 10);
87
+ return rclnodejs.timerGetTimeUntilNextCall(this._handle);
88
88
  }
89
89
  }
90
90
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rclnodejs",
3
- "version": "0.29.0",
3
+ "version": "0.31.0",
4
4
  "description": "ROS2.0 JavaScript client with Node.js",
5
5
  "main": "index.js",
6
6
  "types": "types/index.d.ts",
@@ -50,10 +50,10 @@
50
50
  "@typescript-eslint/parser": "^8.18.0",
51
51
  "babel-eslint": "^10.1.0",
52
52
  "clang-format": "^1.8.0",
53
- "commander": "^12.1.0",
53
+ "commander": "^13.1.0",
54
54
  "deep-equal": "^1.1.1",
55
55
  "eslint": "^9.16.0",
56
- "eslint-config-prettier": "^9.1.0",
56
+ "eslint-config-prettier": "^10.0.1",
57
57
  "eslint-plugin-prettier": "^5.2.1",
58
58
  "husky": "^9.1.7",
59
59
  "jsdoc": "^4.0.4",
@@ -63,7 +63,6 @@
63
63
  "tree-kill": "^1.2.2",
64
64
  "typescript": "^5.7.2"
65
65
  },
66
- "//": "Pin int64-napi to ^1.0.2",
67
66
  "dependencies": {
68
67
  "@rclnodejs/ref-array-di": "^1.2.2",
69
68
  "@rclnodejs/ref-napi": "^4.0.0",
@@ -75,7 +74,7 @@
75
74
  "dot": "^1.1.3",
76
75
  "dtslint": "^4.2.1",
77
76
  "fs-extra": "^11.2.0",
78
- "int64-napi": "^1.0.2",
77
+ "json-bigint": "^1.0.0",
79
78
  "is-close": "^1.3.3",
80
79
  "mkdirp": "^3.0.1",
81
80
  "mz": "^2.7.0",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rosidl-generator",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "Generate JavaScript object from ROS IDL(.msg) files",
5
5
  "main": "index.js",
6
6
  "authors": [
@@ -27,7 +27,12 @@ function copyMsgObject(msg, obj) {
27
27
  for (let i in obj) {
28
28
  if (msg.hasMember(i)) {
29
29
  const type = typeof obj[i];
30
- if (type === 'string' || type === 'number' || type === 'boolean') {
30
+ if (
31
+ type === 'string' ||
32
+ type === 'number' ||
33
+ type === 'boolean' ||
34
+ type === 'bigint'
35
+ ) {
31
36
  // A primitive-type value
32
37
  msg[i] = obj[i];
33
38
  } else if (Array.isArray(obj[i]) || isTypedArray(obj[i])) {
@@ -80,17 +85,20 @@ function verifyMessage(message, obj) {
80
85
  case 'char':
81
86
  case 'int16':
82
87
  case 'int32':
83
- case 'int64':
84
88
  case 'byte':
85
89
  case 'uint16':
86
90
  case 'uint32':
87
- case 'uint64':
88
91
  case 'float32':
89
92
  case 'float64':
90
93
  if (typeof obj[name] != 'number') {
91
94
  return false;
92
95
  }
93
96
  break;
97
+ case 'int64':
98
+ case 'uint64':
99
+ if (typeof obj[name] != 'bigint') {
100
+ return false;
101
+ }
94
102
  case 'bool':
95
103
  if (typeof obj[name] != 'boolean') {
96
104
  return false;
@@ -171,7 +179,12 @@ function toROSMessage(TypeClass, obj) {
171
179
 
172
180
  function constructFromPlanObject(msg, obj) {
173
181
  const type = typeof obj;
174
- if (type === 'string' || type === 'number' || type === 'boolean') {
182
+ if (
183
+ type === 'string' ||
184
+ type === 'number' ||
185
+ type === 'boolean' ||
186
+ type === 'bigint'
187
+ ) {
175
188
  msg.data = obj;
176
189
  } else if (type === 'object') {
177
190
  copyMsgObject(msg, obj);
@@ -161,9 +161,7 @@ async function generateMsgForSrv(filePath, interfaceInfo, pkgMap) {
161
161
  async function addInterfaceInfos(filePath, dir, pkgMap) {
162
162
  const interfaceInfo = grabInterfaceInfo(filePath, true);
163
163
  const ignore = pkgFilters.matchesAny(interfaceInfo);
164
- if (ignore) {
165
- console.log('Omitting filtered interface: ', interfaceInfo);
166
- } else {
164
+ if (!ignore) {
167
165
  if (path.extname(filePath) === '.msg') {
168
166
  // Some .msg files were generated prior to 0.3.2 for .action files,
169
167
  // which has been disabled. So these files should be ignored here.
@@ -232,9 +230,7 @@ async function findPackagesInDirectory(dir) {
232
230
  amentExecuted
233
231
  );
234
232
  const ignore = pkgFilters.matchesAny(interfaceInfo);
235
- if (ignore) {
236
- console.log('Omitting filtered interface: ', interfaceInfo);
237
- } else {
233
+ if (!ignore) {
238
234
  if (path.extname(file.name) === '.msg') {
239
235
  // Some .msg files were generated prior to 0.3.2 for .action files,
240
236
  // which has been disabled. So these files should be ignored here.
@@ -157,6 +157,10 @@ function isTypedArrayType(type) {
157
157
  return typedArrayType.indexOf(type.type.toLowerCase()) !== -1;
158
158
  }
159
159
 
160
+ function isBigInt(type) {
161
+ return ['int64', 'uint64'].indexOf(type.type.toLowerCase()) !== -1;
162
+ }
163
+
160
164
  const willUseTypedArray = isTypedArrayType(it.spec.baseType);
161
165
  const currentTypedArray = getTypedArrayName(it.spec.baseType);
162
166
  const currentTypedArrayElementType = getTypedArrayElementName(it.spec.baseType);
@@ -303,7 +307,13 @@ class {{=objectWrapper}} {
303
307
  this._refObject.{{=field.name}} = {{=field.default_value}};
304
308
  {{?}}
305
309
  {{?? field.type.isPrimitiveType && !isTypedArrayType(field.type) && field.default_value}}
306
- this._{{=field.name}}Array = {{=JSON.stringify(field.default_value)}};
310
+ {{? isBigInt(field.type)}}
311
+ {{/* For non-TypedArray like int64/uint64. */}}
312
+ this._{{=field.name}}Array = {{=JSON.stringify(field.default_value)}}.map(num => BigInt(num));
313
+ {{??}}
314
+ {{/* For non-TypedArray like bool. */}}
315
+ this._{{=field.name}}Array = {{=JSON.stringify(field.default_value)}};
316
+ {{?}}
307
317
  {{?? field.type.isPrimitiveType && isTypedArrayType(field.type) && field.default_value}}
308
318
  this._wrapperFields.{{=field.name}}.fill({{=getTypedArrayName(field.type)}}.from({{=JSON.stringify(field.default_value)}}));
309
319
  {{?}}
@@ -376,8 +386,11 @@ class {{=objectWrapper}} {
376
386
  }
377
387
  }
378
388
  }
389
+ {{?? isBigInt(field.type)}}
390
+ {{/* For non-TypedArray like int64/uint64. */}}
391
+ this._refObject.{{=field.name}} = this._{{=field.name}}Array.map(num => num.toString());
379
392
  {{??}}
380
- {{/* For non-TypedArray like int64/uint64/bool. */}}
393
+ {{/* For non-TypedArray like bool. */}}
381
394
  this._refObject.{{=field.name}} = this._{{=field.name}}Array;
382
395
  {{?}}
383
396
  {{?? field.type.isArray && field.type.isPrimitiveType && isTypedArrayType(field.type) && field.type.isFixedSizeArray}}
@@ -527,6 +540,8 @@ class {{=objectWrapper}} {
527
540
  return this._wrapperFields.{{=field.name}};
528
541
  {{?? !field.type.isArray && field.type.type === 'string' && it.spec.msgName !== 'String'}}
529
542
  return this._wrapperFields.{{=field.name}}.data;
543
+ {{?? isBigInt(field.type)}}
544
+ return BigInt(this._refObject.{{=field.name}});
530
545
  {{??}}
531
546
  return this._refObject.{{=field.name}};
532
547
  {{?}}
@@ -559,6 +574,11 @@ class {{=objectWrapper}} {
559
574
  }
560
575
  {{?? !field.type.isArray && field.type.type === 'string' && it.spec.msgName !== 'String'}}
561
576
  this._wrapperFields.{{=field.name}}.data = value;
577
+ {{?? isBigInt(field.type)}}
578
+ if (typeof value !== "bigint") {
579
+ throw new TypeError('{{=field.name}} must be type of bigint');
580
+ }
581
+ this._refObject.{{=field.name}} = value.toString();
562
582
  {{??}}
563
583
  {{? it.spec.msgName === 'String'}}
564
584
  this._refObject.size = Buffer.byteLength(value);
@@ -14,11 +14,20 @@
14
14
 
15
15
  'use strict';
16
16
 
17
+ const compareVersions = require('compare-versions');
17
18
  const path = require('path');
18
19
  const execFile = require('child_process').execFile;
19
20
 
20
21
  const pythonExecutable = require('./py_utils').getPythonExecutable('python3');
21
22
 
23
+ const contextSupportedVersion = '21.0.0.0';
24
+ const currentVersion = process.version;
25
+ const isContextSupported = compareVersions.compare(
26
+ currentVersion.substring(1, currentVersion.length),
27
+ contextSupportedVersion,
28
+ '>='
29
+ );
30
+
22
31
  const rosidlParser = {
23
32
  parseMessageFile(packageName, filePath) {
24
33
  return this._parseFile('parse_message_file', packageName, filePath);
@@ -32,6 +41,25 @@ const rosidlParser = {
32
41
  return this._parseFile('parse_action_file', packageName, filePath);
33
42
  },
34
43
 
44
+ _parseJSONObject(str) {
45
+ // For nodejs >= `contextSupportedVersion`, we leverage context parameter to
46
+ // convert unsafe integer to string, otherwise, json-bigint is used.
47
+ // See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse
48
+ if (isContextSupported) {
49
+ return JSON.parse(str, (key, value, context) => {
50
+ if (
51
+ Number.isInteger(value) &&
52
+ !Number.isSafeInteger(Number(context.source))
53
+ ) {
54
+ return context.source;
55
+ }
56
+ return value;
57
+ });
58
+ }
59
+ const JSONbigString = require('json-bigint')({ storeAsString: true });
60
+ return JSONbigString.parse(str);
61
+ },
62
+
35
63
  _parseFile(command, packageName, filePath) {
36
64
  return new Promise((resolve, reject) => {
37
65
  const args = [
@@ -54,7 +82,7 @@ const rosidlParser = {
54
82
  )
55
83
  );
56
84
  } else {
57
- resolve(JSON.parse(stdout));
85
+ resolve(this._parseJSONObject(stdout));
58
86
  }
59
87
  }
60
88
  );
@@ -478,7 +478,6 @@ function primitiveType2JSName(type) {
478
478
  case 'int8':
479
479
  case 'int16':
480
480
  case 'int32':
481
- case 'int64':
482
481
 
483
482
  // signed explicit float types
484
483
  case 'float32':
@@ -488,7 +487,6 @@ function primitiveType2JSName(type) {
488
487
  case 'uint8':
489
488
  case 'uint16':
490
489
  case 'uint32':
491
- case 'uint64':
492
490
  jsName = 'number';
493
491
  break;
494
492
  case 'bool':
@@ -499,6 +497,10 @@ function primitiveType2JSName(type) {
499
497
  case 'wstring':
500
498
  jsName = 'string';
501
499
  break;
500
+ case 'int64':
501
+ case 'uint64':
502
+ jsName = 'bigint';
503
+ break;
502
504
  }
503
505
 
504
506
  return jsName;
@@ -0,0 +1,69 @@
1
+ // Licensed under the Apache License, Version 2.0 (the "License");
2
+ // you may not use this file except in compliance with the License.
3
+ // You may obtain a copy of the License at
4
+ //
5
+ // http://www.apache.org/licenses/LICENSE-2.0
6
+ //
7
+ // Unless required by applicable law or agreed to in writing, software
8
+ // distributed under the License is distributed on an "AS IS" BASIS,
9
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10
+ // See the License for the specific language governing permissions and
11
+ // limitations under the License.
12
+
13
+ 'use strict';
14
+
15
+ const os = require('os');
16
+ const path = require('path');
17
+ const { execSync } = require('child_process');
18
+
19
+ const dependencies = [
20
+ 'rcl',
21
+ 'rcutils',
22
+ 'rmw',
23
+ 'rcl_yaml_param_parser',
24
+ 'rosidl_typesupport_interface',
25
+ 'rcl_action',
26
+ 'action_msgs',
27
+ 'service_msgs',
28
+ 'unique_identifier_msgs',
29
+ 'builtin_interfaces',
30
+ 'rcl_lifecycle',
31
+ 'lifecycle_msgs',
32
+ 'rosidl_runtime_c',
33
+ 'rosidl_dynamic_typesupport',
34
+ 'type_description_interfaces',
35
+ ];
36
+
37
+ const command = os.type() === 'Windows_NT' ? 'where ros2' : 'which ros2';
38
+
39
+ function getROSRootPath() {
40
+ try {
41
+ // Execute the command to find the ROS2 installation path.
42
+ let ros2Path = execSync(command).toString().trim();
43
+ return path.dirname(path.dirname(ros2Path));
44
+ } catch (error) {
45
+ console.error(`Error: ${error.message}`);
46
+ }
47
+ }
48
+
49
+ function getROSLibPath() {
50
+ return path.join(getROSRootPath(), 'lib');
51
+ }
52
+
53
+ function getROSIncludeRootPath() {
54
+ return path.join(getROSRootPath(), 'include');
55
+ }
56
+
57
+ function getIncludePaths() {
58
+ const includeRoot = getROSIncludeRootPath();
59
+ return dependencies.map((dependency) => {
60
+ return path.join(includeRoot, dependency);
61
+ });
62
+ }
63
+
64
+ module.exports = {
65
+ getROSRootPath,
66
+ getROSLibPath,
67
+ getROSIncludeRootPath,
68
+ getIncludePaths,
69
+ };
@@ -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.29.0](https://github.com/RobotWebTools/rclnodejs/tree/0.29.0)) | [Humble](https://github.com/RobotWebTools/rclnodejs/tree/humble-hawksbill)<br>[Jazzy](https://github.com/RobotWebTools/rclnodejs/tree/jazzy) |
48
+ | latest version (currently [v0.31.0](https://github.com/RobotWebTools/rclnodejs/tree/0.31.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