From: Erik Andresen Date: Thu, 2 Apr 2020 05:19:40 +0000 (+0200) Subject: www: speed control X-Git-Url: https://defiant.homedns.org/gitweb/?a=commitdiff_plain;h=38fd577a1efa5248a91105c1a907cb0f364a6dfa;p=ros_wild_thumper.git www: speed control --- diff --git a/www/assets/javascripts/application.js b/www/assets/javascripts/application.js index a7b4785..2fc4176 100644 --- a/www/assets/javascripts/application.js +++ b/www/assets/javascripts/application.js @@ -189,12 +189,12 @@ function init() { Y = e.pageY; } // relative click position - var Xrel = X - this.offsetLeft - $(this).width()/2; - var Yrel = Y - this.offsetTop - $(this).height()/2; + var Xrel = X - this.getBoundingClientRect().left - $(this).width()/2; + var Yrel = Y - this.getBoundingClientRect().top - $(this).height()/2; // scale to -1..+1 var trans = -Yrel / ($(this).height()/2); var rot = -Xrel / ($(this).width()/2); - setSpeed(trans, rot*3); + setSpeed(trans*$("#scale_trans").val(), rot*$("#scale_rot").val()); } }); @@ -238,6 +238,8 @@ function init() { // reload $("img").attr("src", $("img").attr("src")) }); + + $("input[type='number']").spinner(); } Vue.component('input-value', { diff --git a/www/assets/javascripts/roslib.js b/www/assets/javascripts/roslib.js index 2193da8..fccc080 100644 --- a/www/assets/javascripts/roslib.js +++ b/www/assets/javascripts/roslib.js @@ -1,4 +1,4 @@ -(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 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]); + +},{}],2:[function(require,module,exports){ +/* +object-assign +(c) Sindre Sorhus +@license MIT +*/ + +'use strict'; +/* eslint-disable no-unused-vars */ +var getOwnPropertySymbols = Object.getOwnPropertySymbols; +var hasOwnProperty = Object.prototype.hasOwnProperty; +var propIsEnumerable = Object.prototype.propertyIsEnumerable; + +function toObject(val) { + if (val === null || val === undefined) { + throw new TypeError('Object.assign cannot be called with null or undefined'); + } + + return Object(val); +} + +function shouldUseNative() { + try { + if (!Object.assign) { + return false; + } + + // Detect buggy property enumeration order in older V8 versions. + + // https://bugs.chromium.org/p/v8/issues/detail?id=4118 + var test1 = new String('abc'); // eslint-disable-line no-new-wrappers + test1[5] = 'de'; + if (Object.getOwnPropertyNames(test1)[0] === '5') { + return false; + } + + // https://bugs.chromium.org/p/v8/issues/detail?id=3056 + var test2 = {}; + for (var i = 0; i < 10; i++) { + test2['_' + String.fromCharCode(i)] = i; + } + var order2 = Object.getOwnPropertyNames(test2).map(function (n) { + return test2[n]; + }); + if (order2.join('') !== '0123456789') { + return false; + } + + // https://bugs.chromium.org/p/v8/issues/detail?id=3056 + var test3 = {}; + 'abcdefghijklmnopqrst'.split('').forEach(function (letter) { + test3[letter] = letter; + }); + if (Object.keys(Object.assign({}, test3)).join('') !== + 'abcdefghijklmnopqrst') { + return false; + } + + return true; + } catch (err) { + // We don't expect any of the above to throw, but better to be safe. + return false; + } +} + +module.exports = shouldUseNative() ? Object.assign : function (target, source) { + var from; + var to = toObject(target); + var symbols; + + for (var s = 1; s < arguments.length; s++) { + from = Object(arguments[s]); + + for (var key in from) { + if (hasOwnProperty.call(from, key)) { + to[key] = from[key]; + } + } + + if (getOwnPropertySymbols) { + symbols = getOwnPropertySymbols(from); + for (var i = 0; i < symbols.length; i++) { + if (propIsEnumerable.call(from, symbols[i])) { + to[symbols[i]] = from[symbols[i]]; + } + } + } + } + + return to; +}; + +},{}],3:[function(require,module,exports){ +/** + * @fileOverview + * @author Russell Toris - rctoris@wpi.edu + */ + +/** + * If you use roslib in a browser, all the classes will be exported to a global variable called ROSLIB. + * + * If you use nodejs, this is the variable you get when you require('roslib') + */ +var ROSLIB = this.ROSLIB || { + REVISION : '0.20.0' +}; + +var assign = require('object-assign'); + +// Add core components +assign(ROSLIB, require('./core')); + +assign(ROSLIB, require('./actionlib')); + +assign(ROSLIB, require('./math')); + +assign(ROSLIB, require('./tf')); + +assign(ROSLIB, require('./urdf')); + +module.exports = ROSLIB; + +},{"./actionlib":9,"./core":18,"./math":23,"./tf":26,"./urdf":38,"object-assign":2}],4:[function(require,module,exports){ +(function (global){ +global.ROSLIB = require('./RosLib'); +}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{"./RosLib":3}],5:[function(require,module,exports){ +/** + * @fileOverview + * @author Russell Toris - rctoris@wpi.edu + */ + +var Topic = require('../core/Topic'); +var Message = require('../core/Message'); +var EventEmitter2 = require('eventemitter2').EventEmitter2; + +/** + * An actionlib action client. + * + * Emits the following events: + * * 'timeout' - if a timeout occurred while sending a goal + * * 'status' - the status messages received from the action server + * * 'feedback' - the feedback messages received from the action server + * * 'result' - the result returned from the action server + * + * @constructor + * @param options - object with following keys: + * * ros - the ROSLIB.Ros connection handle + * * serverName - the action server name, like /fibonacci + * * actionName - the action message name, like 'actionlib_tutorials/FibonacciAction' + * * timeout - the timeout length when connecting to the action server + */ +function ActionClient(options) { + var that = this; + options = options || {}; + this.ros = options.ros; + this.serverName = options.serverName; + this.actionName = options.actionName; + this.timeout = options.timeout; + this.omitFeedback = options.omitFeedback; + this.omitStatus = options.omitStatus; + this.omitResult = options.omitResult; + this.goals = {}; + + // flag to check if a status has been received + var receivedStatus = false; + + // create the topics associated with actionlib + this.feedbackListener = new Topic({ + ros : this.ros, + name : this.serverName + '/feedback', + messageType : this.actionName + 'Feedback' + }); + + this.statusListener = new Topic({ + ros : this.ros, + name : this.serverName + '/status', + messageType : 'actionlib_msgs/GoalStatusArray' + }); + + this.resultListener = new Topic({ + ros : this.ros, + name : this.serverName + '/result', + messageType : this.actionName + 'Result' + }); + + this.goalTopic = new Topic({ + ros : this.ros, + name : this.serverName + '/goal', + messageType : this.actionName + 'Goal' + }); + + this.cancelTopic = new Topic({ + ros : this.ros, + name : this.serverName + '/cancel', + messageType : 'actionlib_msgs/GoalID' + }); + + // advertise the goal and cancel topics + this.goalTopic.advertise(); + this.cancelTopic.advertise(); + + // subscribe to the status topic + if (!this.omitStatus) { + this.statusListener.subscribe(function(statusMessage) { + receivedStatus = true; + statusMessage.status_list.forEach(function(status) { + var goal = that.goals[status.goal_id.id]; + if (goal) { + goal.emit('status', status); + } + }); + }); + } + + // subscribe the the feedback topic + if (!this.omitFeedback) { + this.feedbackListener.subscribe(function(feedbackMessage) { + var goal = that.goals[feedbackMessage.status.goal_id.id]; + if (goal) { + goal.emit('status', feedbackMessage.status); + goal.emit('feedback', feedbackMessage.feedback); + } + }); + } + + // subscribe to the result topic + if (!this.omitResult) { + this.resultListener.subscribe(function(resultMessage) { + var goal = that.goals[resultMessage.status.goal_id.id]; + + if (goal) { + goal.emit('status', resultMessage.status); + goal.emit('result', resultMessage.result); + } + }); + } + + // If timeout specified, emit a 'timeout' event if the action server does not respond + if (this.timeout) { + setTimeout(function() { + if (!receivedStatus) { + that.emit('timeout'); + } + }, this.timeout); + } +} + +ActionClient.prototype.__proto__ = EventEmitter2.prototype; + +/** + * Cancel all goals associated with this ActionClient. + */ +ActionClient.prototype.cancel = function() { + var cancelMessage = new Message(); + this.cancelTopic.publish(cancelMessage); +}; + +/** + * Unsubscribe and unadvertise all topics associated with this ActionClient. + */ +ActionClient.prototype.dispose = function() { + this.goalTopic.unadvertise(); + this.cancelTopic.unadvertise(); + if (!this.omitStatus) {this.statusListener.unsubscribe();} + if (!this.omitFeedback) {this.feedbackListener.unsubscribe();} + if (!this.omitResult) {this.resultListener.unsubscribe();} +}; + +module.exports = ActionClient; + +},{"../core/Message":10,"../core/Topic":17,"eventemitter2":1}],6:[function(require,module,exports){ +/** + * @fileOverview + * @author Justin Young - justin@oodar.com.au + * @author Russell Toris - rctoris@wpi.edu + */ + +var Topic = require('../core/Topic'); +var Message = require('../core/Message'); +var EventEmitter2 = require('eventemitter2').EventEmitter2; + +/** + * An actionlib action listener + * + * Emits the following events: + * * 'status' - the status messages received from the action server + * * 'feedback' - the feedback messages received from the action server + * * 'result' - the result returned from the action server + * + * @constructor + * @param options - object with following keys: + * * ros - the ROSLIB.Ros connection handle + * * serverName - the action server name, like /fibonacci + * * actionName - the action message name, like 'actionlib_tutorials/FibonacciAction' + */ +function ActionListener(options) { + var that = this; + options = options || {}; + this.ros = options.ros; + this.serverName = options.serverName; + this.actionName = options.actionName; + this.timeout = options.timeout; + this.omitFeedback = options.omitFeedback; + this.omitStatus = options.omitStatus; + this.omitResult = options.omitResult; + + + // create the topics associated with actionlib + var goalListener = new Topic({ + ros : this.ros, + name : this.serverName + '/goal', + messageType : this.actionName + 'Goal' + }); + + var feedbackListener = new Topic({ + ros : this.ros, + name : this.serverName + '/feedback', + messageType : this.actionName + 'Feedback' + }); + + var statusListener = new Topic({ + ros : this.ros, + name : this.serverName + '/status', + messageType : 'actionlib_msgs/GoalStatusArray' + }); + + var resultListener = new Topic({ + ros : this.ros, + name : this.serverName + '/result', + messageType : this.actionName + 'Result' + }); + + goalListener.subscribe(function(goalMessage) { + that.emit('goal', goalMessage); + }); + + statusListener.subscribe(function(statusMessage) { + statusMessage.status_list.forEach(function(status) { + that.emit('status', status); + }); + }); + + feedbackListener.subscribe(function(feedbackMessage) { + that.emit('status', feedbackMessage.status); + that.emit('feedback', feedbackMessage.feedback); + }); + + // subscribe to the result topic + resultListener.subscribe(function(resultMessage) { + that.emit('status', resultMessage.status); + that.emit('result', resultMessage.result); + }); + +} + +ActionListener.prototype.__proto__ = EventEmitter2.prototype; + +module.exports = ActionListener; + +},{"../core/Message":10,"../core/Topic":17,"eventemitter2":1}],7:[function(require,module,exports){ +/** + * @fileOverview + * @author Russell Toris - rctoris@wpi.edu + */ + +var Message = require('../core/Message'); +var EventEmitter2 = require('eventemitter2').EventEmitter2; + +/** + * An actionlib goal goal is associated with an action server. + * + * Emits the following events: + * * 'timeout' - if a timeout occurred while sending a goal + * + * @constructor + * @param object with following keys: + * * actionClient - the ROSLIB.ActionClient to use with this goal + * * goalMessage - The JSON object containing the goal for the action server + */ +function Goal(options) { + var that = this; + this.actionClient = options.actionClient; + this.goalMessage = options.goalMessage; + this.isFinished = false; + + // Used to create random IDs + var date = new Date(); + + // Create a random ID + this.goalID = 'goal_' + Math.random() + '_' + date.getTime(); + // Fill in the goal message + this.goalMessage = new Message({ + goal_id : { + stamp : { + secs : 0, + nsecs : 0 + }, + id : this.goalID + }, + goal : this.goalMessage + }); + + this.on('status', function(status) { + that.status = status; + }); + + this.on('result', function(result) { + that.isFinished = true; + that.result = result; + }); + + this.on('feedback', function(feedback) { + that.feedback = feedback; + }); + + // Add the goal + this.actionClient.goals[this.goalID] = this; +} + +Goal.prototype.__proto__ = EventEmitter2.prototype; + +/** + * Send the goal to the action server. + * + * @param timeout (optional) - a timeout length for the goal's result + */ +Goal.prototype.send = function(timeout) { + var that = this; + that.actionClient.goalTopic.publish(that.goalMessage); + if (timeout) { + setTimeout(function() { + if (!that.isFinished) { + that.emit('timeout'); + } + }, timeout); + } +}; + +/** + * Cancel the current goal. + */ +Goal.prototype.cancel = function() { + var cancelMessage = new Message({ + id : this.goalID + }); + this.actionClient.cancelTopic.publish(cancelMessage); +}; + +module.exports = Goal; +},{"../core/Message":10,"eventemitter2":1}],8:[function(require,module,exports){ +/** + * @fileOverview + * @author Laura Lindzey - lindzey@gmail.com + */ + +var Topic = require('../core/Topic'); +var Message = require('../core/Message'); +var EventEmitter2 = require('eventemitter2').EventEmitter2; + +/** + * An actionlib action server client. + * + * Emits the following events: + * * 'goal' - goal sent by action client + * * 'cancel' - action client has canceled the request + * + * @constructor + * @param options - object with following keys: + * * ros - the ROSLIB.Ros connection handle + * * serverName - the action server name, like /fibonacci + * * actionName - the action message name, like 'actionlib_tutorials/FibonacciAction' + */ + +function SimpleActionServer(options) { + var that = this; + options = options || {}; + this.ros = options.ros; + this.serverName = options.serverName; + this.actionName = options.actionName; + + // create and advertise publishers + this.feedbackPublisher = new Topic({ + ros : this.ros, + name : this.serverName + '/feedback', + messageType : this.actionName + 'Feedback' + }); + this.feedbackPublisher.advertise(); + + var statusPublisher = new Topic({ + ros : this.ros, + name : this.serverName + '/status', + messageType : 'actionlib_msgs/GoalStatusArray' + }); + statusPublisher.advertise(); + + this.resultPublisher = new Topic({ + ros : this.ros, + name : this.serverName + '/result', + messageType : this.actionName + 'Result' + }); + this.resultPublisher.advertise(); + + // create and subscribe to listeners + var goalListener = new Topic({ + ros : this.ros, + name : this.serverName + '/goal', + messageType : this.actionName + 'Goal' + }); + + var cancelListener = new Topic({ + ros : this.ros, + name : this.serverName + '/cancel', + messageType : 'actionlib_msgs/GoalID' + }); + + // Track the goals and their status in order to publish status... + this.statusMessage = new Message({ + header : { + stamp : {secs : 0, nsecs : 100}, + frame_id : '' + }, + status_list : [] + }); + + // needed for handling preemption prompted by a new goal being received + this.currentGoal = null; // currently tracked goal + this.nextGoal = null; // the one that'll be preempting + + goalListener.subscribe(function(goalMessage) { + + if(that.currentGoal) { + that.nextGoal = goalMessage; + // needs to happen AFTER rest is set up + that.emit('cancel'); + } else { + that.statusMessage.status_list = [{goal_id : goalMessage.goal_id, status : 1}]; + that.currentGoal = goalMessage; + that.emit('goal', goalMessage.goal); + } + }); + + // helper function for determing ordering of timestamps + // returns t1 < t2 + var isEarlier = function(t1, t2) { + if(t1.secs > 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(); + image.crossOrigin = "Anonymous"; + // 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]); diff --git a/www/assets/stylesheets/images/ui-icons_444444_256x240.png b/www/assets/stylesheets/images/ui-icons_444444_256x240.png new file mode 100644 index 0000000..c2daae1 Binary files /dev/null and b/www/assets/stylesheets/images/ui-icons_444444_256x240.png differ diff --git a/www/assets/stylesheets/images/ui-icons_555555_256x240.png b/www/assets/stylesheets/images/ui-icons_555555_256x240.png new file mode 100644 index 0000000..4784928 Binary files /dev/null and b/www/assets/stylesheets/images/ui-icons_555555_256x240.png differ diff --git a/www/assets/stylesheets/images/ui-icons_777620_256x240.png b/www/assets/stylesheets/images/ui-icons_777620_256x240.png new file mode 100644 index 0000000..d2f58d2 Binary files /dev/null and b/www/assets/stylesheets/images/ui-icons_777620_256x240.png differ diff --git a/www/assets/stylesheets/images/ui-icons_777777_256x240.png b/www/assets/stylesheets/images/ui-icons_777777_256x240.png new file mode 100644 index 0000000..1d53258 Binary files /dev/null and b/www/assets/stylesheets/images/ui-icons_777777_256x240.png differ diff --git a/www/assets/stylesheets/images/ui-icons_cc0000_256x240.png b/www/assets/stylesheets/images/ui-icons_cc0000_256x240.png new file mode 100644 index 0000000..2825f20 Binary files /dev/null and b/www/assets/stylesheets/images/ui-icons_cc0000_256x240.png differ diff --git a/www/assets/stylesheets/images/ui-icons_ffffff_256x240.png b/www/assets/stylesheets/images/ui-icons_ffffff_256x240.png new file mode 100644 index 0000000..136a4f9 Binary files /dev/null and b/www/assets/stylesheets/images/ui-icons_ffffff_256x240.png differ diff --git a/www/index.html b/www/index.html index 7a86765..253c35d 100644 --- a/www/index.html +++ b/www/index.html @@ -112,7 +112,15 @@
-
+
+
+
+
+
+ + +
+