roslib
Version:
The standard ROS Javascript Library
692 lines (643 loc) • 19.9 kB
JavaScript
/**
* @fileoverview
* @author Brandon Alexander - baalexander@gmail.com
*/
var WebSocket = require('ws');
var WorkerSocket = require('../util/workerSocket');
var socketAdapter = require('./SocketAdapter.js');
var Service = require('./Service');
var ServiceRequest = require('./ServiceRequest');
var assign = require('object-assign');
var EventEmitter2 = require('eventemitter2').EventEmitter2;
/**
* Manages connection to the server and all interactions with ROS.
*
* Emits the following events:
* * 'error' - there was an error with ROS
* * 'connection' - connected to the WebSocket server
* * 'close' - disconnected to the WebSocket server
* * <topicName> - a message came from rosbridge with the given topic name
* * <serviceID> - a service response came from rosbridge with the given ID
*
* @constructor
* @param options - possible keys include: <br>
* * url (optional) - (can be specified later with `connect`) the WebSocket URL for rosbridge or the node server url to connect using socket.io (if socket.io exists in the page) <br>
* * groovyCompatibility - don't use interfaces that changed after the last groovy release or rosbridge_suite and related tools (defaults to true)
* * transportLibrary (optional) - one of 'websocket', 'workersocket' (default), 'socket.io' or RTCPeerConnection instance controlling how the connection is created in `connect`.
* * transportOptions (optional) - the options to use use when creating a connection. Currently only used if `transportLibrary` is RTCPeerConnection.
*/
function Ros(options) {
options = options || {};
this.socket = null;
this.idCounter = 0;
this.isConnected = false;
this.transportLibrary = options.transportLibrary || 'websocket';
this.transportOptions = options.transportOptions || {};
if (typeof options.groovyCompatibility === 'undefined') {
this.groovyCompatibility = true;
}
else {
this.groovyCompatibility = options.groovyCompatibility;
}
// Sets unlimited event listeners.
this.setMaxListeners(0);
// begin by checking if a URL was given
if (options.url) {
this.connect(options.url);
}
}
Ros.prototype.__proto__ = EventEmitter2.prototype;
/**
* Connect to the specified WebSocket.
*
* @param url - WebSocket URL or RTCDataChannel label for Rosbridge
*/
Ros.prototype.connect = function(url) {
if (this.transportLibrary === 'socket.io') {
this.socket = assign(io(url, {'force new connection': true}), socketAdapter(this));
this.socket.on('connect', this.socket.onopen);
this.socket.on('data', this.socket.onmessage);
this.socket.on('close', this.socket.onclose);
this.socket.on('error', this.socket.onerror);
} else if (this.transportLibrary.constructor.name === 'RTCPeerConnection') {
this.socket = assign(this.transportLibrary.createDataChannel(url, this.transportOptions), socketAdapter(this));
} else if (this.transportLibrary === 'websocket') {
if (!this.socket || this.socket.readyState === WebSocket.CLOSED) {
var sock = new WebSocket(url);
sock.binaryType = 'arraybuffer';
this.socket = assign(sock, socketAdapter(this));
}
} else if (this.transportLibrary === 'workersocket') {
this.socket = assign(new WorkerSocket(url), socketAdapter(this));
} else {
throw 'Unknown transportLibrary: ' + this.transportLibrary.toString();
}
};
/**
* Disconnect from the WebSocket server.
*/
Ros.prototype.close = function() {
if (this.socket) {
this.socket.close();
}
};
/**
* Sends an authorization request to the server.
*
* @param mac - MAC (hash) string given by the trusted source.
* @param client - IP of the client.
* @param dest - IP of the destination.
* @param rand - Random string given by the trusted source.
* @param t - Time of the authorization request.
* @param level - User level as a string given by the client.
* @param end - End time of the client's session.
*/
Ros.prototype.authenticate = function(mac, client, dest, rand, t, level, end) {
// create the request
var auth = {
op : 'auth',
mac : mac,
client : client,
dest : dest,
rand : rand,
t : t,
level : level,
end : end
};
// send the request
this.callOnConnection(auth);
};
/**
* Sends the message over the WebSocket, but queues the message up if not yet
* connected.
*/
Ros.prototype.callOnConnection = function(message) {
var that = this;
var messageJson = JSON.stringify(message);
var emitter = null;
if (this.transportLibrary === 'socket.io') {
emitter = function(msg){that.socket.emit('operation', msg);};
} else {
emitter = function(msg){that.socket.send(msg);};
}
if (!this.isConnected) {
that.once('connection', function() {
emitter(messageJson);
});
} else {
emitter(messageJson);
}
};
/**
* Sends a set_level request to the server
*
* @param level - Status level (none, error, warning, info)
* @param id - Optional: Operation ID to change status level on
*/
Ros.prototype.setStatusLevel = function(level, id){
var levelMsg = {
op: 'set_level',
level: level,
id: id
};
this.callOnConnection(levelMsg);
};
/**
* Retrieves Action Servers in ROS as an array of string
*
* @param callback function with params:
* * actionservers - Array of action server names
* @param failedCallback - the callback function when the service call failed (optional). Params:
* * error - the error message reported by ROS
*/
Ros.prototype.getActionServers = function(callback, failedCallback) {
var getActionServers = new Service({
ros : this,
name : '/rosapi/action_servers',
serviceType : 'rosapi/GetActionServers'
});
var request = new ServiceRequest({});
if (typeof failedCallback === 'function'){
getActionServers.callService(request,
function(result) {
callback(result.action_servers);
},
function(message){
failedCallback(message);
}
);
}else{
getActionServers.callService(request, function(result) {
callback(result.action_servers);
});
}
};
/**
* Retrieves list of topics in ROS as an array.
*
* @param callback function with params:
* * topics - Array of topic names
* * types - Array of message type names
* @param failedCallback - the callback function when the service call failed (optional). Params:
* * error - the error message reported by ROS
*/
Ros.prototype.getTopics = function(callback, failedCallback) {
var topicsClient = new Service({
ros : this,
name : '/rosapi/topics',
serviceType : 'rosapi/Topics'
});
var request = new ServiceRequest();
if (typeof failedCallback === 'function'){
topicsClient.callService(request,
function(result) {
callback(result);
},
function(message){
failedCallback(message);
}
);
}else{
topicsClient.callService(request, function(result) {
callback(result);
});
}
};
/**
* Retrieves Topics in ROS as an array as specific type
*
* @param topicType topic type to find
* @param callback function with params:
* * topics - Array of topic names
* @param failedCallback - the callback function when the service call failed (optional). Params:
* * error - the error message reported by ROS
*/
Ros.prototype.getTopicsForType = function(topicType, callback, failedCallback) {
var topicsForTypeClient = new Service({
ros : this,
name : '/rosapi/topics_for_type',
serviceType : 'rosapi/TopicsForType'
});
var request = new ServiceRequest({
type: topicType
});
if (typeof failedCallback === 'function'){
topicsForTypeClient.callService(request,
function(result) {
callback(result.topics);
},
function(message){
failedCallback(message);
}
);
}else{
topicsForTypeClient.callService(request, function(result) {
callback(result.topics);
});
}
};
/**
* Retrieves list of active service names in ROS.
*
* @param callback - function with the following params:
* * services - array of service names
* @param failedCallback - the callback function when the service call failed (optional). Params:
* * error - the error message reported by ROS
*/
Ros.prototype.getServices = function(callback, failedCallback) {
var servicesClient = new Service({
ros : this,
name : '/rosapi/services',
serviceType : 'rosapi/Services'
});
var request = new ServiceRequest();
if (typeof failedCallback === 'function'){
servicesClient.callService(request,
function(result) {
callback(result.services);
},
function(message) {
failedCallback(message);
}
);
}else{
servicesClient.callService(request, function(result) {
callback(result.services);
});
}
};
/**
* Retrieves list of services in ROS as an array as specific type
*
* @param serviceType service type to find
* @param callback function with params:
* * topics - Array of service names
* @param failedCallback - the callback function when the service call failed (optional). Params:
* * error - the error message reported by ROS
*/
Ros.prototype.getServicesForType = function(serviceType, callback, failedCallback) {
var servicesForTypeClient = new Service({
ros : this,
name : '/rosapi/services_for_type',
serviceType : 'rosapi/ServicesForType'
});
var request = new ServiceRequest({
type: serviceType
});
if (typeof failedCallback === 'function'){
servicesForTypeClient.callService(request,
function(result) {
callback(result.services);
},
function(message) {
failedCallback(message);
}
);
}else{
servicesForTypeClient.callService(request, function(result) {
callback(result.services);
});
}
};
/**
* Retrieves a detail of ROS service request.
*
* @param service name of service:
* @param callback - function with params:
* * type - String of the service type
* @param failedCallback - the callback function when the service call failed (optional). Params:
* * error - the error message reported by ROS
*/
Ros.prototype.getServiceRequestDetails = function(type, callback, failedCallback) {
var serviceTypeClient = new Service({
ros : this,
name : '/rosapi/service_request_details',
serviceType : 'rosapi/ServiceRequestDetails'
});
var request = new ServiceRequest({
type: type
});
if (typeof failedCallback === 'function'){
serviceTypeClient.callService(request,
function(result) {
callback(result);
},
function(message){
failedCallback(message);
}
);
}else{
serviceTypeClient.callService(request, function(result) {
callback(result);
});
}
};
/**
* Retrieves a detail of ROS service request.
*
* @param service name of service
* @param callback - function with params:
* * type - String of the service type
* @param failedCallback - the callback function when the service call failed (optional). Params:
* * error - the error message reported by ROS
*/
Ros.prototype.getServiceResponseDetails = function(type, callback, failedCallback) {
var serviceTypeClient = new Service({
ros : this,
name : '/rosapi/service_response_details',
serviceType : 'rosapi/ServiceResponseDetails'
});
var request = new ServiceRequest({
type: type
});
if (typeof failedCallback === 'function'){
serviceTypeClient.callService(request,
function(result) {
callback(result);
},
function(message){
failedCallback(message);
}
);
}else{
serviceTypeClient.callService(request, function(result) {
callback(result);
});
}
};
/**
* Retrieves list of active node names in ROS.
*
* @param callback - function with the following params:
* * nodes - array of node names
* @param failedCallback - the callback function when the service call failed (optional). Params:
* * error - the error message reported by ROS
*/
Ros.prototype.getNodes = function(callback, failedCallback) {
var nodesClient = new Service({
ros : this,
name : '/rosapi/nodes',
serviceType : 'rosapi/Nodes'
});
var request = new ServiceRequest();
if (typeof failedCallback === 'function'){
nodesClient.callService(request,
function(result) {
callback(result.nodes);
},
function(message) {
failedCallback(message);
}
);
}else{
nodesClient.callService(request, function(result) {
callback(result.nodes);
});
}
};
/**
* Retrieves list subscribed topics, publishing topics and services of a specific node
*
* @param node name of the node:
* @param callback - function with params:
* * publications - array of published topic names
* * subscriptions - array of subscribed topic names
* * services - array of service names hosted
* @param failedCallback - the callback function when the service call failed (optional). Params:
* * error - the error message reported by ROS
*/
Ros.prototype.getNodeDetails = function(node, callback, failedCallback) {
var nodesClient = new Service({
ros : this,
name : '/rosapi/node_details',
serviceType : 'rosapi/NodeDetails'
});
var request = new ServiceRequest({
node: node
});
if (typeof failedCallback === 'function'){
nodesClient.callService(request,
function(result) {
callback(result.subscribing, result.publishing, result.services);
},
function(message) {
failedCallback(message);
}
);
} else {
nodesClient.callService(request, function(result) {
callback(result);
});
}
};
/**
* Retrieves list of param names from the ROS Parameter Server.
*
* @param callback function with params:
* * params - array of param names.
* @param failedCallback - the callback function when the service call failed (optional). Params:
* * error - the error message reported by ROS
*/
Ros.prototype.getParams = function(callback, failedCallback) {
var paramsClient = new Service({
ros : this,
name : '/rosapi/get_param_names',
serviceType : 'rosapi/GetParamNames'
});
var request = new ServiceRequest();
if (typeof failedCallback === 'function'){
paramsClient.callService(request,
function(result) {
callback(result.names);
},
function(message){
failedCallback(message);
}
);
}else{
paramsClient.callService(request, function(result) {
callback(result.names);
});
}
};
/**
* Retrieves a type of ROS topic.
*
* @param topic name of the topic:
* @param callback - function with params:
* * type - String of the topic type
* @param failedCallback - the callback function when the service call failed (optional). Params:
* * error - the error message reported by ROS
*/
Ros.prototype.getTopicType = function(topic, callback, failedCallback) {
var topicTypeClient = new Service({
ros : this,
name : '/rosapi/topic_type',
serviceType : 'rosapi/TopicType'
});
var request = new ServiceRequest({
topic: topic
});
if (typeof failedCallback === 'function'){
topicTypeClient.callService(request,
function(result) {
callback(result.type);
},
function(message){
failedCallback(message);
}
);
}else{
topicTypeClient.callService(request, function(result) {
callback(result.type);
});
}
};
/**
* Retrieves a type of ROS service.
*
* @param service name of service:
* @param callback - function with params:
* * type - String of the service type
* @param failedCallback - the callback function when the service call failed (optional). Params:
* * error - the error message reported by ROS
*/
Ros.prototype.getServiceType = function(service, callback, failedCallback) {
var serviceTypeClient = new Service({
ros : this,
name : '/rosapi/service_type',
serviceType : 'rosapi/ServiceType'
});
var request = new ServiceRequest({
service: service
});
if (typeof failedCallback === 'function'){
serviceTypeClient.callService(request,
function(result) {
callback(result.type);
},
function(message){
failedCallback(message);
}
);
}else{
serviceTypeClient.callService(request, function(result) {
callback(result.type);
});
}
};
/**
* Retrieves a detail of ROS message.
*
* @param message - String of a topic type
* @param callback - function with params:
* * details - Array of the message detail
* @param failedCallback - the callback function when the service call failed (optional). Params:
* * error - the error message reported by ROS
*/
Ros.prototype.getMessageDetails = function(message, callback, failedCallback) {
var messageDetailClient = new Service({
ros : this,
name : '/rosapi/message_details',
serviceType : 'rosapi/MessageDetails'
});
var request = new ServiceRequest({
type: message
});
if (typeof failedCallback === 'function'){
messageDetailClient.callService(request,
function(result) {
callback(result.typedefs);
},
function(message){
failedCallback(message);
}
);
}else{
messageDetailClient.callService(request, function(result) {
callback(result.typedefs);
});
}
};
/**
* Decode a typedefs into a dictionary like `rosmsg show foo/bar`
*
* @param defs - array of type_def dictionary
*/
Ros.prototype.decodeTypeDefs = function(defs) {
var that = this;
// calls itself recursively to resolve type definition using hints.
var decodeTypeDefsRec = function(theType, hints) {
var typeDefDict = {};
for (var i = 0; i < theType.fieldnames.length; i++) {
var arrayLen = theType.fieldarraylen[i];
var fieldName = theType.fieldnames[i];
var fieldType = theType.fieldtypes[i];
if (fieldType.indexOf('/') === -1) { // check the fieldType includes '/' or not
if (arrayLen === -1) {
typeDefDict[fieldName] = fieldType;
}
else {
typeDefDict[fieldName] = [fieldType];
}
}
else {
// lookup the name
var sub = false;
for (var j = 0; j < hints.length; j++) {
if (hints[j].type.toString() === fieldType.toString()) {
sub = hints[j];
break;
}
}
if (sub) {
var subResult = decodeTypeDefsRec(sub, hints);
if (arrayLen === -1) {
}
else {
typeDefDict[fieldName] = [subResult];
}
}
else {
that.emit('error', 'Cannot find ' + fieldType + ' in decodeTypeDefs');
}
}
}
return typeDefDict;
};
return decodeTypeDefsRec(defs[0], defs);
};
/**
* Retrieves list of topics and their associated type definitions.
*
* @param callback function with params:
* * topics - Array of topic names
* * types - Array of message type names
* * typedefs_full_text - Array of full definitions of message types, similar to `gendeps --cat`
* @param failedCallback - the callback function when the service call failed (optional). Params:
* * error - the error message reported by ROS
*
*/
Ros.prototype.getTopicsAndRawTypes = function(callback, failedCallback) {
var topicsAndRawTypesClient = new Service({
ros : this,
name : '/rosapi/topics_and_raw_types',
serviceType : 'rosapi/TopicsAndRawTypes'
});
var request = new ServiceRequest();
if (typeof failedCallback === 'function'){
topicsAndRawTypesClient.callService(request,
function(result) {
callback(result);
},
function(message){
failedCallback(message);
}
);
}else{
topicsAndRawTypesClient.callService(request, function(result) {
callback(result);
});
}
};
module.exports = Ros;