X-Git-Url: https://defiant.homedns.org/gitweb/?p=ros_wild_thumper.git;a=blobdiff_plain;f=www%2Fassets%2Fjavascripts%2Froslib.js;fp=www%2Fassets%2Fjavascripts%2Froslib.js;h=2193da849c1293025b6459ece8b7ac3b94c0c09b;hp=0000000000000000000000000000000000000000;hb=01848a9a84b296519ca4b4f41906159fd4158213;hpb=e1f7a5148b9999b57a91c1ddb244397bf841de7e diff --git a/www/assets/javascripts/roslib.js b/www/assets/javascripts/roslib.js new file mode 100644 index 0000000..2193da8 --- /dev/null +++ b/www/assets/javascripts/roslib.js @@ -0,0 +1,3693 @@ +(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]);