rclnodejs 0.21.3 → 0.22.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.
@@ -19,8 +19,13 @@ const Entity = require('./entity.js');
19
19
  const debug = require('debug')('rclnodejs:subscription');
20
20
 
21
21
  /**
22
- * @class - Class representing a Subscription in ROS
22
+ * @class - Class representing a ROS 2 Subscription
23
23
  * @hideconstructor
24
+ * Includes support for content-filtering topics beginning with the
25
+ * ROS Humble release. To learn more about content-filtering
26
+ * @see {@link Node#options}
27
+ * @see {@link Node#createSubscription}
28
+ * @see {@link https://www.omg.org/spec/DDS/1.4/PDF|DDS 1.4 specification, Annex B}
24
29
  */
25
30
 
26
31
  class Subscription extends Entity {
@@ -42,13 +47,21 @@ class Subscription extends Entity {
42
47
 
43
48
  static createSubscription(nodeHandle, typeClass, topic, options, callback) {
44
49
  let type = typeClass.type();
50
+
51
+ // convert contentFilter.parameters to a string[]
52
+ if (options.contentFilter && options.contentFilter.parameters) {
53
+ options.contentFilter.parameters = options.contentFilter.parameters.map(
54
+ (param) => param.toString()
55
+ );
56
+ }
57
+
45
58
  let handle = rclnodejs.createSubscription(
46
59
  nodeHandle,
47
60
  type.pkgName,
48
61
  type.subFolder,
49
62
  type.interfaceName,
50
63
  topic,
51
- options.qos
64
+ options
52
65
  );
53
66
  return new Subscription(handle, typeClass, topic, options, callback);
54
67
  }
@@ -66,6 +79,43 @@ class Subscription extends Entity {
66
79
  get isRaw() {
67
80
  return this._isRaw;
68
81
  }
82
+
83
+ /**
84
+ * Test if the RMW supports content-filtered topics and that this subscription
85
+ * has an active wellformed content-filter.
86
+ * @returns {boolean} True if content-filtering will be applied; otherwise false.
87
+ */
88
+ hasContentFilter() {
89
+ return rclnodejs.hasContentFilter(this.handle);
90
+ }
91
+
92
+ /**
93
+ * If the RMW supports content-filtered topics set this subscription's content-filter.
94
+ * @param {object} contentFilter - The content-filter description.
95
+ * @param {string} contentFilter.expression - Specifies the criteria to select messages of interest.
96
+ * It is similar to the WHERE part of an SQL clause. Clear the current contentFilter if
97
+ * the expression is undefined or an empty string.
98
+ * @param {object[]} [contentFilter.parameters=undefined] - Array of objects that give values to
99
+ * the ‘parameters’ (i.e., "%n" tokens) in the filter_expression. The number of supplied parameters must
100
+ * fit with the requested values in the filter_expression (i.e., the number of %n tokens). default: undefined.
101
+ * @returns {boolean} - True if successful; false otherwise
102
+ * @see {@link https://www.omg.org/spec/DDS/1.4/PDF|DDS 1.4 specification, Annex B}
103
+ */
104
+ setContentFilter(contentFilter) {
105
+ return contentFilter?.expression
106
+ ? rclnodejs.setContentFilter(this.handle, contentFilter)
107
+ : this.clearContentFilter();
108
+ }
109
+
110
+ /**
111
+ * Clear the current content-filter. No filtering is to be applied.
112
+ * @returns {boolean} - True if successful; false otherwise
113
+ */
114
+ clearContentFilter() {
115
+ return this.hasContentFilter()
116
+ ? rclnodejs.clearContentFilter(this.handle)
117
+ : true;
118
+ }
69
119
  }
70
120
 
71
121
  module.exports = Subscription;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rclnodejs",
3
- "version": "0.21.3",
3
+ "version": "0.22.0",
4
4
  "description": "ROS2.0 JavaScript client with Node.js",
5
5
  "main": "index.js",
6
6
  "types": "types/index.d.ts",
@@ -13,21 +13,28 @@
13
13
  "typescript"
14
14
  ],
15
15
  "scripts": {
16
- "install": "node-gyp rebuild",
16
+ "build": "node-gyp -j 16 build",
17
+ "build:dev": "node-gyp -j 16 build --debug",
18
+ "rebuild": "npm run clean && node-gyp -j 16 rebuild",
19
+ "rebuild:dev": "npm run clean && node-gyp -j 16 rebuild --debug",
20
+ "generate-messages": "node scripts/generate_messages.js",
21
+ "clean": "node-gyp clean && rimraf ./generated",
22
+ "install": "npm run rebuild",
23
+ "postinstall": "npm run generate-messages",
17
24
  "docs": "cd docs && make",
18
25
  "test": "node --expose-gc ./scripts/run_test.js && npm run dtslint",
19
26
  "dtslint": "node scripts/generate_tsd.js && dtslint test/types",
20
27
  "lint": "eslint --max-warnings=0 --ext js,ts index.js types scripts lib example rosidl_gen rosidl_parser test benchmark/rclnodejs && node ./scripts/cpplint.js",
21
- "postinstall": "node scripts/generate_messages.js",
22
28
  "format": "clang-format -i -style=file ./src/*.cpp ./src/*.hpp && prettier --write \"{lib,rosidl_gen,rostsd_gen,rosidl_parser,types,example,test,scripts,benchmark}/**/*.{js,md,ts}\" ./*.{js,md,ts}"
23
29
  },
24
30
  "bin": {
25
31
  "generate-ros-messages": "./scripts/generate_messages.js"
26
32
  },
27
33
  "authors": [
28
- "Minggang Wang <minggang.wang@intel.com>",
34
+ "Minggang Wang <minggangw@gmail.com>",
29
35
  "Kenny Yuan <kaining.yuan@intel.com>",
30
36
  "Wanming Lin <wanming.lin@intel.com>",
37
+ "Wayne Parrott",
31
38
  "Zhong Qiu <zhongx.qiu@intel.com>"
32
39
  ],
33
40
  "license": "Apache-2.0",
@@ -49,7 +56,7 @@
49
56
  "husky": "^4.2.5",
50
57
  "jsdoc": "^3.6.7",
51
58
  "lint-staged": "^10.2.11",
52
- "mocha": "^8.0.1",
59
+ "mocha": "^10.2.0",
53
60
  "prettier": "^2.0.5",
54
61
  "rimraf": "^3.0.2",
55
62
  "sinon": "^9.0.2",
@@ -89,6 +96,6 @@
89
96
  ]
