(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o 0 && tree._listeners.length > this._events.maxListeners ) { tree._listeners.warned = true; logPossibleMemoryLeak.call(this, tree._listeners.length, name); } } return true; } name = type.shift(); } return true; } // By default EventEmitters will print a warning if more than // 10 listeners are added to it. This is a useful default which // helps finding memory leaks. // // Obviously not all Emitters should be limited to 10. This function allows // that to be increased. Set to zero for unlimited. EventEmitter.prototype.delimiter = '.'; EventEmitter.prototype.setMaxListeners = function(n) { if (n !== undefined) { this._events || init.call(this); this._events.maxListeners = n; if (!this._conf) this._conf = {}; this._conf.maxListeners = n; } }; EventEmitter.prototype.event = ''; EventEmitter.prototype.once = function(event, fn) { this.many(event, 1, fn); return this; }; EventEmitter.prototype.many = function(event, ttl, fn) { var self = this; if (typeof fn !== 'function') { throw new Error('many only accepts instances of Function'); } function listener() { if (--ttl === 0) { self.off(event, listener); } fn.apply(this, arguments); } listener._origin = fn; this.on(event, listener); return self; }; EventEmitter.prototype.emit = function() { this._events || init.call(this); var type = arguments[0]; if (type === 'newListener' && !this.newListener) { if (!this._events.newListener) { return false; } } var al = arguments.length; var args,l,i,j; var handler; if (this._all && this._all.length) { handler = this._all.slice(); if (al > 3) { args = new Array(al); for (j = 0; j < al; j++) args[j] = arguments[j]; } for (i = 0, l = handler.length; i < l; i++) { this.event = type; switch (al) { case 1: handler[i].call(this, type); break; case 2: handler[i].call(this, type, arguments[1]); break; case 3: handler[i].call(this, type, arguments[1], arguments[2]); break; default: handler[i].apply(this, args); } } } if (this.wildcard) { handler = []; var ns = typeof type === 'string' ? type.split(this.delimiter) : type.slice(); searchListenerTree.call(this, handler, ns, this.listenerTree, 0); } else { handler = this._events[type]; if (typeof handler === 'function') { this.event = type; switch (al) { case 1: handler.call(this); break; case 2: handler.call(this, arguments[1]); break; case 3: handler.call(this, arguments[1], arguments[2]); break; default: args = new Array(al - 1); for (j = 1; j < al; j++) args[j - 1] = arguments[j]; handler.apply(this, args); } return true; } else if (handler) { // need to make copy of handlers because list can change in the middle // of emit call handler = handler.slice(); } } if (handler && handler.length) { if (al > 3) { args = new Array(al - 1); for (j = 1; j < al; j++) args[j - 1] = arguments[j]; } for (i = 0, l = handler.length; i < l; i++) { this.event = type; switch (al) { case 1: handler[i].call(this); break; case 2: handler[i].call(this, arguments[1]); break; case 3: handler[i].call(this, arguments[1], arguments[2]); break; default: handler[i].apply(this, args); } } return true; } else if (!this._all && type === 'error') { if (arguments[1] instanceof Error) { throw arguments[1]; // Unhandled 'error' event } else { throw new Error("Uncaught, unspecified 'error' event."); } return false; } return !!this._all; }; EventEmitter.prototype.emitAsync = function() { this._events || init.call(this); var type = arguments[0]; if (type === 'newListener' && !this.newListener) { if (!this._events.newListener) { return Promise.resolve([false]); } } var promises= []; var al = arguments.length; var args,l,i,j; var handler; if (this._all) { if (al > 3) { args = new Array(al); for (j = 1; j < al; j++) args[j] = arguments[j]; } for (i = 0, l = this._all.length; i < l; i++) { this.event = type; switch (al) { case 1: promises.push(this._all[i].call(this, type)); break; case 2: promises.push(this._all[i].call(this, type, arguments[1])); break; case 3: promises.push(this._all[i].call(this, type, arguments[1], arguments[2])); break; default: promises.push(this._all[i].apply(this, args)); } } } if (this.wildcard) { handler = []; var ns = typeof type === 'string' ? type.split(this.delimiter) : type.slice(); searchListenerTree.call(this, handler, ns, this.listenerTree, 0); } else { handler = this._events[type]; } if (typeof handler === 'function') { this.event = type; switch (al) { case 1: promises.push(handler.call(this)); break; case 2: promises.push(handler.call(this, arguments[1])); break; case 3: promises.push(handler.call(this, arguments[1], arguments[2])); break; default: args = new Array(al - 1); for (j = 1; j < al; j++) args[j - 1] = arguments[j]; promises.push(handler.apply(this, args)); } } else if (handler && handler.length) { if (al > 3) { args = new Array(al - 1); for (j = 1; j < al; j++) args[j - 1] = arguments[j]; } for (i = 0, l = handler.length; i < l; i++) { this.event = type; switch (al) { case 1: promises.push(handler[i].call(this)); break; case 2: promises.push(handler[i].call(this, arguments[1])); break; case 3: promises.push(handler[i].call(this, arguments[1], arguments[2])); break; default: promises.push(handler[i].apply(this, args)); } } } else if (!this._all && type === 'error') { if (arguments[1] instanceof Error) { return Promise.reject(arguments[1]); // Unhandled 'error' event } else { return Promise.reject("Uncaught, unspecified 'error' event."); } } return Promise.all(promises); }; EventEmitter.prototype.on = function(type, listener) { if (typeof type === 'function') { this.onAny(type); return this; } if (typeof listener !== 'function') { throw new Error('on only accepts instances of Function'); } this._events || init.call(this); // To avoid recursion in the case that type == "newListeners"! Before // adding it to the listeners, first emit "newListeners". this.emit('newListener', type, listener); if (this.wildcard) { growListenerTree.call(this, type, listener); return this; } if (!this._events[type]) { // Optimize the case of one listener. Don't need the extra array object. this._events[type] = listener; } else { if (typeof this._events[type] === 'function') { // Change to array. this._events[type] = [this._events[type]]; } // If we've already got an array, just append. this._events[type].push(listener); // Check for listener leak if ( !this._events[type].warned && this._events.maxListeners > 0 && this._events[type].length > this._events.maxListeners ) { this._events[type].warned = true; logPossibleMemoryLeak.call(this, this._events[type].length, type); } } return this; }; EventEmitter.prototype.onAny = function(fn) { if (typeof fn !== 'function') { throw new Error('onAny only accepts instances of Function'); } if (!this._all) { this._all = []; } // Add the function to the event listener collection. this._all.push(fn); return this; }; EventEmitter.prototype.addListener = EventEmitter.prototype.on; EventEmitter.prototype.off = function(type, listener) { if (typeof listener !== 'function') { throw new Error('removeListener only takes instances of Function'); } var handlers,leafs=[]; if(this.wildcard) { var ns = typeof type === 'string' ? type.split(this.delimiter) : type.slice(); leafs = searchListenerTree.call(this, null, ns, this.listenerTree, 0); } else { // does not use listeners(), so no side effect of creating _events[type] if (!this._events[type]) return this; handlers = this._events[type]; leafs.push({_listeners:handlers}); } for (var iLeaf=0; iLeaf 0) { recursivelyGarbageCollect(root[key]); } if (Object.keys(obj).length === 0) { delete root[key]; } } } recursivelyGarbageCollect(this.listenerTree); return this; }; EventEmitter.prototype.offAny = function(fn) { var i = 0, l = 0, fns; if (fn && this._all && this._all.length > 0) { fns = this._all; for(i = 0, l = fns.length; i < l; i++) { if(fn === fns[i]) { fns.splice(i, 1); this.emit("removeListenerAny", fn); return this; } } } else { fns = this._all; for(i = 0, l = fns.length; i < l; i++) this.emit("removeListenerAny", fns[i]); this._all = []; } return this; }; EventEmitter.prototype.removeListener = EventEmitter.prototype.off; EventEmitter.prototype.removeAllListeners = function(type) { if (arguments.length === 0) { !this._events || init.call(this); return this; } if (this.wildcard) { var ns = typeof type === 'string' ? type.split(this.delimiter) : type.slice(); var leafs = searchListenerTree.call(this, null, ns, this.listenerTree, 0); for (var iLeaf=0; iLeaf t2.secs) { return false; } else if(t1.secs < t2.secs) { return true; } else if(t1.nsecs < t2.nsecs) { return true; } else { return false; } }; // TODO: this may be more complicated than necessary, since I'm // not sure if the callbacks can ever wind up with a scenario // where we've been preempted by a next goal, it hasn't finished // processing, and then we get a cancel message cancelListener.subscribe(function(cancelMessage) { // cancel ALL goals if both empty if(cancelMessage.stamp.secs === 0 && cancelMessage.stamp.secs === 0 && cancelMessage.id === '') { that.nextGoal = null; if(that.currentGoal) { that.emit('cancel'); } } else { // treat id and stamp independently if(that.currentGoal && cancelMessage.id === that.currentGoal.goal_id.id) { that.emit('cancel'); } else if(that.nextGoal && cancelMessage.id === that.nextGoal.goal_id.id) { that.nextGoal = null; } if(that.nextGoal && isEarlier(that.nextGoal.goal_id.stamp, cancelMessage.stamp)) { that.nextGoal = null; } if(that.currentGoal && isEarlier(that.currentGoal.goal_id.stamp, cancelMessage.stamp)) { that.emit('cancel'); } } }); // publish status at pseudo-fixed rate; required for clients to know they've connected var statusInterval = setInterval( function() { var currentTime = new Date(); var secs = Math.floor(currentTime.getTime()/1000); var nsecs = Math.round(1000000000*(currentTime.getTime()/1000-secs)); that.statusMessage.header.stamp.secs = secs; that.statusMessage.header.stamp.nsecs = nsecs; statusPublisher.publish(that.statusMessage); }, 500); // publish every 500ms } SimpleActionServer.prototype.__proto__ = EventEmitter2.prototype; /** * Set action state to succeeded and return to client */ SimpleActionServer.prototype.setSucceeded = function(result2) { var resultMessage = new Message({ status : {goal_id : this.currentGoal.goal_id, status : 3}, result : result2 }); this.resultPublisher.publish(resultMessage); this.statusMessage.status_list = []; if(this.nextGoal) { this.currentGoal = this.nextGoal; this.nextGoal = null; this.emit('goal', this.currentGoal.goal); } else { this.currentGoal = null; } }; /** * Function to send feedback */ SimpleActionServer.prototype.sendFeedback = function(feedback2) { var feedbackMessage = new Message({ status : {goal_id : this.currentGoal.goal_id, status : 1}, feedback : feedback2 }); this.feedbackPublisher.publish(feedbackMessage); }; /** * Handle case where client requests preemption */ SimpleActionServer.prototype.setPreempted = function() { this.statusMessage.status_list = []; var resultMessage = new Message({ status : {goal_id : this.currentGoal.goal_id, status : 2}, }); this.resultPublisher.publish(resultMessage); if(this.nextGoal) { this.currentGoal = this.nextGoal; this.nextGoal = null; this.emit('goal', this.currentGoal.goal); } else { this.currentGoal = null; } }; module.exports = SimpleActionServer; },{"../core/Message":10,"../core/Topic":17,"eventemitter2":1}],9:[function(require,module,exports){ var Ros = require('../core/Ros'); var mixin = require('../mixin'); var action = module.exports = { ActionClient: require('./ActionClient'), ActionListener: require('./ActionListener'), Goal: require('./Goal'), SimpleActionServer: require('./SimpleActionServer') }; mixin(Ros, ['ActionClient', 'SimpleActionServer'], action); },{"../core/Ros":12,"../mixin":24,"./ActionClient":5,"./ActionListener":6,"./Goal":7,"./SimpleActionServer":8}],10:[function(require,module,exports){ /** * @fileoverview * @author Brandon Alexander - baalexander@gmail.com */ var assign = require('object-assign'); /** * Message objects are used for publishing and subscribing to and from topics. * * @constructor * @param values - object matching the fields defined in the .msg definition file */ function Message(values) { assign(this, values); } module.exports = Message; },{"object-assign":2}],11:[function(require,module,exports){ /** * @fileoverview * @author Brandon Alexander - baalexander@gmail.com */ var Service = require('./Service'); var ServiceRequest = require('./ServiceRequest'); /** * A ROS parameter. * * @constructor * @param options - possible keys include: * * ros - the ROSLIB.Ros connection handle * * name - the param name, like max_vel_x */ function Param(options) { options = options || {}; this.ros = options.ros; this.name = options.name; } /** * Fetches the value of the param. * * @param callback - function with the following params: * * value - the value of the param from ROS. */ Param.prototype.get = function(callback) { var paramClient = new Service({ ros : this.ros, name : '/rosapi/get_param', serviceType : 'rosapi/GetParam' }); var request = new ServiceRequest({ name : this.name }); paramClient.callService(request, function(result) { var value = JSON.parse(result.value); callback(value); }); }; /** * Sets the value of the param in ROS. * * @param value - value to set param to. */ Param.prototype.set = function(value, callback) { var paramClient = new Service({ ros : this.ros, name : '/rosapi/set_param', serviceType : 'rosapi/SetParam' }); var request = new ServiceRequest({ name : this.name, value : JSON.stringify(value) }); paramClient.callService(request, callback); }; /** * Delete this parameter on the ROS server. */ Param.prototype.delete = function(callback) { var paramClient = new Service({ ros : this.ros, name : '/rosapi/delete_param', serviceType : 'rosapi/DeleteParam' }); var request = new ServiceRequest({ name : this.name }); paramClient.callService(request, callback); }; module.exports = Param; },{"./Service":13,"./ServiceRequest":14}],12:[function(require,module,exports){ /** * @fileoverview * @author Brandon Alexander - baalexander@gmail.com */ var WebSocket = require('ws'); 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 * * - a message came from rosbridge with the given topic name * * - a service response came from rosbridge with the given ID * * @constructor * @param options - possible keys include:
* * 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)
* * 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' (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 { this.socket = assign(new WebSocket(url), socketAdapter(this)); } }; /** * 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 * * * actionservers - Array of action server names */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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. */ 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 */ 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 */ 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 callback - function with params: * * details - Array of the message detail * @param message - String of a topic type */ 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); }; module.exports = Ros; },{"./Service":13,"./ServiceRequest":14,"./SocketAdapter.js":16,"eventemitter2":1,"object-assign":2,"ws":39}],13:[function(require,module,exports){ /** * @fileoverview * @author Brandon Alexander - baalexander@gmail.com */ var ServiceResponse = require('./ServiceResponse'); var ServiceRequest = require('./ServiceRequest'); var EventEmitter2 = require('eventemitter2').EventEmitter2; /** * A ROS service client. * * @constructor * @params options - possible keys include: * * ros - the ROSLIB.Ros connection handle * * name - the service name, like /add_two_ints * * serviceType - the service type, like 'rospy_tutorials/AddTwoInts' */ function Service(options) { options = options || {}; this.ros = options.ros; this.name = options.name; this.serviceType = options.serviceType; this.isAdvertised = false; this._serviceCallback = null; } Service.prototype.__proto__ = EventEmitter2.prototype; /** * Calls the service. Returns the service response in the callback. * * @param request - the ROSLIB.ServiceRequest to send * @param callback - function with params: * * response - the response from the service request * @param failedCallback - the callback function when the service call failed (optional). Params: * * error - the error message reported by ROS */ Service.prototype.callService = function(request, callback, failedCallback) { if (this.isAdvertised) { return; } var serviceCallId = 'call_service:' + this.name + ':' + (++this.ros.idCounter); if (callback || failedCallback) { this.ros.once(serviceCallId, function(message) { if (message.result !== undefined && message.result === false) { if (typeof failedCallback === 'function') { failedCallback(message.values); } } else if (typeof callback === 'function') { callback(new ServiceResponse(message.values)); } }); } var call = { op : 'call_service', id : serviceCallId, service : this.name, args : request }; this.ros.callOnConnection(call); }; /** * Every time a message is published for the given topic, the callback * will be called with the message object. * * @param callback - function with the following params: * * message - the published message */ Service.prototype.advertise = function(callback) { if (this.isAdvertised || typeof callback !== 'function') { return; } this._serviceCallback = callback; this.ros.on(this.name, this._serviceResponse.bind(this)); this.ros.callOnConnection({ op: 'advertise_service', type: this.serviceType, service: this.name }); this.isAdvertised = true; }; Service.prototype.unadvertise = function() { if (!this.isAdvertised) { return; } this.ros.callOnConnection({ op: 'unadvertise_service', service: this.name }); this.isAdvertised = false; }; Service.prototype._serviceResponse = function(rosbridgeRequest) { var response = {}; var success = this._serviceCallback(rosbridgeRequest.args, response); var call = { op: 'service_response', service: this.name, values: new ServiceResponse(response), result: success }; if (rosbridgeRequest.id) { call.id = rosbridgeRequest.id; } this.ros.callOnConnection(call); }; module.exports = Service; },{"./ServiceRequest":14,"./ServiceResponse":15,"eventemitter2":1}],14:[function(require,module,exports){ /** * @fileoverview * @author Brandon Alexander - balexander@willowgarage.com */ var assign = require('object-assign'); /** * A ServiceRequest is passed into the service call. * * @constructor * @param values - object matching the fields defined in the .srv definition file */ function ServiceRequest(values) { assign(this, values); } module.exports = ServiceRequest; },{"object-assign":2}],15:[function(require,module,exports){ /** * @fileoverview * @author Brandon Alexander - balexander@willowgarage.com */ var assign = require('object-assign'); /** * A ServiceResponse is returned from the service call. * * @constructor * @param values - object matching the fields defined in the .srv definition file */ function ServiceResponse(values) { assign(this, values); } module.exports = ServiceResponse; },{"object-assign":2}],16:[function(require,module,exports){ /** * Socket event handling utilities for handling events on either * WebSocket and TCP sockets * * Note to anyone reviewing this code: these functions are called * in the context of their parent object, unless bound * @fileOverview */ 'use strict'; var decompressPng = require('../util/decompressPng'); var WebSocket = require('ws'); var BSON = null; if(typeof bson !== 'undefined'){ BSON = bson().BSON; } /** * Events listeners for a WebSocket or TCP socket to a JavaScript * ROS Client. Sets up Messages for a given topic to trigger an * event on the ROS client. * * @namespace SocketAdapter * @private */ function SocketAdapter(client) { function handleMessage(message) { if (message.op === 'publish') { client.emit(message.topic, message.msg); } else if (message.op === 'service_response') { client.emit(message.id, message); } else if (message.op === 'call_service') { client.emit(message.service, message); } else if(message.op === 'status'){ if(message.id){ client.emit('status:'+message.id, message); } else { client.emit('status', message); } } } function handlePng(message, callback) { if (message.op === 'png') { decompressPng(message.data, callback); } else { callback(message); } } function decodeBSON(data, callback) { if (!BSON) { throw 'Cannot process BSON encoded message without BSON header.'; } var reader = new FileReader(); reader.onload = function() { var uint8Array = new Uint8Array(this.result); var msg = BSON.deserialize(uint8Array); callback(msg); }; reader.readAsArrayBuffer(data); } return { /** * Emits a 'connection' event on WebSocket connection. * * @param event - the argument to emit with the event. * @memberof SocketAdapter */ onopen: function onOpen(event) { client.isConnected = true; client.emit('connection', event); }, /** * Emits a 'close' event on WebSocket disconnection. * * @param event - the argument to emit with the event. * @memberof SocketAdapter */ onclose: function onClose(event) { client.isConnected = false; client.emit('close', event); }, /** * Emits an 'error' event whenever there was an error. * * @param event - the argument to emit with the event. * @memberof SocketAdapter */ onerror: function onError(event) { client.emit('error', event); }, /** * Parses message responses from rosbridge and sends to the appropriate * topic, service, or param. * * @param message - the raw JSON message from rosbridge. * @memberof SocketAdapter */ onmessage: function onMessage(data) { if (typeof Blob !== 'undefined' && data.data instanceof Blob) { decodeBSON(data.data, function (message) { handlePng(message, handleMessage); }); } else { var message = JSON.parse(typeof data === 'string' ? data : data.data); handlePng(message, handleMessage); } } }; } module.exports = SocketAdapter; },{"../util/decompressPng":41,"ws":39}],17:[function(require,module,exports){ /** * @fileoverview * @author Brandon Alexander - baalexander@gmail.com */ var EventEmitter2 = require('eventemitter2').EventEmitter2; var Message = require('./Message'); /** * Publish and/or subscribe to a topic in ROS. * * Emits the following events: * * 'warning' - if there are any warning during the Topic creation * * 'message' - the message data from rosbridge * * @constructor * @param options - object with following keys: * * ros - the ROSLIB.Ros connection handle * * name - the topic name, like /cmd_vel * * messageType - the message type, like 'std_msgs/String' * * compression - the type of compression to use, like 'png' * * throttle_rate - the rate (in ms in between messages) at which to throttle the topics * * queue_size - the queue created at bridge side for re-publishing webtopics (defaults to 100) * * latch - latch the topic when publishing * * queue_length - the queue length at bridge side used when subscribing (defaults to 0, no queueing). * * reconnect_on_close - the flag to enable resubscription and readvertisement on close event(defaults to true). */ function Topic(options) { options = options || {}; this.ros = options.ros; this.name = options.name; this.messageType = options.messageType; this.isAdvertised = false; this.compression = options.compression || 'none'; this.throttle_rate = options.throttle_rate || 0; this.latch = options.latch || false; this.queue_size = options.queue_size || 100; this.queue_length = options.queue_length || 0; this.reconnect_on_close = options.reconnect_on_close || true; // Check for valid compression types if (this.compression && this.compression !== 'png' && this.compression !== 'none') { this.emit('warning', this.compression + ' compression is not supported. No compression will be used.'); } // Check if throttle rate is negative if (this.throttle_rate < 0) { this.emit('warning', this.throttle_rate + ' is not allowed. Set to 0'); this.throttle_rate = 0; } var that = this; if (this.reconnect_on_close) { this.callForSubscribeAndAdvertise = function(message) { that.ros.callOnConnection(message); that.waitForReconnect = false; that.reconnectFunc = function() { if(!that.waitForReconnect) { that.waitForReconnect = true; that.ros.callOnConnection(message); that.ros.once('connection', function() { that.waitForReconnect = false; }); } }; that.ros.on('close', that.reconnectFunc); }; } else { this.callForSubscribeAndAdvertise = this.ros.callOnConnection; } this._messageCallback = function(data) { that.emit('message', new Message(data)); }; } Topic.prototype.__proto__ = EventEmitter2.prototype; /** * Every time a message is published for the given topic, the callback * will be called with the message object. * * @param callback - function with the following params: * * message - the published message */ Topic.prototype.subscribe = function(callback) { if (typeof callback === 'function') { this.on('message', callback); } if (this.subscribeId) { return; } this.ros.on(this.name, this._messageCallback); this.subscribeId = 'subscribe:' + this.name + ':' + (++this.ros.idCounter); this.callForSubscribeAndAdvertise({ op: 'subscribe', id: this.subscribeId, type: this.messageType, topic: this.name, compression: this.compression, throttle_rate: this.throttle_rate, queue_length: this.queue_length }); }; /** * Unregisters as a subscriber for the topic. Unsubscribing stop remove * all subscribe callbacks. To remove a call back, you must explicitly * pass the callback function in. * * @param callback - the optional callback to unregister, if * * provided and other listeners are registered the topic won't * * unsubscribe, just stop emitting to the passed listener */ Topic.prototype.unsubscribe = function(callback) { if (callback) { this.off('message', callback); // If there is any other callbacks still subscribed don't unsubscribe if (this.listeners('message').length) { return; } } if (!this.subscribeId) { return; } // Note: Don't call this.removeAllListeners, allow client to handle that themselves this.ros.off(this.name, this._messageCallback); if(this.reconnect_on_close) { this.ros.off('close', this.reconnectFunc); } this.emit('unsubscribe'); this.ros.callOnConnection({ op: 'unsubscribe', id: this.subscribeId, topic: this.name }); this.subscribeId = null; }; /** * Registers as a publisher for the topic. */ Topic.prototype.advertise = function() { if (this.isAdvertised) { return; } this.advertiseId = 'advertise:' + this.name + ':' + (++this.ros.idCounter); this.callForSubscribeAndAdvertise({ op: 'advertise', id: this.advertiseId, type: this.messageType, topic: this.name, latch: this.latch, queue_size: this.queue_size }); this.isAdvertised = true; if(!this.reconnect_on_close) { var that = this; this.ros.on('close', function() { that.isAdvertised = false; }); } }; /** * Unregisters as a publisher for the topic. */ Topic.prototype.unadvertise = function() { if (!this.isAdvertised) { return; } if(this.reconnect_on_close) { this.ros.off('close', this.reconnectFunc); } this.emit('unadvertise'); this.ros.callOnConnection({ op: 'unadvertise', id: this.advertiseId, topic: this.name }); this.isAdvertised = false; }; /** * Publish the message. * * @param message - A ROSLIB.Message object. */ Topic.prototype.publish = function(message) { if (!this.isAdvertised) { this.advertise(); } this.ros.idCounter++; var call = { op: 'publish', id: 'publish:' + this.name + ':' + this.ros.idCounter, topic: this.name, msg: message, latch: this.latch }; this.ros.callOnConnection(call); }; module.exports = Topic; },{"./Message":10,"eventemitter2":1}],18:[function(require,module,exports){ var mixin = require('../mixin'); var core = module.exports = { Ros: require('./Ros'), Topic: require('./Topic'), Message: require('./Message'), Param: require('./Param'), Service: require('./Service'), ServiceRequest: require('./ServiceRequest'), ServiceResponse: require('./ServiceResponse') }; mixin(core.Ros, ['Param', 'Service', 'Topic'], core); },{"../mixin":24,"./Message":10,"./Param":11,"./Ros":12,"./Service":13,"./ServiceRequest":14,"./ServiceResponse":15,"./Topic":17}],19:[function(require,module,exports){ /** * @fileoverview * @author David Gossow - dgossow@willowgarage.com */ var Vector3 = require('./Vector3'); var Quaternion = require('./Quaternion'); /** * A Pose in 3D space. Values are copied into this object. * * @constructor * @param options - object with following keys: * * position - the Vector3 describing the position * * orientation - the ROSLIB.Quaternion describing the orientation */ function Pose(options) { options = options || {}; // copy the values into this object if they exist this.position = new Vector3(options.position); this.orientation = new Quaternion(options.orientation); } /** * Apply a transform against this pose. * * @param tf the transform */ Pose.prototype.applyTransform = function(tf) { this.position.multiplyQuaternion(tf.rotation); this.position.add(tf.translation); var tmp = tf.rotation.clone(); tmp.multiply(this.orientation); this.orientation = tmp; }; /** * Clone a copy of this pose. * * @returns the cloned pose */ Pose.prototype.clone = function() { return new Pose(this); }; module.exports = Pose; },{"./Quaternion":20,"./Vector3":22}],20:[function(require,module,exports){ /** * @fileoverview * @author David Gossow - dgossow@willowgarage.com */ /** * A Quaternion. * * @constructor * @param options - object with following keys: * * x - the x value * * y - the y value * * z - the z value * * w - the w value */ function Quaternion(options) { options = options || {}; this.x = options.x || 0; this.y = options.y || 0; this.z = options.z || 0; this.w = (typeof options.w === 'number') ? options.w : 1; } /** * Perform a conjugation on this quaternion. */ Quaternion.prototype.conjugate = function() { this.x *= -1; this.y *= -1; this.z *= -1; }; /** * Return the norm of this quaternion. */ Quaternion.prototype.norm = function() { return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w); }; /** * Perform a normalization on this quaternion. */ Quaternion.prototype.normalize = function() { var l = Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w); if (l === 0) { this.x = 0; this.y = 0; this.z = 0; this.w = 1; } else { l = 1 / l; this.x = this.x * l; this.y = this.y * l; this.z = this.z * l; this.w = this.w * l; } }; /** * Convert this quaternion into its inverse. */ Quaternion.prototype.invert = function() { this.conjugate(); this.normalize(); }; /** * Set the values of this quaternion to the product of itself and the given quaternion. * * @param q the quaternion to multiply with */ Quaternion.prototype.multiply = function(q) { var newX = this.x * q.w + this.y * q.z - this.z * q.y + this.w * q.x; var newY = -this.x * q.z + this.y * q.w + this.z * q.x + this.w * q.y; var newZ = this.x * q.y - this.y * q.x + this.z * q.w + this.w * q.z; var newW = -this.x * q.x - this.y * q.y - this.z * q.z + this.w * q.w; this.x = newX; this.y = newY; this.z = newZ; this.w = newW; }; /** * Clone a copy of this quaternion. * * @returns the cloned quaternion */ Quaternion.prototype.clone = function() { return new Quaternion(this); }; module.exports = Quaternion; },{}],21:[function(require,module,exports){ /** * @fileoverview * @author David Gossow - dgossow@willowgarage.com */ var Vector3 = require('./Vector3'); var Quaternion = require('./Quaternion'); /** * A Transform in 3-space. Values are copied into this object. * * @constructor * @param options - object with following keys: * * translation - the Vector3 describing the translation * * rotation - the ROSLIB.Quaternion describing the rotation */ function Transform(options) { options = options || {}; // Copy the values into this object if they exist this.translation = new Vector3(options.translation); this.rotation = new Quaternion(options.rotation); } /** * Clone a copy of this transform. * * @returns the cloned transform */ Transform.prototype.clone = function() { return new Transform(this); }; module.exports = Transform; },{"./Quaternion":20,"./Vector3":22}],22:[function(require,module,exports){ /** * @fileoverview * @author David Gossow - dgossow@willowgarage.com */ /** * A 3D vector. * * @constructor * @param options - object with following keys: * * x - the x value * * y - the y value * * z - the z value */ function Vector3(options) { options = options || {}; this.x = options.x || 0; this.y = options.y || 0; this.z = options.z || 0; } /** * Set the values of this vector to the sum of itself and the given vector. * * @param v the vector to add with */ Vector3.prototype.add = function(v) { this.x += v.x; this.y += v.y; this.z += v.z; }; /** * Set the values of this vector to the difference of itself and the given vector. * * @param v the vector to subtract with */ Vector3.prototype.subtract = function(v) { this.x -= v.x; this.y -= v.y; this.z -= v.z; }; /** * Multiply the given Quaternion with this vector. * * @param q - the quaternion to multiply with */ Vector3.prototype.multiplyQuaternion = function(q) { var ix = q.w * this.x + q.y * this.z - q.z * this.y; var iy = q.w * this.y + q.z * this.x - q.x * this.z; var iz = q.w * this.z + q.x * this.y - q.y * this.x; var iw = -q.x * this.x - q.y * this.y - q.z * this.z; this.x = ix * q.w + iw * -q.x + iy * -q.z - iz * -q.y; this.y = iy * q.w + iw * -q.y + iz * -q.x - ix * -q.z; this.z = iz * q.w + iw * -q.z + ix * -q.y - iy * -q.x; }; /** * Clone a copy of this vector. * * @returns the cloned vector */ Vector3.prototype.clone = function() { return new Vector3(this); }; module.exports = Vector3; },{}],23:[function(require,module,exports){ module.exports = { Pose: require('./Pose'), Quaternion: require('./Quaternion'), Transform: require('./Transform'), Vector3: require('./Vector3') }; },{"./Pose":19,"./Quaternion":20,"./Transform":21,"./Vector3":22}],24:[function(require,module,exports){ /** * Mixin a feature to the core/Ros prototype. * For example, mixin(Ros, ['Topic'], {Topic: }) * will add a topic bound to any Ros instances so a user * can call `var topic = ros.Topic({name: '/foo'});` * * @author Graeme Yeates - github.com/megawac */ module.exports = function(Ros, classes, features) { classes.forEach(function(className) { var Class = features[className]; Ros.prototype[className] = function(options) { options.ros = this; return new Class(options); }; }); }; },{}],25:[function(require,module,exports){ /** * @fileoverview * @author David Gossow - dgossow@willowgarage.com */ var ActionClient = require('../actionlib/ActionClient'); var Goal = require('../actionlib/Goal'); var Service = require('../core/Service.js'); var ServiceRequest = require('../core/ServiceRequest.js'); var Transform = require('../math/Transform'); /** * A TF Client that listens to TFs from tf2_web_republisher. * * @constructor * @param options - object with following keys: * * ros - the ROSLIB.Ros connection handle * * fixedFrame - the fixed frame, like /base_link * * angularThres - the angular threshold for the TF republisher * * transThres - the translation threshold for the TF republisher * * rate - the rate for the TF republisher * * updateDelay - the time (in ms) to wait after a new subscription * to update the TF republisher's list of TFs * * topicTimeout - the timeout parameter for the TF republisher * * serverName (optional) - the name of the tf2_web_republisher server * * repubServiceName (optional) - the name of the republish_tfs service (non groovy compatibility mode only) * default: '/republish_tfs' */ function TFClient(options) { options = options || {}; this.ros = options.ros; this.fixedFrame = options.fixedFrame || '/base_link'; this.angularThres = options.angularThres || 2.0; this.transThres = options.transThres || 0.01; this.rate = options.rate || 10.0; this.updateDelay = options.updateDelay || 50; var seconds = options.topicTimeout || 2.0; var secs = Math.floor(seconds); var nsecs = Math.floor((seconds - secs) * 1000000000); this.topicTimeout = { secs: secs, nsecs: nsecs }; this.serverName = options.serverName || '/tf2_web_republisher'; this.repubServiceName = options.repubServiceName || '/republish_tfs'; this.currentGoal = false; this.currentTopic = false; this.frameInfos = {}; this.republisherUpdateRequested = false; // Create an Action client this.actionClient = this.ros.ActionClient({ serverName : this.serverName, actionName : 'tf2_web_republisher/TFSubscriptionAction', omitStatus : true, omitResult : true }); // Create a Service client this.serviceClient = this.ros.Service({ name: this.repubServiceName, serviceType: 'tf2_web_republisher/RepublishTFs' }); } /** * Process the incoming TF message and send them out using the callback * functions. * * @param tf - the TF message from the server */ TFClient.prototype.processTFArray = function(tf) { var that = this; tf.transforms.forEach(function(transform) { var frameID = transform.child_frame_id; if (frameID[0] === '/') { frameID = frameID.substring(1); } var info = this.frameInfos[frameID]; if (info) { info.transform = new Transform({ translation : transform.transform.translation, rotation : transform.transform.rotation }); info.cbs.forEach(function(cb) { cb(info.transform); }); } }, this); }; /** * Create and send a new goal (or service request) to the tf2_web_republisher * based on the current list of TFs. */ TFClient.prototype.updateGoal = function() { var goalMessage = { source_frames : Object.keys(this.frameInfos), target_frame : this.fixedFrame, angular_thres : this.angularThres, trans_thres : this.transThres, rate : this.rate }; // if we're running in groovy compatibility mode (the default) // then use the action interface to tf2_web_republisher if(this.ros.groovyCompatibility) { if (this.currentGoal) { this.currentGoal.cancel(); } this.currentGoal = new Goal({ actionClient : this.actionClient, goalMessage : goalMessage }); this.currentGoal.on('feedback', this.processTFArray.bind(this)); this.currentGoal.send(); } else { // otherwise, use the service interface // The service interface has the same parameters as the action, // plus the timeout goalMessage.timeout = this.topicTimeout; var request = new ServiceRequest(goalMessage); this.serviceClient.callService(request, this.processResponse.bind(this)); } this.republisherUpdateRequested = false; }; /** * Process the service response and subscribe to the tf republisher * topic * * @param response the service response containing the topic name */ TFClient.prototype.processResponse = function(response) { // if we subscribed to a topic before, unsubscribe so // the republisher stops publishing it if (this.currentTopic) { this.currentTopic.unsubscribe(); } this.currentTopic = this.ros.Topic({ name: response.topic_name, messageType: 'tf2_web_republisher/TFArray' }); this.currentTopic.subscribe(this.processTFArray.bind(this)); }; /** * Subscribe to the given TF frame. * * @param frameID - the TF frame to subscribe to * @param callback - function with params: * * transform - the transform data */ TFClient.prototype.subscribe = function(frameID, callback) { // remove leading slash, if it's there if (frameID[0] === '/') { frameID = frameID.substring(1); } // if there is no callback registered for the given frame, create emtpy callback list if (!this.frameInfos[frameID]) { this.frameInfos[frameID] = { cbs: [] }; if (!this.republisherUpdateRequested) { setTimeout(this.updateGoal.bind(this), this.updateDelay); this.republisherUpdateRequested = true; } } // if we already have a transform, call back immediately else if (this.frameInfos[frameID].transform) { callback(this.frameInfos[frameID].transform); } this.frameInfos[frameID].cbs.push(callback); }; /** * Unsubscribe from the given TF frame. * * @param frameID - the TF frame to unsubscribe from * @param callback - the callback function to remove */ TFClient.prototype.unsubscribe = function(frameID, callback) { // remove leading slash, if it's there if (frameID[0] === '/') { frameID = frameID.substring(1); } var info = this.frameInfos[frameID]; for (var cbs = info && info.cbs || [], idx = cbs.length; idx--;) { if (cbs[idx] === callback) { cbs.splice(idx, 1); } } if (!callback || cbs.length === 0) { delete this.frameInfos[frameID]; } }; /** * Unsubscribe and unadvertise all topics associated with this TFClient. */ TFClient.prototype.dispose = function() { this.actionClient.dispose(); if (this.currentTopic) { this.currentTopic.unsubscribe(); } }; module.exports = TFClient; },{"../actionlib/ActionClient":5,"../actionlib/Goal":7,"../core/Service.js":13,"../core/ServiceRequest.js":14,"../math/Transform":21}],26:[function(require,module,exports){ var Ros = require('../core/Ros'); var mixin = require('../mixin'); var tf = module.exports = { TFClient: require('./TFClient') }; mixin(Ros, ['TFClient'], tf); },{"../core/Ros":12,"../mixin":24,"./TFClient":25}],27:[function(require,module,exports){ /** * @fileOverview * @author Benjamin Pitzer - ben.pitzer@gmail.com * @author Russell Toris - rctoris@wpi.edu */ var Vector3 = require('../math/Vector3'); var UrdfTypes = require('./UrdfTypes'); /** * A Box element in a URDF. * * @constructor * @param options - object with following keys: * * xml - the XML element to parse */ function UrdfBox(options) { this.dimension = null; this.type = UrdfTypes.URDF_BOX; // Parse the xml string var xyz = options.xml.getAttribute('size').split(' '); this.dimension = new Vector3({ x : parseFloat(xyz[0]), y : parseFloat(xyz[1]), z : parseFloat(xyz[2]) }); } module.exports = UrdfBox; },{"../math/Vector3":22,"./UrdfTypes":36}],28:[function(require,module,exports){ /** * @fileOverview * @author Benjamin Pitzer - ben.pitzer@gmail.com * @author Russell Toris - rctoris@wpi.edu */ /** * A Color element in a URDF. * * @constructor * @param options - object with following keys: * * xml - the XML element to parse */ function UrdfColor(options) { // Parse the xml string var rgba = options.xml.getAttribute('rgba').split(' '); this.r = parseFloat(rgba[0]); this.g = parseFloat(rgba[1]); this.b = parseFloat(rgba[2]); this.a = parseFloat(rgba[3]); } module.exports = UrdfColor; },{}],29:[function(require,module,exports){ /** * @fileOverview * @author Benjamin Pitzer - ben.pitzer@gmail.com * @author Russell Toris - rctoris@wpi.edu */ var UrdfTypes = require('./UrdfTypes'); /** * A Cylinder element in a URDF. * * @constructor * @param options - object with following keys: * * xml - the XML element to parse */ function UrdfCylinder(options) { this.type = UrdfTypes.URDF_CYLINDER; this.length = parseFloat(options.xml.getAttribute('length')); this.radius = parseFloat(options.xml.getAttribute('radius')); } module.exports = UrdfCylinder; },{"./UrdfTypes":36}],30:[function(require,module,exports){ /** * @fileOverview * @author David V. Lu!! davidvlu@gmail.com */ /** * A Joint element in a URDF. * * @constructor * @param options - object with following keys: * * xml - the XML element to parse */ function UrdfJoint(options) { this.name = options.xml.getAttribute('name'); this.type = options.xml.getAttribute('type'); var parents = options.xml.getElementsByTagName('parent'); if(parents.length > 0) { this.parent = parents[0].getAttribute('link'); } var children = options.xml.getElementsByTagName('child'); if(children.length > 0) { this.child = children[0].getAttribute('link'); } var limits = options.xml.getElementsByTagName('limit'); if (limits.length > 0) { this.minval = parseFloat( limits[0].getAttribute('lower') ); this.maxval = parseFloat( limits[0].getAttribute('upper') ); } } module.exports = UrdfJoint; },{}],31:[function(require,module,exports){ /** * @fileOverview * @author Benjamin Pitzer - ben.pitzer@gmail.com * @author Russell Toris - rctoris@wpi.edu */ var UrdfVisual = require('./UrdfVisual'); /** * A Link element in a URDF. * * @constructor * @param options - object with following keys: * * xml - the XML element to parse */ function UrdfLink(options) { this.name = options.xml.getAttribute('name'); this.visuals = []; var visuals = options.xml.getElementsByTagName('visual'); for( var i=0; i 0) { this.textureFilename = textures[0].getAttribute('filename'); } // Color var colors = options.xml.getElementsByTagName('color'); if (colors.length > 0) { // Parse the RBGA string this.color = new UrdfColor({ xml : colors[0] }); } } UrdfMaterial.prototype.isLink = function() { return this.color === null && this.textureFilename === null; }; var assign = require('object-assign'); UrdfMaterial.prototype.assign = function(obj) { return assign(this, obj); }; module.exports = UrdfMaterial; },{"./UrdfColor":28,"object-assign":2}],33:[function(require,module,exports){ /** * @fileOverview * @author Benjamin Pitzer - ben.pitzer@gmail.com * @author Russell Toris - rctoris@wpi.edu */ var Vector3 = require('../math/Vector3'); var UrdfTypes = require('./UrdfTypes'); /** * A Mesh element in a URDF. * * @constructor * @param options - object with following keys: * * xml - the XML element to parse */ function UrdfMesh(options) { this.scale = null; this.type = UrdfTypes.URDF_MESH; this.filename = options.xml.getAttribute('filename'); // Check for a scale var scale = options.xml.getAttribute('scale'); if (scale) { // Get the XYZ var xyz = scale.split(' '); this.scale = new Vector3({ x : parseFloat(xyz[0]), y : parseFloat(xyz[1]), z : parseFloat(xyz[2]) }); } } module.exports = UrdfMesh; },{"../math/Vector3":22,"./UrdfTypes":36}],34:[function(require,module,exports){ /** * @fileOverview * @author Benjamin Pitzer - ben.pitzer@gmail.com * @author Russell Toris - rctoris@wpi.edu */ var UrdfMaterial = require('./UrdfMaterial'); var UrdfLink = require('./UrdfLink'); var UrdfJoint = require('./UrdfJoint'); var DOMParser = require('xmldom').DOMParser; // See https://developer.mozilla.org/docs/XPathResult#Constants var XPATH_FIRST_ORDERED_NODE_TYPE = 9; /** * A URDF Model can be used to parse a given URDF into the appropriate elements. * * @constructor * @param options - object with following keys: * * xml - the XML element to parse * * string - the XML element to parse as a string */ function UrdfModel(options) { options = options || {}; var xmlDoc = options.xml; var string = options.string; this.materials = {}; this.links = {}; this.joints = {}; // Check if we are using a string or an XML element if (string) { // Parse the string var parser = new DOMParser(); xmlDoc = parser.parseFromString(string, 'text/xml'); } // Initialize the model with the given XML node. // Get the robot tag var robotXml = xmlDoc.documentElement; // Get the robot name this.name = robotXml.getAttribute('name'); // Parse all the visual elements we need for (var nodes = robotXml.childNodes, i = 0; i < nodes.length; i++) { var node = nodes[i]; if (node.tagName === 'material') { var material = new UrdfMaterial({ xml : node }); // Make sure this is unique if (this.materials[material.name] !== void 0) { if( this.materials[material.name].isLink() ) { this.materials[material.name].assign( material ); } else { console.warn('Material ' + material.name + 'is not unique.'); } } else { this.materials[material.name] = material; } } else if (node.tagName === 'link') { var link = new UrdfLink({ xml : node }); // Make sure this is unique if (this.links[link.name] !== void 0) { console.warn('Link ' + link.name + ' is not unique.'); } else { // Check for a material for( var j=0; j 0) { var geom = geoms[0]; var shape = null; // Check for the shape for (var i = 0; i < geom.childNodes.length; i++) { var node = geom.childNodes[i]; if (node.nodeType === 1) { shape = node; break; } } // Check the type var type = shape.nodeName; if (type === 'sphere') { this.geometry = new UrdfSphere({ xml : shape }); } else if (type === 'box') { this.geometry = new UrdfBox({ xml : shape }); } else if (type === 'cylinder') { this.geometry = new UrdfCylinder({ xml : shape }); } else if (type === 'mesh') { this.geometry = new UrdfMesh({ xml : shape }); } else { console.warn('Unknown geometry type ' + type); } } // Material var materials = xml.getElementsByTagName('material'); if (materials.length > 0) { this.material = new UrdfMaterial({ xml : materials[0] }); } } module.exports = UrdfVisual; },{"../math/Pose":19,"../math/Quaternion":20,"../math/Vector3":22,"./UrdfBox":27,"./UrdfCylinder":29,"./UrdfMaterial":32,"./UrdfMesh":33,"./UrdfSphere":35}],38:[function(require,module,exports){ module.exports = require('object-assign')({ UrdfBox: require('./UrdfBox'), UrdfColor: require('./UrdfColor'), UrdfCylinder: require('./UrdfCylinder'), UrdfLink: require('./UrdfLink'), UrdfMaterial: require('./UrdfMaterial'), UrdfMesh: require('./UrdfMesh'), UrdfModel: require('./UrdfModel'), UrdfSphere: require('./UrdfSphere'), UrdfVisual: require('./UrdfVisual') }, require('./UrdfTypes')); },{"./UrdfBox":27,"./UrdfColor":28,"./UrdfCylinder":29,"./UrdfLink":31,"./UrdfMaterial":32,"./UrdfMesh":33,"./UrdfModel":34,"./UrdfSphere":35,"./UrdfTypes":36,"./UrdfVisual":37,"object-assign":2}],39:[function(require,module,exports){ (function (global){ module.exports = global.WebSocket; }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) },{}],40:[function(require,module,exports){ /* global document */ module.exports = function Canvas() { return document.createElement('canvas'); }; },{}],41:[function(require,module,exports){ (function (global){ /** * @fileOverview * @author Graeme Yeates - github.com/megawac */ 'use strict'; var Canvas = require('canvas'); var Image = Canvas.Image || global.Image; /** * If a message was compressed as a PNG image (a compression hack since * gzipping over WebSockets * is not supported yet), this function places the * "image" in a canvas element then decodes the * "image" as a Base64 string. * * @private * @param data - object containing the PNG data. * @param callback - function with params: * * data - the uncompressed data */ function decompressPng(data, callback) { // Uncompresses the data before sending it through (use image/canvas to do so). var image = new Image(); // When the image loads, extracts the raw data (JSON message). image.onload = function() { // Creates a local canvas to draw on. var canvas = new Canvas(); var context = canvas.getContext('2d'); // Sets width and height. canvas.width = image.width; canvas.height = image.height; // Prevents anti-aliasing and loosing data context.imageSmoothingEnabled = false; context.webkitImageSmoothingEnabled = false; context.mozImageSmoothingEnabled = false; // Puts the data into the image. context.drawImage(image, 0, 0); // Grabs the raw, uncompressed data. var imageData = context.getImageData(0, 0, image.width, image.height).data; // Constructs the JSON. var jsonData = ''; for (var i = 0; i < imageData.length; i += 4) { // RGB jsonData += String.fromCharCode(imageData[i], imageData[i + 1], imageData[i + 2]); } callback(JSON.parse(jsonData)); }; // Sends the image data to load. image.src = 'data:image/png;base64,' + data; } module.exports = decompressPng; }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) },{"canvas":40}],42:[function(require,module,exports){ (function (global){ exports.DOMImplementation = global.DOMImplementation; exports.XMLSerializer = global.XMLSerializer; exports.DOMParser = global.DOMParser; }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) },{}]},{},[4]);