rclnodejs 0.21.4 → 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.
- package/.github/workflows/identify-ros-distro.yml +1 -0
- package/.github/workflows/linux-build-and-test-compatibility.yml +7 -8
- package/.github/workflows/linux-build-and-test.yml +3 -3
- package/.github/workflows/windows-build-and-test-compatibility.yml +8 -6
- package/.github/workflows/windows-build-and-test.yml +9 -4
- package/bin/linux-x64-110/rclnodejs.node +0 -0
- package/binding.gyp +2 -0
- package/index.js +4 -0
- package/lib/action/client.js +1 -2
- package/lib/action/server.js +1 -2
- package/lib/client.js +3 -2
- package/lib/distro.js +1 -1
- package/lib/entity.js +40 -0
- package/lib/node.js +85 -37
- package/lib/rmw.js +29 -0
- package/lib/subscription.js +52 -2
- package/package.json +12 -5
- package/rosidl_gen/blocklist.json +5 -0
- package/rosidl_gen/filter.js +104 -0
- package/rosidl_gen/idl_generator.js +7 -0
- package/rosidl_gen/index.js +19 -6
- package/rosidl_gen/packages.js +39 -39
- package/rostsd_gen/index.js +9 -1
- package/scripts/run_test.js +0 -6
- package/src/rcl_bindings.cpp +185 -1
- package/types/entity.d.ts +1 -0
- package/types/interfaces.d.ts +20 -0
- package/types/node.d.ts +50 -2
- package/types/subscription.d.ts +28 -0
package/lib/subscription.js
CHANGED
|
@@ -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
|
|
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
|
|
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.
|
|
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
|
-
"
|
|
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 <
|
|
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": "^
|
|
59
|
+
"mocha": "^10.2.0",
|
|
53
60
|
"prettier": "^2.0.5",
|
|
54
61
|
"rimraf": "^3.0.2",
|
|
55
62
|
"sinon": "^9.0.2",
|
|
@@ -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);
|
package/rosidl_gen/index.js
CHANGED
|
@@ -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
|
-
|
|
24
|
-
|
|
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
|
-
|
|
46
|
-
|
|
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
|
|
package/rosidl_gen/packages.js
CHANGED
|
@@ -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()
|
|
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
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
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
|
-
|
|
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
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
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
|
-
|
|
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
|
});
|
package/rostsd_gen/index.js
CHANGED
|
@@ -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 (
|
|
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
|
|
package/scripts/run_test.js
CHANGED
|
@@ -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) {
|
package/src/rcl_bindings.cpp
CHANGED
|
@@ -36,6 +36,7 @@
|
|
|
36
36
|
#include <rosidl_generator_c/string_functions.h>
|
|
37
37
|
#endif
|
|
38
38
|
|
|
39
|
+
#include <iostream>
|
|
39
40
|
#include <memory>
|
|
40
41
|
#include <string>
|
|
41
42
|
#include <vector>
|
|
@@ -652,6 +653,8 @@ NAN_METHOD(CreateSubscription) {
|
|
|
652
653
|
*Nan::Utf8String(info[3]->ToString(currentContent).ToLocalChecked()));
|
|
653
654
|
std::string topic(
|
|
654
655
|
*Nan::Utf8String(info[4]->ToString(currentContent).ToLocalChecked()));
|
|
656
|
+
v8::Local<v8::Object> options =
|
|
657
|
+
info[5]->ToObject(currentContent).ToLocalChecked();
|
|
655
658
|
|
|
656
659
|
rcl_subscription_t* subscription =
|
|
657
660
|
reinterpret_cast<rcl_subscription_t*>(malloc(sizeof(rcl_subscription_t)));
|
|
@@ -659,12 +662,75 @@ NAN_METHOD(CreateSubscription) {
|
|
|
659
662
|
|
|
660
663
|
rcl_subscription_options_t subscription_ops =
|
|
661
664
|
rcl_subscription_get_default_options();
|
|
662
|
-
auto qos_profile = GetQoSProfile(info[5]);
|
|
663
665
|
|
|
666
|
+
v8::Local<v8::Value> qos =
|
|
667
|
+
Nan::Get(options, Nan::New("qos").ToLocalChecked()).ToLocalChecked();
|
|
668
|
+
auto qos_profile = GetQoSProfile(qos);
|
|
664
669
|
if (qos_profile) {
|
|
665
670
|
subscription_ops.qos = *qos_profile;
|
|
666
671
|
}
|
|
667
672
|
|
|
673
|
+
#if ROS_VERSION >= 2205 // 2205 => Humble+
|
|
674
|
+
if (Nan::Has(options, Nan::New("contentFilter").ToLocalChecked())
|
|
675
|
+
.FromMaybe(false)) {
|
|
676
|
+
// configure content-filter
|
|
677
|
+
v8::MaybeLocal<v8::Value> contentFilterVal =
|
|
678
|
+
Nan::Get(options, Nan::New("contentFilter").ToLocalChecked());
|
|
679
|
+
|
|
680
|
+
if (!Nan::Equals(contentFilterVal.ToLocalChecked(), Nan::Undefined())
|
|
681
|
+
.ToChecked()) {
|
|
682
|
+
v8::Local<v8::Object> contentFilter = contentFilterVal.ToLocalChecked()
|
|
683
|
+
->ToObject(currentContent)
|
|
684
|
+
.ToLocalChecked();
|
|
685
|
+
|
|
686
|
+
// expression property is required
|
|
687
|
+
std::string expression(*Nan::Utf8String(
|
|
688
|
+
Nan::Get(contentFilter, Nan::New("expression").ToLocalChecked())
|
|
689
|
+
.ToLocalChecked()
|
|
690
|
+
->ToString(currentContent)
|
|
691
|
+
.ToLocalChecked()));
|
|
692
|
+
|
|
693
|
+
// parameters property (string[]) is optional
|
|
694
|
+
int argc = 0;
|
|
695
|
+
char** argv = nullptr;
|
|
696
|
+
|
|
697
|
+
if (Nan::Has(contentFilter, Nan::New("parameters").ToLocalChecked())
|
|
698
|
+
.FromMaybe(false)) {
|
|
699
|
+
v8::Local<v8::Array> jsArgv = v8::Local<v8::Array>::Cast(
|
|
700
|
+
Nan::Get(contentFilter, Nan::New("parameters").ToLocalChecked())
|
|
701
|
+
.ToLocalChecked());
|
|
702
|
+
argc = jsArgv->Length();
|
|
703
|
+
if (argc > 0) {
|
|
704
|
+
argv = reinterpret_cast<char**>(malloc(argc * sizeof(char*)));
|
|
705
|
+
for (int i = 0; i < argc; i++) {
|
|
706
|
+
Nan::MaybeLocal<v8::Value> jsElement = Nan::Get(jsArgv, i);
|
|
707
|
+
Nan::Utf8String utf8_arg(jsElement.ToLocalChecked());
|
|
708
|
+
int len = utf8_arg.length() + 1;
|
|
709
|
+
argv[i] = reinterpret_cast<char*>(malloc(len * sizeof(char*)));
|
|
710
|
+
snprintf(argv[i], len, "%s", *utf8_arg);
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
rcl_ret_t ret = rcl_subscription_options_set_content_filter_options(
|
|
716
|
+
expression.c_str(), argc, (const char**)argv, &subscription_ops);
|
|
717
|
+
|
|
718
|
+
if (ret != RCL_RET_OK) {
|
|
719
|
+
Nan::ThrowError(rcl_get_error_string().str);
|
|
720
|
+
rcl_reset_error();
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
if (argc) {
|
|
724
|
+
for (int i = 0; i < argc; i++) {
|
|
725
|
+
free(argv[i]);
|
|
726
|
+
}
|
|
727
|
+
free(argv);
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
#endif
|
|
733
|
+
|
|
668
734
|
const rosidl_message_type_support_t* ts =
|
|
669
735
|
GetMessageTypeSupport(package_name, message_sub_folder, message_name);
|
|
670
736
|
|
|
@@ -689,6 +755,121 @@ NAN_METHOD(CreateSubscription) {
|
|
|
689
755
|
}
|
|
690
756
|
}
|
|
691
757
|
|
|
758
|
+
NAN_METHOD(HasContentFilter) {
|
|
759
|
+
#if ROS_VERSION < 2205 // 2205 => Humble+
|
|
760
|
+
info.GetReturnValue().Set(Nan::False());
|
|
761
|
+
return;
|
|
762
|
+
#else
|
|
763
|
+
|
|
764
|
+
RclHandle* subscription_handle = RclHandle::Unwrap<RclHandle>(
|
|
765
|
+
Nan::To<v8::Object>(info[0]).ToLocalChecked());
|
|
766
|
+
rcl_subscription_t* subscription =
|
|
767
|
+
reinterpret_cast<rcl_subscription_t*>(subscription_handle->ptr());
|
|
768
|
+
|
|
769
|
+
bool is_valid = rcl_subscription_is_cft_enabled(subscription);
|
|
770
|
+
info.GetReturnValue().Set(Nan::New(is_valid));
|
|
771
|
+
#endif
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
NAN_METHOD(SetContentFilter) {
|
|
775
|
+
#if ROS_VERSION < 2205 // 2205 => Humble+
|
|
776
|
+
info.GetReturnValue().Set(Nan::False());
|
|
777
|
+
return;
|
|
778
|
+
#else
|
|
779
|
+
v8::Local<v8::Context> currentContext = Nan::GetCurrentContext();
|
|
780
|
+
RclHandle* subscription_handle = RclHandle::Unwrap<RclHandle>(
|
|
781
|
+
Nan::To<v8::Object>(info[0]).ToLocalChecked());
|
|
782
|
+
rcl_subscription_t* subscription =
|
|
783
|
+
reinterpret_cast<rcl_subscription_t*>(subscription_handle->ptr());
|
|
784
|
+
|
|
785
|
+
v8::Local<v8::Object> contentFilter =
|
|
786
|
+
info[1]->ToObject(currentContext).ToLocalChecked();
|
|
787
|
+
|
|
788
|
+
Nan::MaybeLocal<v8::Value> jsExpression =
|
|
789
|
+
Nan::Get(contentFilter, Nan::New("expression").ToLocalChecked());
|
|
790
|
+
Nan::Utf8String utf8_arg(jsExpression.ToLocalChecked());
|
|
791
|
+
int len = utf8_arg.length() + 1;
|
|
792
|
+
char* expression = reinterpret_cast<char*>(malloc(len * sizeof(char*)));
|
|
793
|
+
snprintf(expression, len, "%s", *utf8_arg);
|
|
794
|
+
|
|
795
|
+
// parameters property (string[]) is optional
|
|
796
|
+
int argc = 0;
|
|
797
|
+
char** argv = nullptr;
|
|
798
|
+
|
|
799
|
+
if (Nan::Has(contentFilter, Nan::New("parameters").ToLocalChecked())
|
|
800
|
+
.FromMaybe(false)) {
|
|
801
|
+
v8::Local<v8::Array> jsArgv = v8::Local<v8::Array>::Cast(
|
|
802
|
+
Nan::Get(contentFilter, Nan::New("parameters").ToLocalChecked())
|
|
803
|
+
.ToLocalChecked());
|
|
804
|
+
argc = jsArgv->Length();
|
|
805
|
+
if (argc > 0) {
|
|
806
|
+
argv = reinterpret_cast<char**>(malloc(argc * sizeof(char*)));
|
|
807
|
+
for (int i = 0; i < argc; i++) {
|
|
808
|
+
Nan::MaybeLocal<v8::Value> jsElement = Nan::Get(jsArgv, i);
|
|
809
|
+
Nan::Utf8String utf8_arg(jsElement.ToLocalChecked());
|
|
810
|
+
int len = utf8_arg.length() + 1;
|
|
811
|
+
argv[i] = reinterpret_cast<char*>(malloc(len * sizeof(char*)));
|
|
812
|
+
snprintf(argv[i], len, "%s", *utf8_arg);
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
// create ctf options
|
|
818
|
+
rcl_subscription_content_filter_options_t options =
|
|
819
|
+
rcl_get_zero_initialized_subscription_content_filter_options();
|
|
820
|
+
|
|
821
|
+
THROW_ERROR_IF_NOT_EQUAL(RCL_RET_OK,
|
|
822
|
+
rcl_subscription_content_filter_options_set(
|
|
823
|
+
subscription,
|
|
824
|
+
expression, // expression.c_str(),
|
|
825
|
+
argc, (const char**)argv, &options),
|
|
826
|
+
rcl_get_error_string().str);
|
|
827
|
+
|
|
828
|
+
THROW_ERROR_IF_NOT_EQUAL(
|
|
829
|
+
RCL_RET_OK, rcl_subscription_set_content_filter(subscription, &options),
|
|
830
|
+
rcl_get_error_string().str);
|
|
831
|
+
|
|
832
|
+
if (argc) {
|
|
833
|
+
free(expression);
|
|
834
|
+
|
|
835
|
+
for (int i = 0; i < argc; i++) {
|
|
836
|
+
free(argv[i]);
|
|
837
|
+
}
|
|
838
|
+
free(argv);
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
info.GetReturnValue().Set(Nan::True());
|
|
842
|
+
#endif
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
NAN_METHOD(ClearContentFilter) {
|
|
846
|
+
#if ROS_VERSION < 2205 // 2205 => Humble+
|
|
847
|
+
info.GetReturnValue().Set(Nan::False());
|
|
848
|
+
return;
|
|
849
|
+
#else
|
|
850
|
+
RclHandle* subscription_handle = RclHandle::Unwrap<RclHandle>(
|
|
851
|
+
Nan::To<v8::Object>(info[0]).ToLocalChecked());
|
|
852
|
+
rcl_subscription_t* subscription =
|
|
853
|
+
reinterpret_cast<rcl_subscription_t*>(subscription_handle->ptr());
|
|
854
|
+
|
|
855
|
+
// create ctf options
|
|
856
|
+
rcl_subscription_content_filter_options_t options =
|
|
857
|
+
rcl_get_zero_initialized_subscription_content_filter_options();
|
|
858
|
+
|
|
859
|
+
THROW_ERROR_IF_NOT_EQUAL(
|
|
860
|
+
RCL_RET_OK,
|
|
861
|
+
rcl_subscription_content_filter_options_init(
|
|
862
|
+
subscription, "", 0, (const char**)nullptr, &options),
|
|
863
|
+
rcl_get_error_string().str);
|
|
864
|
+
|
|
865
|
+
THROW_ERROR_IF_NOT_EQUAL(
|
|
866
|
+
RCL_RET_OK, rcl_subscription_set_content_filter(subscription, &options),
|
|
867
|
+
rcl_get_error_string().str);
|
|
868
|
+
|
|
869
|
+
info.GetReturnValue().Set(Nan::True());
|
|
870
|
+
#endif
|
|
871
|
+
}
|
|
872
|
+
|
|
692
873
|
NAN_METHOD(CreatePublisher) {
|
|
693
874
|
v8::Local<v8::Context> currentContent = Nan::GetCurrentContext();
|
|
694
875
|
// Extract arguments
|
|
@@ -1783,6 +1964,9 @@ std::vector<BindingMethod> binding_methods = {
|
|
|
1783
1964
|
{"getRosTimeOverrideIsEnabled", GetRosTimeOverrideIsEnabled},
|
|
1784
1965
|
{"rclTake", RclTake},
|
|
1785
1966
|
{"createSubscription", CreateSubscription},
|
|
1967
|
+
{"hasContentFilter", HasContentFilter},
|
|
1968
|
+
{"setContentFilter", SetContentFilter},
|
|
1969
|
+
{"clearContentFilter", ClearContentFilter},
|
|
1786
1970
|
{"createPublisher", CreatePublisher},
|
|
1787
1971
|
{"publish", Publish},
|
|
1788
1972
|
{"getTopic", GetTopic},
|