90
97
  },
91
98
  "engines": {
92
- "node": ">= 10.23.1 <18.0.0"
99
+ "node": ">= 10.23.1 <20.0.0"
93
100
  }
94
101
  }
@@ -0,0 +1,5 @@
1
+ [
2
+ {
3
+ "pkgName": "rosbag2_storage_mcap_testdata"
4
+ }
5
+ ]
@@ -0,0 +1,104 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const os = require('os');
4
+
5
+ // blocklist.json format
6
+ // [
7
+ // {
8
+ // pkgName: RegExString,
9
+ // interfaceName: RegExString,
10
+ // os: RegExString
11
+ // },
12
+ // ...
13
+ // ]
14
+ //
15
+ // examples
16
+ // [
17
+ // {
18
+ // "pkgName": "action*"
19
+ // },
20
+ // {
21
+ // "pkgName": "std_msgs",
22
+ // },
23
+ // {
24
+ // "pkgName": "std_msgs",
25
+ // "interfaceName": "String"
26
+ // },
27
+ // {
28
+ // "os": "Linux"
29
+ // },
30
+ // ]
31
+
32
+ const RosPackageFilters = {
33
+ filters: [],
34
+ _loaded: false,
35
+
36
+ addFilter: function (pkgName, interfaceName, os) {
37
+ this.filters.push({
38
+ pkgName: pkgName,
39
+ interfaceName: interfaceName,
40
+ os: os,
41
+ });
42
+ },
43
+
44
+ _matches: function (filter, pkgInfo) {
45
+ if (filter.os && filter.os.test(os.type())) {
46
+ return true;
47
+ }
48
+
49
+ if (filter.pkgName) {
50
+ if (filter.pkgName.test(pkgInfo.pkgName)) {
51
+ if (!filter.interfaceName) {
52
+ return true;
53
+ }
54
+ } else {
55
+ return false;
56
+ }
57
+ }
58
+
59
+ if (
60
+ filter.interfaceName &&
61
+ filter.interfaceName.test(pkgInfo.interfaceName)
62
+ ) {
63
+ return true;
64
+ }
65
+
66
+ return false;
67
+ },
68
+
69
+ load: function (
70
+ blocklistPath = path.join(__dirname, '../rosidl_gen/blocklist.json')
71
+ ) {
72
+ this._loaded = true;
73
+
74
+ if (!fs.existsSync(blocklistPath)) return;
75
+
76
+ // eslint-disable-next-line
77
+ let blocklistData = JSON.parse(fs.readFileSync(blocklistPath, 'utf8'));
78
+
79
+ let filters = blocklistData.map((pkgFilterData) => {
80
+ let filter = {};
81
+ if (pkgFilterData['pkgName']) {
82
+ filter.pkgName = new RegExp(pkgFilterData.pkgName);
83
+ }
84
+ if (pkgFilterData['interfaceName']) {
85
+ filter.interfaceName = new RegExp(pkgFilterData.interfaceName);
86
+ }
87
+ if (pkgFilterData['os']) {
88
+ filter.os = new RegExp(pkgFilterData.os);
89
+ }
90
+ return filter;
91
+ });
92
+
93
+ this.filters = filters.filter(
94
+ (filter) => !filter.os || filter.os.test(os.type())
95
+ );
96
+ },
97
+
98
+ matchesAny: function (pkgInfo) {
99
+ if (!this._loaded) this.load();
100
+ return this.filters.some((filter) => this._matches(filter, pkgInfo));
101
+ },
102
+ };
103
+
104
+ module.exports = RosPackageFilters;
@@ -30,6 +30,13 @@ function removeEmptyLines(str) {
30
30
  return str.replace(/^\s*\n/gm, '');
31
31
  }
