Skip to content

Commit 463a17e

Browse files
authored
Remapping (#89)
-Adds the ability to remap topics, services, and parameters through the command line. Also supports special remapping keys for node name, ip, host, master and namespace. -Adds tests for remapping -Adds tests for some new node initialization options.
1 parent b24a9b8 commit 463a17e

10 files changed

Lines changed: 840 additions & 194 deletions

File tree

src/examples/standard.js

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ const SetBool = rosnodejs.require('std_srvs').srv.SetBool;
77
rosnodejs.initNode('/test_node')
88
.then((rosNode) => {
99
// EXP 1) Service Server
10-
let service = rosNode.advertiseService('/set_bool', SetBool,
10+
let service = rosNode.advertiseService('set_bool', SetBool,
1111
(req, resp) => {
1212
rosnodejs.log.info('Handling request! ' + JSON.stringify(req));
1313
resp.success = !req.data;
@@ -16,7 +16,7 @@ rosnodejs.initNode('/test_node')
1616
});
1717

1818
// EXP 2) Service Client
19-
let serviceClient = rosNode.serviceClient('/set_bool', 'std_srvs/SetBool', {persist: true});
19+
let serviceClient = rosNode.serviceClient('set_bool', 'std_srvs/SetBool', {persist: true});
2020
rosNode.waitForService(serviceClient.getService(), 2000)
2121
.then((available) => {
2222
if (available) {
@@ -32,7 +32,7 @@ rosnodejs.initNode('/test_node')
3232
});
3333
})
3434
.then(() => {
35-
let serviceClient2 = rosNode.serviceClient('/set_bool', 'std_srvs/SetBool');
35+
let serviceClient2 = rosNode.serviceClient('set_bool', 'std_srvs/SetBool');
3636
serviceClient2.call(request).then((resp) => {
3737
rosnodejs.log.info('Non persistent response ' + JSON.stringify(resp));
3838
})
@@ -41,12 +41,12 @@ rosnodejs.initNode('/test_node')
4141
});
4242

4343
// EXP 3) Params
44-
rosNode.setParam('~junk', {'hi': 2}).then(() => {
45-
rosNode.getParam('~junk').then((val) => { rosnodejs.log.info('Got Param!!! ' + JSON.stringify(val)); });
44+
rosNode.setParam('junk', {'hi': 2}).then(() => {
45+
rosNode.getParam('junk').then((val) => { rosnodejs.log.info('Got Param!!! ' + JSON.stringify(val)); });
4646
});
4747

4848
// EXP 4) Publisher
49-
let pub = rosNode.advertise( '/my_topic', std_msgs.String,
49+
let pub = rosNode.advertise( 'my_topic', std_msgs.String,
5050
{
5151
queueSize: 1,
5252
latching: true,
@@ -67,7 +67,7 @@ rosnodejs.initNode('/test_node')
6767
}, 5);
6868

6969
// EXP 5) Subscriber
70-
let sub = rosNode.subscribe('/my_topic', 'std_msgs/String',
70+
let sub = rosNode.subscribe('my_topic', 'std_msgs/String',
7171
(data) => {
7272
rosnodejs.log.info('SUB DATA ' + data.data);
7373
},

src/index.js

Lines changed: 105 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -36,81 +36,15 @@ const packages = require('./utils/messageGeneration/packages.js');
3636
const ActionServer = require('./actions/ActionServer.js');
3737

3838
const MsgLoader = require('./utils/messageGeneration/MessageLoader.js');
39+
const RemapUtils = require('./utils/remapping_utils.js');
40+
const names = require('./lib/Names.js');
3941

4042
// will be initialized through call to initNode
4143
let log = Logging.getLogger();
4244
let rosNode = null;
4345
let pingMasterTimeout = null;
4446

4547
//------------------------------------------------------------------
46-
/**
47-
* @private
48-
* Helper function to see if the master is available and able to accept
49-
* connections.
50-
* @param {number} timeout time in ms between connection attempts
51-
* @param {number} maxTimeout maximum time in ms to retry before timing out.
52-
* A negative number will make it retry forever. 0 will only make one attempt
53-
* before timing out.
54-
*/
55-
function _checkMasterHelper(timeout=100, maxTimeout=-1) {
56-
let startTime = Date.now();
57-
const localHelper = (resolve,reject) => {
58-
pingMasterTimeout = setTimeout(() => {
59-
// also check that the slave api server is set up
60-
if (!rosNode.slaveApiSetupComplete()) {
61-
if (Date.now() - startTime >= maxTimeout && !(maxTimeout < 0) ) {
62-
log.error(`Unable to register with master node [${rosNode.getRosMasterUri()}]: unable to set up slave API Server. Stopping...`);
63-
reject(Error('Unable to setup slave API server.'));
64-
return;
65-
}
66-
localHelper(resolve, reject);
67-
return;
68-
}
69-
rosNode.getMasterUri({ maxAttempts: 1 })
70-
.then(() => {
71-
log.infoOnce(`Connected to master at ${rosNode.getRosMasterUri()}!`);
72-
pingMasterTimeout = null;
73-
resolve();
74-
})
75-
.catch((err, resp) => {
76-
if (Date.now() - startTime >= maxTimeout && !(maxTimeout < 0) ){
77-
log.error(`Timed out before registering with master node [${rosNode.getRosMasterUri()}]: master may not be running yet.`);
78-
reject(Error('Registration with master timed out.'));
79-
return;
80-
} else {
81-
log.warnThrottle(60000, `Unable to register with master node [${rosNode.getRosMasterUri()}]: master may not be running yet. Will keep trying.`);
82-
localHelper(resolve, reject);
83-
}
84-
});
85-
}, timeout);
86-
};
87-
88-
return new Promise((resolve, reject) => {
89-
localHelper(resolve,reject);
90-
});
91-
}
92-
93-
/**
94-
* Very basic validation of node name - needs to start with a '/'
95-
* TODO: more
96-
* @return {string} name of node after validation
97-
*/
98-
function _validateNodeName(nodeName) {
99-
if (!nodeName.startsWith('/')) {
100-
nodeName = '/' + nodeName;
101-
}
102-
return nodeName;
103-
}
104-
105-
/**
106-
* Appends a random string of numeric characters to the end
107-
* of the node name. Follows rospy logic.
108-
* @param nodeName {string} string to anonymize
109-
* @return {string} anonymized nodeName
110-
*/
111-
function _anonymizeNodeName(nodeName) {
112-
return util.format('%s_%s_%s', nodeName, process.pid, Date.now());
113-
}
11448

11549
let Rosnodejs = {
11650
/**
@@ -131,12 +65,25 @@ let Rosnodejs = {
13165
* @return {Promise} resolved when connection to master is established
13266
*/
13367
initNode(nodeName, options) {
134-
options = options || {};
135-
if (options.anonymous) {
136-
nodeName = _anonymizeNodeName(nodeName);
68+
if (typeof nodeName !== 'string') {
69+
throw new Error('The node name must be a string');
13770
}
71+
else if (nodeName.length === 0) {
72+
throw new Error('The node name must not be empty!');
73+
}
74+
75+
options = options || {};
76+
77+
// process remappings from command line arguments.
78+
// First two are $ node <file> so we skip them
79+
const remappings = RemapUtils.processRemapping(process.argv.slice(2));
13880

139-
nodeName = _validateNodeName(nodeName);
81+
// initialize netUtils from possible command line remappings
82+
netUtils.init(remappings);
83+
84+
const [resolvedName, namespace] = _resolveNodeName(nodeName, remappings, options);
85+
86+
names.init(remappings, namespace);
14087

14188
if (rosNode !== null) {
14289
if (nodeName === rosNode.getNodeName()) {
@@ -147,17 +94,14 @@ let Rosnodejs = {
14794
+ rosNode.getNodeName() + '] already exists'));
14895
}
14996

150-
let rosMasterUri = process.env.ROS_MASTER_URI;
151-
if (options.rosMasterUri) {
152-
rosMasterUri = options.rosMasterUri;
153-
}
154-
155-
Logging.initializeNodeLogger(nodeName, options.logging);
97+
Logging.initializeNodeLogger(resolvedName, options.logging);
15698

15799
// create the ros node. Return a promise that will
158100
// resolve when connection to master is established
159101
const nodeOpts = options.node || {};
160-
rosNode = new RosNode(nodeName, rosMasterUri, nodeOpts);
102+
const rosMasterUri = options.rosMasterUri || remappings['__master'] || process.env.ROS_MASTER_URI;;
103+
104+
rosNode = new RosNode(resolvedName, rosMasterUri, nodeOpts);
161105

162106
return new Promise((resolve,reject)=>{
163107
this._loadOnTheFlyMessages(options)
@@ -338,3 +282,85 @@ let Rosnodejs = {
338282
Rosnodejs.ActionServer = ActionServer;
339283

340284
module.exports = Rosnodejs;
285+
286+
//------------------------------------------------------------------
287+
// Local Helper Functions
288+
//------------------------------------------------------------------
289+
290+
/**
291+
* @private
292+
* Helper function to see if the master is available and able to accept
293+
* connections.
294+
* @param {number} timeout time in ms between connection attempts
295+
* @param {number} maxTimeout maximum time in ms to retry before timing out.
296+
* A negative number will make it retry forever. 0 will only make one attempt
297+
* before timing out.
298+
*/
299+
function _checkMasterHelper(timeout=100, maxTimeout=-1) {
300+
const startTime = Date.now();
301+
302+
const localHelper = (resolve,reject) => {
303+
pingMasterTimeout = setTimeout(() => {
304+
// also check that the slave api server is set up
305+
if (!rosNode.slaveApiSetupComplete()) {
306+
if (Date.now() - startTime >= maxTimeout && maxTimeout >= 0) {
307+
log.error(`Unable to register with master node [${rosNode.getRosMasterUri()}]: unable to set up slave API Server. Stopping...`);
308+
reject(new Error('Unable to setup slave API server.'));
309+
return;
310+
}
311+
localHelper(resolve, reject);
312+
return;
313+
}
314+
rosNode.getMasterUri({ maxAttempts: 1 })
315+
.then(() => {
316+
log.infoOnce(`Connected to master at ${rosNode.getRosMasterUri()}!`);
317+
pingMasterTimeout = null;
318+
resolve();
319+
})
320+
.catch((err, resp) => {
321+
if (Date.now() - startTime >= maxTimeout && !(maxTimeout < 0) ){
322+
log.error(`Timed out before registering with master node [${rosNode.getRosMasterUri()}]: master may not be running yet.`);
323+
reject(new Error('Registration with master timed out.'));
324+
return;
325+
} else {
326+
log.warnThrottle(60000, `Unable to register with master node [${rosNode.getRosMasterUri()}]: master may not be running yet. Will keep trying.`);
327+
localHelper(resolve, reject);
328+
}
329+
});
330+
}, timeout);
331+
};
332+
333+
return new Promise((resolve, reject) => {
334+
localHelper(resolve,reject);
335+
});
336+
}
337+
338+
function _resolveNodeName(nodeName, remappings, options) {
339+
let namespace = remappings['__ns'] || process.env.ROS_NAMESPACE || '';
340+
namespace = names.clean(namespace);
341+
if (namespace.length === 0 || !namespace.startsWith('/')) {
342+
namespace = `/${namespace}`;
343+
}
344+
345+
names.validate(namespace, true);
346+
347+
nodeName = remappings['__name'] || nodeName;
348+
nodeName = names.resolve(namespace, nodeName);
349+
350+
// only anonymize node name if they didn't remap from the command line
351+
if (options.anonymous && !remappings['__name']) {
352+
nodeName = _anonymizeNodeName(nodeName);
353+
}
354+
355+
return [nodeName, namespace]
356+
}
357+
358+
/**
359+
* Appends a random string of numeric characters to the end
360+
* of the node name. Follows rospy logic.
361+
* @param nodeName {string} string to anonymize
362+
* @return {string} anonymized nodeName
363+
*/
364+
function _anonymizeNodeName(nodeName) {
365+
return util.format('%s_%s_%s', nodeName, process.pid, Date.now());
366+
}

0 commit comments

Comments
 (0)