32
32
 
33
+ /**
34
+ * Output generated code to disk. Do not overwrite
35
+ * an existing file. If file already exists do nothing.
36
+ * @param {string} dir
37
+ * @param {string} fileName
38
+ * @param {string} code
39
+ */
33
40
  async function writeGeneratedCode(dir, fileName, code) {
34
41
  await fse.mkdirs(dir);
35
42
  await fse.writeFile(path.join(dir, fileName), code);
@@ -20,13 +20,16 @@ const packages = require('./packages.js');
20
20
  const path = require('path');
21
21
 
22
22
  const generatedRoot = path.join(__dirname, '../generated/');
23
- const installedPackagePaths = process.env.AMENT_PREFIX_PATH.split(
24
- path.delimiter
25
- );
23
+
24
+ function getInstalledPackagePaths() {
25
+ return process.env.AMENT_PREFIX_PATH.split(path.delimiter);
26
+ }
26
27
 
27
28
  async function generateInPath(path) {
28
29
  const pkgs = await packages.findPackagesInDirectory(path);
30
+
29
31
  const pkgsInfo = Array.from(pkgs.values());
32
+
30
33
  await Promise.all(
31
34
  pkgsInfo.map((pkgInfo) => generateJSStructFromIDL(pkgInfo, generatedRoot))
32
35
  );
@@ -38,13 +41,23 @@ async function generateAll(forcedGenerating) {
38
41
  // all the JavaScript files will be created.
39
42
  const exist = await fse.exists(generatedRoot);
40
43
  if (forcedGenerating || !exist) {
44
+ if (exist) {
45
+ // recursively clear all previously generated struct files
46
+ await fse.emptyDir(generatedRoot);
47
+ }
48
+
41
49
  await fse.copy(
42
50
  path.join(__dirname, 'generator.json'),
43
51
  path.join(generatedRoot, 'generator.json')
44
52
  );
45
- await Promise.all(
46
- installedPackagePaths.map((path) => generateInPath(path))
47
- );
53
+
54
+ // Process in AMENT_PREFIX_PATH in reverse order to
55
+ // such that interfaces defined earlier on the AMENX_PREFIX_PATH
56
+ // have higher priority over earlier versions and will override
57
+ // them - occurences of this are expected to be rare.
58
+ for (let path of getInstalledPackagePaths().reverse()) {
59
+ await generateInPath(path);
60
+ }
48
61
  }
49
62
  }
50
63
 
@@ -21,6 +21,7 @@ const path = require('path');
21
21
  const walk = require('walk');
22
22
  const os = require('os');
23
23
  const flat = require('array.prototype.flat');
24
+ const pkgFilters = require('../rosidl_gen/filter.js');
24
25
 
25
26
  const fsp = fs.promises;
26
27
 
@@ -140,28 +141,30 @@ async function findAmentPackagesInDirectory(dir) {
140
141
  pkgs.map((pkg) => getPackageDefinitionsFiles(pkg, dir))
141
142
  );
142
143
 
143
- // Support flat() methond for nodejs < 11.
144
+ // Support flat() method for nodejs < 11.
144
145
  const rosFiles = Array.prototype.flat ? files.flat() : flat(files);
145
146
 
146
147
  const pkgMap = new Map();
147
148
  return new Promise((resolve, reject) => {
148
149
  rosFiles.forEach((filePath) => {
149
- if (path.extname(filePath) === '.msg') {
150
- // Some .msg files were generated prior to 0.3.2 for .action files,
151
- // which has been disabled. So these files should be ignored here.
152
- if (path.dirname(dir).split(path.sep).pop() !== 'action') {
153
- addInterfaceInfo(
154
- grabInterfaceInfo(filePath, true),
155
- 'messages',
156
- pkgMap
157
- );
158
- }
159
- } else if (path.extname(filePath) === '.srv') {
160
- addInterfaceInfo(grabInterfaceInfo(filePath, true), 'services', pkgMap);
161
- } else if (path.extname(filePath) === '.action') {
162
- addInterfaceInfo(grabInterfaceInfo(filePath, true), 'actions', pkgMap);
150
+ const interfaceInfo = grabInterfaceInfo(filePath, true);
151
+ const ignore = pkgFilters.matchesAny(interfaceInfo);
152
+ if (ignore) {
153
+ console.log('Omitting filtered interface: ', interfaceInfo);
163
154
  } else {
164
- // we ignore all other files
155
+ if (path.extname(filePath) === '.msg') {
156
+ // Some .msg files were generated prior to 0.3.2 for .action files,
157
+ // which has been disabled. So these files should be ignored here.
158
+ if (path.dirname(dir).split(path.sep).pop() !== 'action') {
159
+ addInterfaceInfo(interfaceInfo, 'messages', pkgMap);
160
+ }
161
+ } else if (path.extname(filePath) === '.srv') {
162
+ addInterfaceInfo(interfaceInfo, 'services', pkgMap);
163
+ } else if (path.extname(filePath) === '.action') {
164
+ addInterfaceInfo(interfaceInfo, 'actions', pkgMap);
165
+ } else {
166
+ // we ignore all other files
167
+ }
165
168
  }
166
169
  });
167
170
  resolve(pkgMap);
@@ -191,30 +194,27 @@ async function findPackagesInDirectory(dir) {
191
194
  let walker = walk.walk(dir, { followLinks: true });
192
195
  let pkgMap = new Map();
193
196
  walker.on('file', (root, file, next) => {
194
- if (path.extname(file.name) === '.msg') {
195
- // Some .msg files were generated prior to 0.3.2 for .action files,
196
- // which has been disabled. So these files should be ignored here.
197
- if (path.dirname(root).split(path.sep).pop() !== 'action') {
198
- addInterfaceInfo(
199
- grabInterfaceInfo(path.join(root, file.name), amentExecuted),
200
- 'messages',
201
- pkgMap
202
- );
203
- }
204
- } else if (path.extname(file.name) === '.srv') {
205
- addInterfaceInfo(
206
- grabInterfaceInfo(path.join(root, file.name), amentExecuted),
207
- 'services',
208
- pkgMap
209
- );
210
- } else if (path.extname(file.name) === '.action') {
211
- addInterfaceInfo(
212
- grabInterfaceInfo(path.join(root, file.name), amentExecuted),
213
- 'actions',
214
- pkgMap
215
- );
197
+ const interfaceInfo = grabInterfaceInfo(
198
+ path.join(root, file.name),
199
+ amentExecuted
200
+ );
201
+ const ignore = pkgFilters.matchesAny(interfaceInfo);
202
+ if (ignore) {
203
+ console.log('Omitting filtered interface: ', interfaceInfo);
216
204
  } else {
217
- // we ignore all other files
205
+ if (path.extname(file.name) === '.msg') {
206
+ // Some .msg files were generated prior to 0.3.2 for .action files,
207
+ // which has been disabled. So these files should be ignored here.
208
+ if (path.dirname(root).split(path.sep).pop() !== 'action') {
209
+ addInterfaceInfo(interfaceInfo, 'messages', pkgMap);
210
+ }
211
+ } else if (path.extname(file.name) === '.srv') {
212
+ addInterfaceInfo(interfaceInfo, 'services', pkgMap);
213
+ } else if (path.extname(file.name) === '.action') {
214
+ addInterfaceInfo(interfaceInfo, 'actions', pkgMap);
215
+ } else {
216
+ // we ignore all other files
217
+ }
218
218
  }
219
219
  next();
220
220
  });
@@ -31,6 +31,7 @@ declare module "rclnodejs" {
31
31
  const path = require('path');
32
32
  const fs = require('fs');
33
33
  const loader = require('../lib/interface_loader.js');
34
+ const pkgFilters = require('../rosidl_gen/filter.js');
34
35
 
35
36
  async function generateAll() {
36
37
  // load pkg and interface info (msgs and srvs)
@@ -63,7 +64,14 @@ function getPkgInfos(rootDir) {
63
64
 
64
65
  for (let filename of files) {
65
66
  const typeClass = fileName2Typeclass(filename);
66
- if (!typeClass.type) continue;
67
+ if (
68
+ !typeClass.type ||
69
+ pkgFilters.matchesAny({
70
+ pkgName: typeClass.package,
71
+ interfaceName: typeClass.name,
72
+ })
73
+ )
74
+ continue;
67
75
 
68
76
  const rosInterface = loader.loadInterface(typeClass);
69
77
 
@@ -45,7 +45,7 @@ npm i rclnodejs@x.y.z
45
45
 
46
46
  | RCLNODEJS Version | Compatible ROS 2 Release |
47
47
  | :-------------------------------------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: |
48
- | [0.21.3 (current)](https://www.npmjs.com/package/rclnodejs/v/0.21.3) ([API](http://robotwebtools.org/rclnodejs/docs/0.21.3/index.html)) | [Humble Hawksbill](https://github.com/ros2/ros2/releases/tag/release-humble-20220523)<br>[Galactic Geochelone](https://github.com/ros2/ros2/releases/tag/release-galactic-20210716)<br>[Foxy Fitzroy](https://github.com/ros2/ros2/releases/tag/release-foxy-20201211)<br>[Eloquent Elusor](https://github.com/ros2/ros2/releases/tag/release-eloquent-20200124) |
48
+ | [0.21.4 (current)](https://www.npmjs.com/package/rclnodejs/v/0.21.4) ([API](http://robotwebtools.org/rclnodejs/docs/0.21.3/index.html)) | [Humble Hawksbill](https://github.com/ros2/ros2/releases/tag/release-humble-20220523)<br>[Galactic Geochelone](https://github.com/ros2/ros2/releases/tag/release-galactic-20210716)<br>[Foxy Fitzroy](https://github.com/ros2/ros2/releases/tag/release-foxy-20201211)<br>[Eloquent Elusor](https://github.com/ros2/ros2/releases/tag/release-eloquent-20200124) |
49
49
  | [0.10.3](https://github.com/RobotWebTools/rclnodejs/releases/tag/0.10.3) | [Dashing Diademata - Patch 4](https://github.com/ros2/ros2/releases/tag/release-dashing-20191018) |
50
50
 
51
51
  ## Documentation
@@ -20,12 +20,6 @@ const os = require('os');
20
20
  const path = require('path');
21
21
 
22
22
  let rootDir = path.dirname(__dirname);
23
- let actionPath = path.join(rootDir, 'test', 'ros1_actions');
24
- process.env.AMENT_PREFIX_PATH =
25
- process.env.AMENT_PREFIX_PATH + path.delimiter + actionPath;
26
- let msgPath = path.join(rootDir, 'test', 'rclnodejs_test_msgs');
27
- process.env.AMENT_PREFIX_PATH =
28
- process.env.AMENT_PREFIX_PATH + path.delimiter + msgPath;
29
23
 
30
24
  fs.remove(path.join(path.dirname(__dirname), 'generated'), (err) => {
31
25
  if (!err) {