diff --git a/roslaunch_editor/CMakeLists.txt b/roslaunch_editor/CMakeLists.txt
new file mode 100644
index 00000000..ea70c560
--- /dev/null
+++ b/roslaunch_editor/CMakeLists.txt
@@ -0,0 +1,82 @@
+cmake_minimum_required(VERSION 2.8.3)
+project(roslaunch_editor)
+
+find_package(catkin REQUIRED COMPONENTS message_generation)
+
+add_message_files(
+ FILES
+ LaunchFile.msg
+)
+
+add_service_files(
+ FILES
+ ReadLaunchFiles.srv
+ WriteLaunchFiles.srv
+)
+
+generate_messages(
+ DEPENDENCIES
+ # std_msgs # Or other packages containing msgs
+)
+
+catkin_package(
+# INCLUDE_DIRS include
+# LIBRARIES roslaunch_editor
+# CATKIN_DEPENDS other_catkin_pkg
+# DEPENDS system_lib
+)
+
+#############
+## Install ##
+#############
+
+# all install targets should use catkin DESTINATION variables
+# See http://ros.org/doc/api/catkin/html/adv_user_guide/variables.html
+
+## Mark executable scripts (Python etc.) for installation
+## in contrast to setup.py, you can choose the destination
+# install(PROGRAMS
+# scripts/my_python_script
+# DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
+# )
+
+## Mark executables for installation
+## See http://docs.ros.org/melodic/api/catkin/html/howto/format1/building_executables.html
+# install(TARGETS ${PROJECT_NAME}_node
+# RUNTIME DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
+# )
+
+## Mark libraries for installation
+## See http://docs.ros.org/melodic/api/catkin/html/howto/format1/building_libraries.html
+# install(TARGETS ${PROJECT_NAME}
+# ARCHIVE DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION}
+# LIBRARY DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION}
+# RUNTIME DESTINATION ${CATKIN_GLOBAL_BIN_DESTINATION}
+# )
+
+## Mark cpp header files for installation
+# install(DIRECTORY include/${PROJECT_NAME}/
+# DESTINATION ${CATKIN_PACKAGE_INCLUDE_DESTINATION}
+# FILES_MATCHING PATTERN "*.h"
+# PATTERN ".svn" EXCLUDE
+# )
+
+## Mark other files for installation (e.g. launch and bag files, etc.)
+# install(FILES
+# # myfile1
+# # myfile2
+# DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION}
+# )
+
+#############
+## Testing ##
+#############
+
+## Add gtest based cpp test target and link libraries
+# catkin_add_gtest(${PROJECT_NAME}-test test/test_roslaunch_editor.cpp)
+# if(TARGET ${PROJECT_NAME}-test)
+# target_link_libraries(${PROJECT_NAME}-test ${PROJECT_NAME})
+# endif()
+
+## Add folders to be run by python nosetests
+# catkin_add_nosetests(test)
diff --git a/roslaunch_editor/README.md b/roslaunch_editor/README.md
new file mode 100644
index 00000000..9a9e49ca
--- /dev/null
+++ b/roslaunch_editor/README.md
@@ -0,0 +1,28 @@
+# roslaunch_editor
+
+Web-based ROS launch-files editor, created for making configuration of your robot more user-friendly for novices.
+
+
+
+## Quick launch
+
+```bash
+roslaunch roslaunch_editor example.launch
+```
+
+Then, open `http://localhost:8085/roslaunch_editor/` and edit the test launch file.
+
+## Modes
+
+`roslaunch_editor` works in two modes: standalone mode (where running `editor` node is required), and Clover mode (where `clover`'s `shell` node, `roswww_static` and `rosbridge_suite` are utilized). The editor will read and write launch-files and restart the nodes (if configured) using one of these nodes.
+
+The mode is determined automatically, based on advertised ROS-services.
+
+## Parameters
+
+* `items` (`string` or `list`) – launch files to edit, format: `package_name/launch_file_name.launch`.
+* `hide_uncommented` (`boolean`, default: `false`) – don't show arguments without comments.
+* `apply_command` (`string`, default: `''`) – shell command to execute after writing launch-files (e. g. to restart the systemd service).
+* `backup` (`boolean`, default: `false`) – backup overwritten launch-files (backup is written to `file_name.launch.bak`).
+
+Some parameters (`items`, `hide_uncommented`) can be overwritten over GET-parameters, e. g. `?items=package/foo.launch,package/bar.launch&hide_uncommented=1`.
diff --git a/roslaunch_editor/launch/example.launch b/roslaunch_editor/launch/example.launch
new file mode 100644
index 00000000..ff485bfd
--- /dev/null
+++ b/roslaunch_editor/launch/example.launch
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/roslaunch_editor/msg/LaunchFile.msg b/roslaunch_editor/msg/LaunchFile.msg
new file mode 100644
index 00000000..91c8ccf7
--- /dev/null
+++ b/roslaunch_editor/msg/LaunchFile.msg
@@ -0,0 +1,3 @@
+string package
+string name
+string content
diff --git a/roslaunch_editor/package.xml b/roslaunch_editor/package.xml
new file mode 100644
index 00000000..8baa2a8d
--- /dev/null
+++ b/roslaunch_editor/package.xml
@@ -0,0 +1,45 @@
+
+
+ roslaunch_editor
+ 0.0.0
+ Web based roslaunch files editor
+ Oleg Kalachev
+ MIT
+
+
+
+ https://github.com/CopterExpress/clover
+
+
+
+ Oleg Kalachev
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ catkin
+ message_generation
+
+
+
+
+
+
+
diff --git a/roslaunch_editor/roslaunch_editor.jpg b/roslaunch_editor/roslaunch_editor.jpg
new file mode 100644
index 00000000..dfae84ca
Binary files /dev/null and b/roslaunch_editor/roslaunch_editor.jpg differ
diff --git a/roslaunch_editor/src/editor b/roslaunch_editor/src/editor
new file mode 100755
index 00000000..15d90d76
--- /dev/null
+++ b/roslaunch_editor/src/editor
@@ -0,0 +1,73 @@
+#!/usr/bin/env python
+
+import rospy
+import os
+import shutil
+import rospkg
+
+from std_srvs.srv import Trigger
+from roslaunch_editor.srv import ReadLaunchFiles, ReadLaunchFilesResponse, WriteLaunchFiles
+from roslaunch_editor.msg import LaunchFile
+
+rospy.init_node('roslaunch_editor')
+rospack = rospkg.RosPack()
+backup = rospy.get_param('~backup', True)
+apply_command = rospy.get_param('~apply_command', '')
+
+
+def get_launch_file_path(package, name):
+ path = rospack.get_path(package)
+ for root, dirnames, filenames in os.walk(path):
+ if name in filenames:
+ return os.path.join(root, name)
+ raise Exception('Launch file %s/%s not found' % (package, name))
+
+
+def read(req):
+ try:
+ res = ReadLaunchFilesResponse()
+ for launch_file in req.files:
+ path = get_launch_file_path(launch_file.package, launch_file.name)
+ rospy.loginfo('read file %s', path)
+ launch_file.content = open(path, 'r').read()
+ res.files.append(launch_file)
+ res.success = True
+ return res
+ except Exception as e:
+ rospy.logerr(str(e))
+ return {'success': False, 'message': str(e)}
+
+
+def write(req):
+ try:
+ # write files
+ for launch_file in req.files:
+ if not launch_file.name.endswith('.launch'):
+ raise Exception('Launch file name should end with .launch')
+ path = get_launch_file_path(launch_file.package, launch_file.name)
+ rospy.loginfo('write file %s', path)
+ if backup:
+ shutil.copyfile(path, path + '.bak.launch')
+ with open(path, 'w') as f:
+ f.write(launch_file.content)
+
+ # restart the system
+ if not apply_command:
+ return {'success': True}
+
+ rospy.loginfo('apply: %s', apply_command)
+ res = os.system(apply_command)
+ if res == 0:
+ return {'success': True}
+ else:
+ return {'success': False, 'message': 'Error invoking %s' % apply_command}
+ except Exception as e:
+ rospy.logerr(str(e))
+ return {'success': False, 'message': str(e)}
+
+
+rospy.Service('~read', ReadLaunchFiles, read)
+rospy.Service('~write', WriteLaunchFiles, write)
+
+
+rospy.spin()
diff --git a/roslaunch_editor/srv/ReadLaunchFiles.srv b/roslaunch_editor/srv/ReadLaunchFiles.srv
new file mode 100644
index 00000000..d9f9c763
--- /dev/null
+++ b/roslaunch_editor/srv/ReadLaunchFiles.srv
@@ -0,0 +1,7 @@
+# Read launch-files
+
+LaunchFile[] files # content field is ignored
+---
+bool success
+string message
+LaunchFile[] files
diff --git a/roslaunch_editor/srv/WriteLaunchFiles.srv b/roslaunch_editor/srv/WriteLaunchFiles.srv
new file mode 100644
index 00000000..36d18cee
--- /dev/null
+++ b/roslaunch_editor/srv/WriteLaunchFiles.srv
@@ -0,0 +1,6 @@
+# Write launch-files and apply (restart the system)
+
+LaunchFile[] files
+---
+bool success
+string message
diff --git a/roslaunch_editor/test/test.launch b/roslaunch_editor/test/test.launch
new file mode 100644
index 00000000..3866657e
--- /dev/null
+++ b/roslaunch_editor/test/test.launch
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/roslaunch_editor/www/index.html b/roslaunch_editor/www/index.html
new file mode 100644
index 00000000..ebb6b478
--- /dev/null
+++ b/roslaunch_editor/www/index.html
@@ -0,0 +1,140 @@
+
+
+
+
+
+
+ roslaunch editor
+
+
+
+
+
+
+
+
+
+
+
diff --git a/roslaunch_editor/www/loader.css b/roslaunch_editor/www/loader.css
new file mode 100644
index 00000000..0ef98833
--- /dev/null
+++ b/roslaunch_editor/www/loader.css
@@ -0,0 +1,67 @@
+.lds-grid {
+ display: inline-block;
+ position: relative;
+ width: 80px;
+ height: 80px;
+}
+.lds-grid div {
+ position: absolute;
+ width: 15px;
+ height: 15px;
+ border-radius: 50%;
+ background: #fff;
+ animation: lds-grid 1.2s linear infinite;
+}
+.lds-grid div:nth-child(1) {
+ top: 8px;
+ left: 8px;
+ animation-delay: 0s;
+}
+.lds-grid div:nth-child(2) {
+ top: 8px;
+ left: 32px;
+ animation-delay: -0.4s;
+}
+.lds-grid div:nth-child(3) {
+ top: 8px;
+ left: 56px;
+ animation-delay: -0.8s;
+}
+.lds-grid div:nth-child(4) {
+ top: 32px;
+ left: 8px;
+ animation-delay: -0.4s;
+}
+.lds-grid div:nth-child(5) {
+ top: 32px;
+ left: 32px;
+ animation-delay: -0.8s;
+}
+.lds-grid div:nth-child(6) {
+ top: 32px;
+ left: 56px;
+ animation-delay: -1.2s;
+}
+.lds-grid div:nth-child(7) {
+ top: 56px;
+ left: 8px;
+ animation-delay: -0.8s;
+}
+.lds-grid div:nth-child(8) {
+ top: 56px;
+ left: 32px;
+ animation-delay: -1.2s;
+}
+.lds-grid div:nth-child(9) {
+ top: 56px;
+ left: 56px;
+ animation-delay: -1.6s;
+}
+@keyframes lds-grid {
+ 0%, 100% {
+ opacity: 1;
+ }
+ 50% {
+ opacity: 0.5;
+ }
+}
diff --git a/roslaunch_editor/www/main.js b/roslaunch_editor/www/main.js
new file mode 100644
index 00000000..74f0ae1b
--- /dev/null
+++ b/roslaunch_editor/www/main.js
@@ -0,0 +1,287 @@
+var editorElem = document.querySelector('form.editor');
+
+// escape html
+function esc(unsafe) {
+ return unsafe
+ .replace(/&/g, "&")
+ .replace(//g, ">")
+ .replace(/"/g, """)
+ .replace(/'/g, "'");
+ }
+
+function getType(value) {
+ if (value == 'true' || value == 'false') {
+ return 'bool';
+ } else if (Number.isInteger(Number(value))) {
+ return 'int';
+ } else if (!isNaN(Number(value))) {
+ return 'float'
+ } else {
+ return 'string';
+ }
+}
+
+var items = {};
+
+function generateForm(item, content) {
+ var parser = new DOMParser();
+ var doc = items[item].doc = parser.parseFromString(content, 'text/xml');
+ var html = '';
+
+ // go though all arg tags
+ var args = doc.querySelectorAll('launch > arg');
+ for (var arg of args) {
+ var name = arg.getAttribute('name');
+ var comment = '';
+ var value = arg.getAttribute('default');
+ if (value === null) value = '';
+ var type = getType(value);
+
+ // get comment from previous sibling comment with no more than one line break in-between -->
+ var prev = arg.previousSibling;
+ if (prev && prev.nodeType == Node.TEXT_NODE && prev.textContent.split('\n').length <= 2) {
+ prev = arg.previousSibling.previousSibling;
+ if (prev && prev.nodeType == Node.COMMENT_NODE && prev != next /* don't use one comment twice */) {
+ prevArgPrevComment = prev;
+ comment = prev.textContent;
+ }
+ }
+
+ // get comment from next sibling comment without line breaks in-between (more priority)
+ var next = arg.nextSibling;
+ if (next.nodeType == Node.TEXT_NODE && next.textContent.indexOf('\n') == -1) {
+ next = next.nextSibling;
+ }
+ if (next.nodeType == Node.COMMENT_NODE) {
+ comment = next.textContent;
+ }
+
+ // get options
+ var options = comment.match(/^(.*?): ((.*), (.*))$/);
+ if (options) {
+ comment = options[1];
+ options = options[2].split(',');
+ options = options.map(function(option) { return option.trim(); });
+ }
+
+ if (comment.indexOf('noeditor') != -1) continue;
+
+ if (p.hide_uncommented && !comment) continue;
+
+ var path = `launch > arg[name=${name}]`;
+ html += ``
+ }
+
+ return html;
+}
+
+function updateDocs() {
+ editorElem.querySelectorAll('input, select').forEach(function(elem) {
+ var type = elem.getAttribute('type');
+ if (type == 'checkbox') {
+ var value = String(elem.checked);
+ } else {
+ var value = elem.value;
+ }
+ var path = elem.getAttribute('data-path');
+ var item = elem.getAttribute('data-item');
+ var element = items[item].doc.querySelector(path);
+ if (element.getAttribute('default') === null && value == '') {
+ // don't add empty default if it's not set
+ return;
+ }
+ element.setAttribute('default', value);
+ });
+}
+
+editorElem.addEventListener('change', updateDocs);
+
+function addLaunchFile(name, content) {
+ html = `'
+ editorElem.innerHTML += html;
+}
+
+function parseItem(str) {
+ var parsed = str.match(/(.*?)\/(.*)/);
+ var package = parsed[1];
+ var name = parsed[2];
+ return items[str] = { package, name };
+}
+
+var clover;
+
+function determineMode() {
+ return new Promise(function(resolve, reject) {
+ function errcb(err) {
+ alert(err);
+ reject();
+ }
+ // check roslaunch_editor's read service
+ ros.getServiceType('/roslaunch_editor/read', function(t) {
+ if (t == 'roslaunch_editor/ReadLaunchFiles') {
+ clover = false;
+ resolve();
+ return;
+ }
+ // check clover's exec service
+ ros.getServiceType('/exec', function(t) {
+ if (t == 'clover/Execute') {
+ clover = true;
+ resolve();
+ return;
+ }
+ alert('Neither /roslaunch_editor/read nor /exec service not found');
+ reject();
+ }, errcb);
+ }, errcb);
+ });
+}
+
+function errCallback(err) {
+ alert('Error calling service: ' + err);
+}
+
+function loadItems() {
+ editorElem.innerHTML = '';
+
+ if (typeof p.items == 'string') {
+ p.items = p.items.split(',');
+ }
+
+ if (clover) {
+ const boundary = '===BOUNDARY===';
+ var cmd = 'bash -ic "' + p.items.map(function(item) {
+ parseItem(item);
+ return `roscat ${items[item].package} ${items[item].name}`;
+ }).join(` && echo -n ${boundary} && `) + '"';
+ exec.callService(new ROSLIB.ServiceRequest({ cmd }), function(res) {
+ if (res.code != 0) {
+ alert('Error reading launch-files');
+ return;
+ }
+ res.output.split(boundary).forEach(function(content, i) {
+ addLaunchFile(p.items[i], content);
+ });
+ document.body.classList.add('loaded');
+ }, errCallback);
+
+ } else {
+ var req = new ROSLIB.ServiceRequest();
+ req.files = p.items.map(function(item) {
+ parseItem(item);
+ return { 'package': items[item].package, 'name': items[item].name }
+ });
+ readLaunchFiles.callService(req, function(res) {
+ res.files.forEach(function(item, i) {
+ addLaunchFile(p.items[i], item.content);
+ });
+ document.body.classList.add('loaded');
+ }, errCallback);
+ }
+}
+
+// load params
+Promise.all([
+ determineMode(),
+ readParam('apply_command', false, ''),
+ readParam('items', true),
+ readParam('hide_uncommented', true, false),
+ readParam('standalone', false, false),
+]).then(() => loadItems());
+
+function shellEscape(a) {
+ // https://github.com/xxorax/node-shell-escape
+ var ret = [];
+
+ a.forEach(function (s) {
+ if (!['&&', '||', '|', '>', '<', '>>', '<<'].includes(s) && /[^A-Za-z0-9_\/:=-]/.test(s)) {
+ s = "'" + s.replace(/'/g, "'\\''") + "'";
+ s = s.replace(/^(?:'')+/g, '') // unduplicate single-quote at the beginning
+ .replace(/\\'''/g, "\\'"); // remove non-escaped single-quote if there are enclosed between 2 escaped
+ }
+ ret.push(s);
+ });
+
+ return ret.join(' ');
+}
+
+var applying = false;
+
+// TODO: reread launch file on connected
+function apply() {
+ applying = true;
+ document.body.classList.remove('loaded');
+
+ updateDocs();
+
+ if (clover) {
+ var script = '';
+ for (item in items) {
+ if (script) script += ' && ';
+ var content = items[item].doc.documentElement.outerHTML;
+ script += `_roscmd ${items[item].package} ${items[item].name} && echo ${shellEscape([content])} > $arg`;
+ }
+ if (p.apply_command) {
+ script + ' && ' + p.apply_command;
+ }
+
+ var cmd = shellEscape(['bash', '-ic', script]);
+
+ exec.callService(new ROSLIB.ServiceRequest({ cmd: cmd }), function(res) {
+ if (res.code != 0) {
+ alert('Error');
+ return;
+ }
+ document.body.classList.add('loaded');
+ });
+ } else {
+
+ var req = new ROSLIB.ServiceRequest();
+ req.files = Object.keys(items).map(function(key) {
+ var item = items[key];
+ return { package: item.package, name: item.name, content: item.doc.documentElement.outerHTML }
+ });
+
+ writeLaunchFiles.callService(req, function(res) {
+ if (!res.success) {
+ alert('Error writing files ' + res.message);
+ }
+ document.body.classList.add('loaded');
+ applying = false;
+ }, errCallback);
+ }
+}
+
+ros.on('connection', function() {
+ if (applying) {
+ // connection after applying tells that system restarted
+ document.body.classList.add('loaded');
+ applying = false;
+ }
+})
diff --git a/roslaunch_editor/www/ros.js b/roslaunch_editor/www/ros.js
new file mode 100644
index 00000000..520f1054
--- /dev/null
+++ b/roslaunch_editor/www/ros.js
@@ -0,0 +1,46 @@
+var url = 'ws://' + location.hostname + ':9090';
+var ros = new ROSLIB.Ros({ url });
+
+ros.on('connection', function () {
+ document.body.classList.add('connected');
+});
+
+ros.on('close', function () {
+ document.body.classList.remove('connected');
+ setTimeout(function() {
+ try {
+ ros.connect(url);
+ } catch (e) {}
+ }, 2000);
+});
+
+var exec = new ROSLIB.Service({ ros: ros, name : '/exec', serviceType : 'clover/Execute' });
+var readLaunchFiles = new ROSLIB.Service({ ros: ros, name: '/roslaunch_editor/read', serviceType: 'roslaunch_editor/ReadLaunchFiles' });
+var writeLaunchFiles = new ROSLIB.Service({ ros: ros, name: '/roslaunch_editor/write', serviceType: 'roslaunch_editor/WriteLaunchFiles' });
+
+var p = {}; // parameters storage
+
+function readParam(name, fromUrl, _default) {
+ return new Promise(function(resolve, reject) {
+ // read from url
+ if (fromUrl && ((p[name] = new URL(window.location.href).searchParams.get(name)) !== null)) {
+ resolve();
+ return;
+ }
+ // read from ROS params
+ new ROSLIB.Param({ ros: ros, name: '/roslaunch_editor/' + name }).get(function(val) {
+ if (val === null) {
+ if (_default === undefined) {
+ alert('Cannot read required parameter ' + name);
+ reject();
+ } else {
+ p[name] = _default;
+ resolve();
+ }
+ return;
+ }
+ p[name] = val;
+ resolve();
+ })
+ });
+}
diff --git a/roslaunch_editor/www/roslib.js b/roslaunch_editor/www/roslib.js
new file mode 100644
index 00000000..175d3d74
--- /dev/null
+++ b/roslaunch_editor/www/roslib.js
@@ -0,0 +1,4560 @@
+(function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+(function(global, undefined) { "use strict";
+var POW_2_24 = Math.pow(2, -24),
+ POW_2_32 = Math.pow(2, 32),
+ POW_2_53 = Math.pow(2, 53);
+
+function encode(value) {
+ var data = new ArrayBuffer(256);
+ var dataView = new DataView(data);
+ var lastLength;
+ var offset = 0;
+
+ function ensureSpace(length) {
+ var newByteLength = data.byteLength;
+ var requiredLength = offset + length;
+ while (newByteLength < requiredLength)
+ newByteLength *= 2;
+ if (newByteLength !== data.byteLength) {
+ var oldDataView = dataView;
+ data = new ArrayBuffer(newByteLength);
+ dataView = new DataView(data);
+ var uint32count = (offset + 3) >> 2;
+ for (var i = 0; i < uint32count; ++i)
+ dataView.setUint32(i * 4, oldDataView.getUint32(i * 4));
+ }
+
+ lastLength = length;
+ return dataView;
+ }
+ function write() {
+ offset += lastLength;
+ }
+ function writeFloat64(value) {
+ write(ensureSpace(8).setFloat64(offset, value));
+ }
+ function writeUint8(value) {
+ write(ensureSpace(1).setUint8(offset, value));
+ }
+ function writeUint8Array(value) {
+ var dataView = ensureSpace(value.length);
+ for (var i = 0; i < value.length; ++i)
+ dataView.setUint8(offset + i, value[i]);
+ write();
+ }
+ function writeUint16(value) {
+ write(ensureSpace(2).setUint16(offset, value));
+ }
+ function writeUint32(value) {
+ write(ensureSpace(4).setUint32(offset, value));
+ }
+ function writeUint64(value) {
+ var low = value % POW_2_32;
+ var high = (value - low) / POW_2_32;
+ var dataView = ensureSpace(8);
+ dataView.setUint32(offset, high);
+ dataView.setUint32(offset + 4, low);
+ write();
+ }
+ function writeTypeAndLength(type, length) {
+ if (length < 24) {
+ writeUint8(type << 5 | length);
+ } else if (length < 0x100) {
+ writeUint8(type << 5 | 24);
+ writeUint8(length);
+ } else if (length < 0x10000) {
+ writeUint8(type << 5 | 25);
+ writeUint16(length);
+ } else if (length < 0x100000000) {
+ writeUint8(type << 5 | 26);
+ writeUint32(length);
+ } else {
+ writeUint8(type << 5 | 27);
+ writeUint64(length);
+ }
+ }
+
+ function encodeItem(value) {
+ var i;
+
+ if (value === false)
+ return writeUint8(0xf4);
+ if (value === true)
+ return writeUint8(0xf5);
+ if (value === null)
+ return writeUint8(0xf6);
+ if (value === undefined)
+ return writeUint8(0xf7);
+
+ switch (typeof value) {
+ case "number":
+ if (Math.floor(value) === value) {
+ if (0 <= value && value <= POW_2_53)
+ return writeTypeAndLength(0, value);
+ if (-POW_2_53 <= value && value < 0)
+ return writeTypeAndLength(1, -(value + 1));
+ }
+ writeUint8(0xfb);
+ return writeFloat64(value);
+
+ case "string":
+ var utf8data = [];
+ for (i = 0; i < value.length; ++i) {
+ var charCode = value.charCodeAt(i);
+ if (charCode < 0x80) {
+ utf8data.push(charCode);
+ } else if (charCode < 0x800) {
+ utf8data.push(0xc0 | charCode >> 6);
+ utf8data.push(0x80 | charCode & 0x3f);
+ } else if (charCode < 0xd800) {
+ utf8data.push(0xe0 | charCode >> 12);
+ utf8data.push(0x80 | (charCode >> 6) & 0x3f);
+ utf8data.push(0x80 | charCode & 0x3f);
+ } else {
+ charCode = (charCode & 0x3ff) << 10;
+ charCode |= value.charCodeAt(++i) & 0x3ff;
+ charCode += 0x10000;
+
+ utf8data.push(0xf0 | charCode >> 18);
+ utf8data.push(0x80 | (charCode >> 12) & 0x3f);
+ utf8data.push(0x80 | (charCode >> 6) & 0x3f);
+ utf8data.push(0x80 | charCode & 0x3f);
+ }
+ }
+
+ writeTypeAndLength(3, utf8data.length);
+ return writeUint8Array(utf8data);
+
+ default:
+ var length;
+ if (Array.isArray(value)) {
+ length = value.length;
+ writeTypeAndLength(4, length);
+ for (i = 0; i < length; ++i)
+ encodeItem(value[i]);
+ } else if (value instanceof Uint8Array) {
+ writeTypeAndLength(2, value.length);
+ writeUint8Array(value);
+ } else {
+ var keys = Object.keys(value);
+ length = keys.length;
+ writeTypeAndLength(5, length);
+ for (i = 0; i < length; ++i) {
+ var key = keys[i];
+ encodeItem(key);
+ encodeItem(value[key]);
+ }
+ }
+ }
+ }
+
+ encodeItem(value);
+
+ if ("slice" in data)
+ return data.slice(0, offset);
+
+ var ret = new ArrayBuffer(offset);
+ var retView = new DataView(ret);
+ for (var i = 0; i < offset; ++i)
+ retView.setUint8(i, dataView.getUint8(i));
+ return ret;
+}
+
+function decode(data, tagger, simpleValue) {
+ var dataView = new DataView(data);
+ var offset = 0;
+
+ if (typeof tagger !== "function")
+ tagger = function(value) { return value; };
+ if (typeof simpleValue !== "function")
+ simpleValue = function() { return undefined; };
+
+ function read(value, length) {
+ offset += length;
+ return value;
+ }
+ function readArrayBuffer(length) {
+ return read(new Uint8Array(data, offset, length), length);
+ }
+ function readFloat16() {
+ var tempArrayBuffer = new ArrayBuffer(4);
+ var tempDataView = new DataView(tempArrayBuffer);
+ var value = readUint16();
+
+ var sign = value & 0x8000;
+ var exponent = value & 0x7c00;
+ var fraction = value & 0x03ff;
+
+ if (exponent === 0x7c00)
+ exponent = 0xff << 10;
+ else if (exponent !== 0)
+ exponent += (127 - 15) << 10;
+ else if (fraction !== 0)
+ return fraction * POW_2_24;
+
+ tempDataView.setUint32(0, sign << 16 | exponent << 13 | fraction << 13);
+ return tempDataView.getFloat32(0);
+ }
+ function readFloat32() {
+ return read(dataView.getFloat32(offset), 4);
+ }
+ function readFloat64() {
+ return read(dataView.getFloat64(offset), 8);
+ }
+ function readUint8() {
+ return read(dataView.getUint8(offset), 1);
+ }
+ function readUint16() {
+ return read(dataView.getUint16(offset), 2);
+ }
+ function readUint32() {
+ return read(dataView.getUint32(offset), 4);
+ }
+ function readUint64() {
+ return readUint32() * POW_2_32 + readUint32();
+ }
+ function readBreak() {
+ if (dataView.getUint8(offset) !== 0xff)
+ return false;
+ offset += 1;
+ return true;
+ }
+ function readLength(additionalInformation) {
+ if (additionalInformation < 24)
+ return additionalInformation;
+ if (additionalInformation === 24)
+ return readUint8();
+ if (additionalInformation === 25)
+ return readUint16();
+ if (additionalInformation === 26)
+ return readUint32();
+ if (additionalInformation === 27)
+ return readUint64();
+ if (additionalInformation === 31)
+ return -1;
+ throw "Invalid length encoding";
+ }
+ function readIndefiniteStringLength(majorType) {
+ var initialByte = readUint8();
+ if (initialByte === 0xff)
+ return -1;
+ var length = readLength(initialByte & 0x1f);
+ if (length < 0 || (initialByte >> 5) !== majorType)
+ throw "Invalid indefinite length element";
+ return length;
+ }
+
+ function appendUtf16data(utf16data, length) {
+ for (var i = 0; i < length; ++i) {
+ var value = readUint8();
+ if (value & 0x80) {
+ if (value < 0xe0) {
+ value = (value & 0x1f) << 6
+ | (readUint8() & 0x3f);
+ length -= 1;
+ } else if (value < 0xf0) {
+ value = (value & 0x0f) << 12
+ | (readUint8() & 0x3f) << 6
+ | (readUint8() & 0x3f);
+ length -= 2;
+ } else {
+ value = (value & 0x0f) << 18
+ | (readUint8() & 0x3f) << 12
+ | (readUint8() & 0x3f) << 6
+ | (readUint8() & 0x3f);
+ length -= 3;
+ }
+ }
+
+ if (value < 0x10000) {
+ utf16data.push(value);
+ } else {
+ value -= 0x10000;
+ utf16data.push(0xd800 | (value >> 10));
+ utf16data.push(0xdc00 | (value & 0x3ff));
+ }
+ }
+ }
+
+ function decodeItem() {
+ var initialByte = readUint8();
+ var majorType = initialByte >> 5;
+ var additionalInformation = initialByte & 0x1f;
+ var i;
+ var length;
+
+ if (majorType === 7) {
+ switch (additionalInformation) {
+ case 25:
+ return readFloat16();
+ case 26:
+ return readFloat32();
+ case 27:
+ return readFloat64();
+ }
+ }
+
+ length = readLength(additionalInformation);
+ if (length < 0 && (majorType < 2 || 6 < majorType))
+ throw "Invalid length";
+
+ switch (majorType) {
+ case 0:
+ return length;
+ case 1:
+ return -1 - length;
+ case 2:
+ if (length < 0) {
+ var elements = [];
+ var fullArrayLength = 0;
+ while ((length = readIndefiniteStringLength(majorType)) >= 0) {
+ fullArrayLength += length;
+ elements.push(readArrayBuffer(length));
+ }
+ var fullArray = new Uint8Array(fullArrayLength);
+ var fullArrayOffset = 0;
+ for (i = 0; i < elements.length; ++i) {
+ fullArray.set(elements[i], fullArrayOffset);
+ fullArrayOffset += elements[i].length;
+ }
+ return fullArray;
+ }
+ return readArrayBuffer(length);
+ case 3:
+ var utf16data = [];
+ if (length < 0) {
+ while ((length = readIndefiniteStringLength(majorType)) >= 0)
+ appendUtf16data(utf16data, length);
+ } else
+ appendUtf16data(utf16data, length);
+ return String.fromCharCode.apply(null, utf16data);
+ case 4:
+ var retArray;
+ if (length < 0) {
+ retArray = [];
+ while (!readBreak())
+ retArray.push(decodeItem());
+ } else {
+ retArray = new Array(length);
+ for (i = 0; i < length; ++i)
+ retArray[i] = decodeItem();
+ }
+ return retArray;
+ case 5:
+ var retObject = {};
+ for (i = 0; i < length || length < 0 && !readBreak(); ++i) {
+ var key = decodeItem();
+ retObject[key] = decodeItem();
+ }
+ return retObject;
+ case 6:
+ return tagger(decodeItem(), length);
+ case 7:
+ switch (length) {
+ case 20:
+ return false;
+ case 21:
+ return true;
+ case 22:
+ return null;
+ case 23:
+ return undefined;
+ default:
+ return simpleValue(length);
+ }
+ }
+ }
+
+ var ret = decodeItem();
+ if (offset !== data.byteLength)
+ throw "Remaining bytes";
+ return ret;
+}
+
+var obj = { encode: encode, decode: decode };
+
+if (typeof define === "function" && define.amd)
+ define("cbor/cbor", obj);
+else if (typeof module !== 'undefined' && module.exports)
+ module.exports = obj;
+else if (!global.CBOR)
+ global.CBOR = obj;
+
+})(this);
+
+},{}],2:[function(require,module,exports){
+(function (process){
+/*!
+ * EventEmitter2
+ * https://github.com/hij1nx/EventEmitter2
+ *
+ * Copyright (c) 2013 hij1nx
+ * Licensed under the MIT license.
+ */
+;!function(undefined) {
+
+ var isArray = Array.isArray ? Array.isArray : function _isArray(obj) {
+ return Object.prototype.toString.call(obj) === "[object Array]";
+ };
+ var defaultMaxListeners = 10;
+
+ function init() {
+ this._events = {};
+ if (this._conf) {
+ configure.call(this, this._conf);
+ }
+ }
+
+ function configure(conf) {
+ if (conf) {
+ this._conf = conf;
+
+ conf.delimiter && (this.delimiter = conf.delimiter);
+ this._maxListeners = conf.maxListeners !== undefined ? conf.maxListeners : defaultMaxListeners;
+
+ conf.wildcard && (this.wildcard = conf.wildcard);
+ conf.newListener && (this.newListener = conf.newListener);
+ conf.verboseMemoryLeak && (this.verboseMemoryLeak = conf.verboseMemoryLeak);
+
+ if (this.wildcard) {
+ this.listenerTree = {};
+ }
+ } else {
+ this._maxListeners = defaultMaxListeners;
+ }
+ }
+
+ function logPossibleMemoryLeak(count, eventName) {
+ var errorMsg = '(node) warning: possible EventEmitter memory ' +
+ 'leak detected. ' + count + ' listeners added. ' +
+ 'Use emitter.setMaxListeners() to increase limit.';
+
+ if(this.verboseMemoryLeak){
+ errorMsg += ' Event name: ' + eventName + '.';
+ }
+
+ if(typeof process !== 'undefined' && process.emitWarning){
+ var e = new Error(errorMsg);
+ e.name = 'MaxListenersExceededWarning';
+ e.emitter = this;
+ e.count = count;
+ process.emitWarning(e);
+ } else {
+ console.error(errorMsg);
+
+ if (console.trace){
+ console.trace();
+ }
+ }
+ }
+
+ function EventEmitter(conf) {
+ this._events = {};
+ this.newListener = false;
+ this.verboseMemoryLeak = false;
+ configure.call(this, conf);
+ }
+ EventEmitter.EventEmitter2 = EventEmitter; // backwards compatibility for exporting EventEmitter property
+
+ //
+ // Attention, function return type now is array, always !
+ // It has zero elements if no any matches found and one or more
+ // elements (leafs) if there are matches
+ //
+ function searchListenerTree(handlers, type, tree, i) {
+ if (!tree) {
+ return [];
+ }
+ var listeners=[], leaf, len, branch, xTree, xxTree, isolatedBranch, endReached,
+ typeLength = type.length, currentType = type[i], nextType = type[i+1];
+ if (i === typeLength && tree._listeners) {
+ //
+ // If at the end of the event(s) list and the tree has listeners
+ // invoke those listeners.
+ //
+ if (typeof tree._listeners === 'function') {
+ handlers && handlers.push(tree._listeners);
+ return [tree];
+ } else {
+ for (leaf = 0, len = tree._listeners.length; leaf < len; leaf++) {
+ handlers && handlers.push(tree._listeners[leaf]);
+ }
+ return [tree];
+ }
+ }
+
+ if ((currentType === '*' || currentType === '**') || tree[currentType]) {
+ //
+ // If the event emitted is '*' at this part
+ // or there is a concrete match at this patch
+ //
+ if (currentType === '*') {
+ for (branch in tree) {
+ if (branch !== '_listeners' && tree.hasOwnProperty(branch)) {
+ listeners = listeners.concat(searchListenerTree(handlers, type, tree[branch], i+1));
+ }
+ }
+ return listeners;
+ } else if(currentType === '**') {
+ endReached = (i+1 === typeLength || (i+2 === typeLength && nextType === '*'));
+ if(endReached && tree._listeners) {
+ // The next element has a _listeners, add it to the handlers.
+ listeners = listeners.concat(searchListenerTree(handlers, type, tree, typeLength));
+ }
+
+ for (branch in tree) {
+ if (branch !== '_listeners' && tree.hasOwnProperty(branch)) {
+ if(branch === '*' || branch === '**') {
+ if(tree[branch]._listeners && !endReached) {
+ listeners = listeners.concat(searchListenerTree(handlers, type, tree[branch], typeLength));
+ }
+ listeners = listeners.concat(searchListenerTree(handlers, type, tree[branch], i));
+ } else if(branch === nextType) {
+ listeners = listeners.concat(searchListenerTree(handlers, type, tree[branch], i+2));
+ } else {
+ // No match on this one, shift into the tree but not in the type array.
+ listeners = listeners.concat(searchListenerTree(handlers, type, tree[branch], i));
+ }
+ }
+ }
+ return listeners;
+ }
+
+ listeners = listeners.concat(searchListenerTree(handlers, type, tree[currentType], i+1));
+ }
+
+ xTree = tree['*'];
+ if (xTree) {
+ //
+ // If the listener tree will allow any match for this part,
+ // then recursively explore all branches of the tree
+ //
+ searchListenerTree(handlers, type, xTree, i+1);
+ }
+
+ xxTree = tree['**'];
+ if(xxTree) {
+ if(i < typeLength) {
+ if(xxTree._listeners) {
+ // If we have a listener on a '**', it will catch all, so add its handler.
+ searchListenerTree(handlers, type, xxTree, typeLength);
+ }
+
+ // Build arrays of matching next branches and others.
+ for(branch in xxTree) {
+ if(branch !== '_listeners' && xxTree.hasOwnProperty(branch)) {
+ if(branch === nextType) {
+ // We know the next element will match, so jump twice.
+ searchListenerTree(handlers, type, xxTree[branch], i+2);
+ } else if(branch === currentType) {
+ // Current node matches, move into the tree.
+ searchListenerTree(handlers, type, xxTree[branch], i+1);
+ } else {
+ isolatedBranch = {};
+ isolatedBranch[branch] = xxTree[branch];
+ searchListenerTree(handlers, type, { '**': isolatedBranch }, i+1);
+ }
+ }
+ }
+ } else if(xxTree._listeners) {
+ // We have reached the end and still on a '**'
+ searchListenerTree(handlers, type, xxTree, typeLength);
+ } else if(xxTree['*'] && xxTree['*']._listeners) {
+ searchListenerTree(handlers, type, xxTree['*'], typeLength);
+ }
+ }
+
+ return listeners;
+ }
+
+ function growListenerTree(type, listener) {
+
+ type = typeof type === 'string' ? type.split(this.delimiter) : type.slice();
+
+ //
+ // Looks for two consecutive '**', if so, don't add the event at all.
+ //
+ for(var i = 0, len = type.length; i+1 < len; i++) {
+ if(type[i] === '**' && type[i+1] === '**') {
+ return;
+ }
+ }
+
+ var tree = this.listenerTree;
+ var name = type.shift();
+
+ while (name !== undefined) {
+
+ if (!tree[name]) {
+ tree[name] = {};
+ }
+
+ tree = tree[name];
+
+ if (type.length === 0) {
+
+ if (!tree._listeners) {
+ tree._listeners = listener;
+ }
+ else {
+ if (typeof tree._listeners === 'function') {
+ tree._listeners = [tree._listeners];
+ }
+
+ tree._listeners.push(listener);
+
+ if (
+ !tree._listeners.warned &&
+ this._maxListeners > 0 &&
+ tree._listeners.length > this._maxListeners
+ ) {
+ tree._listeners.warned = true;
+ logPossibleMemoryLeak.call(this, tree._listeners.length, name);
+ }
+ }
+ return true;
+ }
+ name = type.shift();
+ }
+ return true;
+ }
+
+ // By default EventEmitters will print a warning if more than
+ // 10 listeners are added to it. This is a useful default which
+ // helps finding memory leaks.
+ //
+ // Obviously not all Emitters should be limited to 10. This function allows
+ // that to be increased. Set to zero for unlimited.
+
+ EventEmitter.prototype.delimiter = '.';
+
+ EventEmitter.prototype.setMaxListeners = function(n) {
+ if (n !== undefined) {
+ this._maxListeners = n;
+ if (!this._conf) this._conf = {};
+ this._conf.maxListeners = n;
+ }
+ };
+
+ EventEmitter.prototype.event = '';
+
+
+ EventEmitter.prototype.once = function(event, fn) {
+ return this._once(event, fn, false);
+ };
+
+ EventEmitter.prototype.prependOnceListener = function(event, fn) {
+ return this._once(event, fn, true);
+ };
+
+ EventEmitter.prototype._once = function(event, fn, prepend) {
+ this._many(event, 1, fn, prepend);
+ return this;
+ };
+
+ EventEmitter.prototype.many = function(event, ttl, fn) {
+ return this._many(event, ttl, fn, false);
+ }
+
+ EventEmitter.prototype.prependMany = function(event, ttl, fn) {
+ return this._many(event, ttl, fn, true);
+ }
+
+ EventEmitter.prototype._many = function(event, ttl, fn, prepend) {
+ var self = this;
+
+ if (typeof fn !== 'function') {
+ throw new Error('many only accepts instances of Function');
+ }
+
+ function listener() {
+ if (--ttl === 0) {
+ self.off(event, listener);
+ }
+ return fn.apply(this, arguments);
+ }
+
+ listener._origin = fn;
+
+ this._on(event, listener, prepend);
+
+ return self;
+ };
+
+ EventEmitter.prototype.emit = function() {
+
+ this._events || init.call(this);
+
+ var type = arguments[0];
+
+ if (type === 'newListener' && !this.newListener) {
+ if (!this._events.newListener) {
+ return false;
+ }
+ }
+
+ var al = arguments.length;
+ var args,l,i,j;
+ var handler;
+
+ if (this._all && this._all.length) {
+ handler = this._all.slice();
+ if (al > 3) {
+ args = new Array(al);
+ for (j = 0; j < al; j++) args[j] = arguments[j];
+ }
+
+ for (i = 0, l = handler.length; i < l; i++) {
+ this.event = type;
+ switch (al) {
+ case 1:
+ handler[i].call(this, type);
+ break;
+ case 2:
+ handler[i].call(this, type, arguments[1]);
+ break;
+ case 3:
+ handler[i].call(this, type, arguments[1], arguments[2]);
+ break;
+ default:
+ handler[i].apply(this, args);
+ }
+ }
+ }
+
+ if (this.wildcard) {
+ handler = [];
+ var ns = typeof type === 'string' ? type.split(this.delimiter) : type.slice();
+ searchListenerTree.call(this, handler, ns, this.listenerTree, 0);
+ } else {
+ handler = this._events[type];
+ if (typeof handler === 'function') {
+ this.event = type;
+ switch (al) {
+ case 1:
+ handler.call(this);
+ break;
+ case 2:
+ handler.call(this, arguments[1]);
+ break;
+ case 3:
+ handler.call(this, arguments[1], arguments[2]);
+ break;
+ default:
+ args = new Array(al - 1);
+ for (j = 1; j < al; j++) args[j - 1] = arguments[j];
+ handler.apply(this, args);
+ }
+ return true;
+ } else if (handler) {
+ // need to make copy of handlers because list can change in the middle
+ // of emit call
+ handler = handler.slice();
+ }
+ }
+
+ if (handler && handler.length) {
+ if (al > 3) {
+ args = new Array(al - 1);
+ for (j = 1; j < al; j++) args[j - 1] = arguments[j];
+ }
+ for (i = 0, l = handler.length; i < l; i++) {
+ this.event = type;
+ switch (al) {
+ case 1:
+ handler[i].call(this);
+ break;
+ case 2:
+ handler[i].call(this, arguments[1]);
+ break;
+ case 3:
+ handler[i].call(this, arguments[1], arguments[2]);
+ break;
+ default:
+ handler[i].apply(this, args);
+ }
+ }
+ return true;
+ } else if (!this._all && type === 'error') {
+ if (arguments[1] instanceof Error) {
+ throw arguments[1]; // Unhandled 'error' event
+ } else {
+ throw new Error("Uncaught, unspecified 'error' event.");
+ }
+ return false;
+ }
+
+ return !!this._all;
+ };
+
+ EventEmitter.prototype.emitAsync = function() {
+
+ this._events || init.call(this);
+
+ var type = arguments[0];
+
+ if (type === 'newListener' && !this.newListener) {
+ if (!this._events.newListener) { return Promise.resolve([false]); }
+ }
+
+ var promises= [];
+
+ var al = arguments.length;
+ var args,l,i,j;
+ var handler;
+
+ if (this._all) {
+ if (al > 3) {
+ args = new Array(al);
+ for (j = 1; j < al; j++) args[j] = arguments[j];
+ }
+ for (i = 0, l = this._all.length; i < l; i++) {
+ this.event = type;
+ switch (al) {
+ case 1:
+ promises.push(this._all[i].call(this, type));
+ break;
+ case 2:
+ promises.push(this._all[i].call(this, type, arguments[1]));
+ break;
+ case 3:
+ promises.push(this._all[i].call(this, type, arguments[1], arguments[2]));
+ break;
+ default:
+ promises.push(this._all[i].apply(this, args));
+ }
+ }
+ }
+
+ if (this.wildcard) {
+ handler = [];
+ var ns = typeof type === 'string' ? type.split(this.delimiter) : type.slice();
+ searchListenerTree.call(this, handler, ns, this.listenerTree, 0);
+ } else {
+ handler = this._events[type];
+ }
+
+ if (typeof handler === 'function') {
+ this.event = type;
+ switch (al) {
+ case 1:
+ promises.push(handler.call(this));
+ break;
+ case 2:
+ promises.push(handler.call(this, arguments[1]));
+ break;
+ case 3:
+ promises.push(handler.call(this, arguments[1], arguments[2]));
+ break;
+ default:
+ args = new Array(al - 1);
+ for (j = 1; j < al; j++) args[j - 1] = arguments[j];
+ promises.push(handler.apply(this, args));
+ }
+ } else if (handler && handler.length) {
+ handler = handler.slice();
+ if (al > 3) {
+ args = new Array(al - 1);
+ for (j = 1; j < al; j++) args[j - 1] = arguments[j];
+ }
+ for (i = 0, l = handler.length; i < l; i++) {
+ this.event = type;
+ switch (al) {
+ case 1:
+ promises.push(handler[i].call(this));
+ break;
+ case 2:
+ promises.push(handler[i].call(this, arguments[1]));
+ break;
+ case 3:
+ promises.push(handler[i].call(this, arguments[1], arguments[2]));
+ break;
+ default:
+ promises.push(handler[i].apply(this, args));
+ }
+ }
+ } else if (!this._all && type === 'error') {
+ if (arguments[1] instanceof Error) {
+ return Promise.reject(arguments[1]); // Unhandled 'error' event
+ } else {
+ return Promise.reject("Uncaught, unspecified 'error' event.");
+ }
+ }
+
+ return Promise.all(promises);
+ };
+
+ EventEmitter.prototype.on = function(type, listener) {
+ return this._on(type, listener, false);
+ };
+
+ EventEmitter.prototype.prependListener = function(type, listener) {
+ return this._on(type, listener, true);
+ };
+
+ EventEmitter.prototype.onAny = function(fn) {
+ return this._onAny(fn, false);
+ };
+
+ EventEmitter.prototype.prependAny = function(fn) {
+ return this._onAny(fn, true);
+ };
+
+ EventEmitter.prototype.addListener = EventEmitter.prototype.on;
+
+ EventEmitter.prototype._onAny = function(fn, prepend){
+ if (typeof fn !== 'function') {
+ throw new Error('onAny only accepts instances of Function');
+ }
+
+ if (!this._all) {
+ this._all = [];
+ }
+
+ // Add the function to the event listener collection.
+ if(prepend){
+ this._all.unshift(fn);
+ }else{
+ this._all.push(fn);
+ }
+
+ return this;
+ }
+
+ EventEmitter.prototype._on = function(type, listener, prepend) {
+ if (typeof type === 'function') {
+ this._onAny(type, listener);
+ return this;
+ }
+
+ if (typeof listener !== 'function') {
+ throw new Error('on only accepts instances of Function');
+ }
+ this._events || init.call(this);
+
+ // To avoid recursion in the case that type == "newListeners"! Before
+ // adding it to the listeners, first emit "newListeners".
+ this.emit('newListener', type, listener);
+
+ if (this.wildcard) {
+ growListenerTree.call(this, type, listener);
+ return this;
+ }
+
+ if (!this._events[type]) {
+ // Optimize the case of one listener. Don't need the extra array object.
+ this._events[type] = listener;
+ }
+ else {
+ if (typeof this._events[type] === 'function') {
+ // Change to array.
+ this._events[type] = [this._events[type]];
+ }
+
+ // If we've already got an array, just add
+ if(prepend){
+ this._events[type].unshift(listener);
+ }else{
+ this._events[type].push(listener);
+ }
+
+ // Check for listener leak
+ if (
+ !this._events[type].warned &&
+ this._maxListeners > 0 &&
+ this._events[type].length > this._maxListeners
+ ) {
+ this._events[type].warned = true;
+ logPossibleMemoryLeak.call(this, this._events[type].length, type);
+ }
+ }
+
+ return this;
+ }
+
+ EventEmitter.prototype.off = function(type, listener) {
+ if (typeof listener !== 'function') {
+ throw new Error('removeListener only takes instances of Function');
+ }
+
+ var handlers,leafs=[];
+
+ if(this.wildcard) {
+ var ns = typeof type === 'string' ? type.split(this.delimiter) : type.slice();
+ leafs = searchListenerTree.call(this, null, ns, this.listenerTree, 0);
+ }
+ else {
+ // does not use listeners(), so no side effect of creating _events[type]
+ if (!this._events[type]) return this;
+ handlers = this._events[type];
+ leafs.push({_listeners:handlers});
+ }
+
+ for (var iLeaf=0; iLeaf 0) {
+ recursivelyGarbageCollect(root[key]);
+ }
+ if (Object.keys(obj).length === 0) {
+ delete root[key];
+ }
+ }
+ }
+ recursivelyGarbageCollect(this.listenerTree);
+
+ return this;
+ };
+
+ EventEmitter.prototype.offAny = function(fn) {
+ var i = 0, l = 0, fns;
+ if (fn && this._all && this._all.length > 0) {
+ fns = this._all;
+ for(i = 0, l = fns.length; i < l; i++) {
+ if(fn === fns[i]) {
+ fns.splice(i, 1);
+ this.emit("removeListenerAny", fn);
+ return this;
+ }
+ }
+ } else {
+ fns = this._all;
+ for(i = 0, l = fns.length; i < l; i++)
+ this.emit("removeListenerAny", fns[i]);
+ this._all = [];
+ }
+ return this;
+ };
+
+ EventEmitter.prototype.removeListener = EventEmitter.prototype.off;
+
+ EventEmitter.prototype.removeAllListeners = function(type) {
+ if (arguments.length === 0) {
+ !this._events || init.call(this);
+ return this;
+ }
+
+ if (this.wildcard) {
+ var ns = typeof type === 'string' ? type.split(this.delimiter) : type.slice();
+ var leafs = searchListenerTree.call(this, null, ns, this.listenerTree, 0);
+
+ for (var iLeaf=0; iLeaf 1) {
+ for (var i = 1; i < arguments.length; i++) {
+ args[i - 1] = arguments[i];
+ }
+ }
+ queue.push(new Item(fun, args));
+ if (queue.length === 1 && !draining) {
+ runTimeout(drainQueue);
+ }
+};
+
+// v8 likes predictible objects
+function Item(fun, array) {
+ this.fun = fun;
+ this.array = array;
+}
+Item.prototype.run = function () {
+ this.fun.apply(null, this.array);
+};
+process.title = 'browser';
+process.browser = true;
+process.env = {};
+process.argv = [];
+process.version = ''; // empty string to avoid regexp issues
+process.versions = {};
+
+function noop() {}
+
+process.on = noop;
+process.addListener = noop;
+process.once = noop;
+process.off = noop;
+process.removeListener = noop;
+process.removeAllListeners = noop;
+process.emit = noop;
+process.prependListener = noop;
+process.prependOnceListener = noop;
+
+process.listeners = function (name) { return [] }
+
+process.binding = function (name) {
+ throw new Error('process.binding is not supported');
+};
+
+process.cwd = function () { return '/' };
+process.chdir = function (dir) {
+ throw new Error('process.chdir is not supported');
+};
+process.umask = function() { return 0; };
+
+},{}],5:[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 : '1.0.1'
+};
+
+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":11,"./core":20,"./math":25,"./tf":28,"./urdf":40,"object-assign":3}],6:[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":5}],7:[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":12,"../core/Topic":19,"eventemitter2":2}],8:[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":12,"../core/Topic":19,"eventemitter2":2}],9:[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":12,"eventemitter2":2}],10:[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":12,"../core/Topic":19,"eventemitter2":2}],11:[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":14,"../mixin":26,"./ActionClient":7,"./ActionListener":8,"./Goal":9,"./SimpleActionServer":10}],12:[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":3}],13:[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":15,"./ServiceRequest":16}],14:[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 {
+ if (!this.socket || this.socket.readyState === WebSocket.CLOSED) {
+ var sock = new WebSocket(url);
+ sock.binaryType = 'arraybuffer';
+ this.socket = assign(sock, 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":15,"./ServiceRequest":16,"./SocketAdapter.js":18,"eventemitter2":2,"object-assign":3,"ws":42}],15:[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. Does nothing if this service is currently advertised.
+ *
+ * @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,
+ type: this.serviceType,
+ args : request
+ };
+ this.ros.callOnConnection(call);
+};
+
+/**
+ * Advertise the service. This turns the Service object from a client
+ * into a server. The callback will be called with every request
+ * that's made on this service.
+ *
+ * @param callback - This works similarly to the callback for a C++ service and should take the following params:
+ * * request - the service request
+ * * response - an empty dictionary. Take care not to overwrite this. Instead, only modify the values within.
+ * It should return true if the service has finished successfully,
+ * i.e. without any fatal errors.
+ */
+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":16,"./ServiceResponse":17,"eventemitter2":2}],16:[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":3}],17:[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":3}],18:[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 CBOR = require('cbor-js');
+var typedArrayTagger = require('../util/cborTypedArrayTags');
+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 if (data.data instanceof ArrayBuffer) {
+ var decoded = CBOR.decode(data.data, typedArrayTagger);
+ handleMessage(decoded);
+ } else {
+ var message = JSON.parse(typeof data === 'string' ? data : data.data);
+ handlePng(message, handleMessage);
+ }
+ }
+ };
+}
+
+module.exports = SocketAdapter;
+
+},{"../util/cborTypedArrayTags":41,"../util/decompressPng":44,"cbor-js":1,"ws":42}],19:[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' or 'cbor'
+ * * 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 !== 'cbor' && this.compression !== 'none') {
+ this.emit('warning', this.compression +
+ ' compression is not supported. No compression will be used.');
+ this.compression = 'none';
+ }
+
+ // 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":12,"eventemitter2":2}],20:[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":26,"./Message":12,"./Param":13,"./Ros":14,"./Service":15,"./ServiceRequest":16,"./ServiceResponse":17,"./Topic":19}],21:[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);
+};
+
+/**
+ * Multiplies this pose with another pose without altering this pose.
+ *
+ * @returns Result of multiplication.
+ */
+Pose.prototype.multiply = function(pose) {
+ var p = pose.clone();
+ p.applyTransform({ rotation: this.orientation, translation: this.position });
+ return p;
+};
+
+/**
+ * Computes the inverse of this pose.
+ *
+ * @returns Inverse of pose.
+ */
+Pose.prototype.getInverse = function() {
+ var inverse = this.clone();
+ inverse.orientation.invert();
+ inverse.position.multiplyQuaternion(inverse.orientation);
+ inverse.position.x *= -1;
+ inverse.position.y *= -1;
+ inverse.position.z *= -1;
+ return inverse;
+};
+
+module.exports = Pose;
+},{"./Quaternion":22,"./Vector3":24}],22:[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;
+
+},{}],23:[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":22,"./Vector3":24}],24:[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;
+},{}],25:[function(require,module,exports){
+module.exports = {
+ Pose: require('./Pose'),
+ Quaternion: require('./Quaternion'),
+ Transform: require('./Transform'),
+ Vector3: require('./Vector3')
+};
+
+},{"./Pose":21,"./Quaternion":22,"./Transform":23,"./Vector3":24}],26:[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);
+ };
+ });
+};
+
+},{}],27:[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":7,"../actionlib/Goal":9,"../core/Service.js":15,"../core/ServiceRequest.js":16,"../math/Transform":23}],28:[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":14,"../mixin":26,"./TFClient":27}],29:[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":24,"./UrdfTypes":38}],30:[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;
+},{}],31:[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":38}],32:[function(require,module,exports){
+/**
+ * @fileOverview
+ * @author David V. Lu!! davidvlu@gmail.com
+ */
+
+var Pose = require('../math/Pose');
+var Vector3 = require('../math/Vector3');
+var Quaternion = require('../math/Quaternion');
+
+/**
+ * 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') );
+ }
+
+ // Origin
+ var origins = options.xml.getElementsByTagName('origin');
+ if (origins.length === 0) {
+ // use the identity as the default
+ this.origin = new Pose();
+ } else {
+ // Check the XYZ
+ var xyz = origins[0].getAttribute('xyz');
+ var position = new Vector3();
+ if (xyz) {
+ xyz = xyz.split(' ');
+ position = new Vector3({
+ x : parseFloat(xyz[0]),
+ y : parseFloat(xyz[1]),
+ z : parseFloat(xyz[2])
+ });
+ }
+
+ // Check the RPY
+ var rpy = origins[0].getAttribute('rpy');
+ var orientation = new Quaternion();
+ if (rpy) {
+ rpy = rpy.split(' ');
+ // Convert from RPY
+ var roll = parseFloat(rpy[0]);
+ var pitch = parseFloat(rpy[1]);
+ var yaw = parseFloat(rpy[2]);
+ var phi = roll / 2.0;
+ var the = pitch / 2.0;
+ var psi = yaw / 2.0;
+ var x = Math.sin(phi) * Math.cos(the) * Math.cos(psi) - Math.cos(phi) * Math.sin(the)
+ * Math.sin(psi);
+ var y = Math.cos(phi) * Math.sin(the) * Math.cos(psi) + Math.sin(phi) * Math.cos(the)
+ * Math.sin(psi);
+ var z = Math.cos(phi) * Math.cos(the) * Math.sin(psi) - Math.sin(phi) * Math.sin(the)
+ * Math.cos(psi);
+ var w = Math.cos(phi) * Math.cos(the) * Math.cos(psi) + Math.sin(phi) * Math.sin(the)
+ * Math.sin(psi);
+
+ orientation = new Quaternion({
+ x : x,
+ y : y,
+ z : z,
+ w : w
+ });
+ orientation.normalize();
+ }
+ this.origin = new Pose({
+ position : position,
+ orientation : orientation
+ });
+ }
+}
+
+module.exports = UrdfJoint;
+
+},{"../math/Pose":21,"../math/Quaternion":22,"../math/Vector3":24}],33:[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":30,"object-assign":3}],35:[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":24,"./UrdfTypes":38}],36:[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":21,"../math/Quaternion":22,"../math/Vector3":24,"./UrdfBox":29,"./UrdfCylinder":31,"./UrdfMaterial":34,"./UrdfMesh":35,"./UrdfSphere":37}],40:[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":29,"./UrdfColor":30,"./UrdfCylinder":31,"./UrdfLink":33,"./UrdfMaterial":34,"./UrdfMesh":35,"./UrdfModel":36,"./UrdfSphere":37,"./UrdfTypes":38,"./UrdfVisual":39,"object-assign":3}],41:[function(require,module,exports){
+'use strict';
+
+var UPPER32 = Math.pow(2, 32);
+
+var warnedPrecision = false;
+function warnPrecision() {
+ if (!warnedPrecision) {
+ warnedPrecision = true;
+ console.warn('CBOR 64-bit integer array values may lose precision. No further warnings.');
+ }
+}
+
+/**
+ * Unpacks 64-bit unsigned integer from byte array.
+ * @param {Uint8Array} bytes
+*/
+function decodeUint64LE(bytes) {
+ warnPrecision();
+
+ var byteLen = bytes.byteLength;
+ var offset = bytes.byteOffset;
+ var arrLen = byteLen / 8;
+
+ var buffer = bytes.buffer.slice(offset, offset + byteLen);
+ var uint32View = new Uint32Array(buffer);
+
+ var arr = new Array(arrLen);
+ for (var i = 0; i < arrLen; i++) {
+ var si = i * 2;
+ var lo = uint32View[si];
+ var hi = uint32View[si+1];
+ arr[i] = lo + UPPER32 * hi;
+ }
+
+ return arr;
+}
+
+/**
+ * Unpacks 64-bit signed integer from byte array.
+ * @param {Uint8Array} bytes
+*/
+function decodeInt64LE(bytes) {
+ warnPrecision();
+
+ var byteLen = bytes.byteLength;
+ var offset = bytes.byteOffset;
+ var arrLen = byteLen / 8;
+
+ var buffer = bytes.buffer.slice(offset, offset + byteLen);
+ var uint32View = new Uint32Array(buffer);
+ var int32View = new Int32Array(buffer);
+
+ var arr = new Array(arrLen);
+ for (var i = 0; i < arrLen; i++) {
+ var si = i * 2;
+ var lo = uint32View[si];
+ var hi = int32View[si+1];
+ arr[i] = lo + UPPER32 * hi;
+ }
+
+ return arr;
+}
+
+/**
+ * Unpacks typed array from byte array.
+ * @param {Uint8Array} bytes
+ * @param {type} ArrayType - desired output array type
+*/
+function decodeNativeArray(bytes, ArrayType) {
+ var byteLen = bytes.byteLength;
+ var offset = bytes.byteOffset;
+ var buffer = bytes.buffer.slice(offset, offset + byteLen);
+ return new ArrayType(buffer);
+}
+
+/**
+ * Support a subset of draft CBOR typed array tags:
+ *
+ * Only support little-endian tags for now.
+ */
+var nativeArrayTypes = {
+ 64: Uint8Array,
+ 69: Uint16Array,
+ 70: Uint32Array,
+ 72: Int8Array,
+ 77: Int16Array,
+ 78: Int32Array,
+ 85: Float32Array,
+ 86: Float64Array
+};
+
+/**
+ * We can also decode 64-bit integer arrays, since ROS has these types.
+ */
+var conversionArrayTypes = {
+ 71: decodeUint64LE,
+ 79: decodeInt64LE
+};
+
+/**
+ * Handles CBOR typed array tags during decoding.
+ * @param {Uint8Array} data
+ * @param {Number} tag
+ */
+function cborTypedArrayTagger(data, tag) {
+ if (tag in nativeArrayTypes) {
+ var arrayType = nativeArrayTypes[tag];
+ return decodeNativeArray(data, arrayType);
+ }
+ if (tag in conversionArrayTypes) {
+ return conversionArrayTypes[tag](data);
+ }
+ return data;
+}
+
+if (typeof module !== 'undefined' && module.exports) {
+ module.exports = cborTypedArrayTagger;
+}
+
+},{}],42:[function(require,module,exports){
+module.exports = window.WebSocket;
+
+},{}],43:[function(require,module,exports){
+/* global document */
+module.exports = function Canvas() {
+ return document.createElement('canvas');
+};
+},{}],44:[function(require,module,exports){
+/**
+ * @fileOverview
+ * @author Graeme Yeates - github.com/megawac
+ */
+
+'use strict';
+
+var Canvas = require('canvas');
+var Image = Canvas.Image || window.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;
+
+},{"canvas":43}],45:[function(require,module,exports){
+exports.DOMImplementation = window.DOMImplementation;
+exports.XMLSerializer = window.XMLSerializer;
+exports.DOMParser = window.DOMParser;
+
+},{}]},{},[6]);
diff --git a/roslaunch_editor/www/switch.css b/roslaunch_editor/www/switch.css
new file mode 100644
index 00000000..d0ec8c3f
--- /dev/null
+++ b/roslaunch_editor/www/switch.css
@@ -0,0 +1,57 @@
+.switch {
+ display: inline-block;
+ cursor: pointer;
+ -webkit-tap-highlight-color: transparent;
+}
+
+.switch i {
+ position: relative;
+ display: inline-block;
+ margin-right: .5rem;
+ width: 46px;
+ height: 26px;
+ background-color: #e6e6e6;
+ border-radius: 23px;
+ vertical-align: text-bottom;
+ transition: all 0.3s linear;
+}
+
+.switch i::before {
+ content: "";
+ position: absolute;
+ left: 0;
+ width: 42px;
+ height: 22px;
+ background-color: #fff;
+ border-radius: 11px;
+ transform: translate3d(2px, 2px, 0) scale3d(1, 1, 1);
+ transition: all 0.25s linear;
+}
+
+.switch i::after {
+ content: "";
+ position: absolute;
+ left: 0;
+ width: 22px;
+ height: 22px;
+ background-color: #fff;
+ border-radius: 11px;
+ box-shadow: 0 2px 2px rgba(0, 0, 0, 0.24);
+ transform: translate3d(2px, 2px, 0);
+ transition: all 0.2s ease-in-out;
+}
+
+.switch:active i::after {
+ width: 28px;
+ transform: translate3d(2px, 2px, 0);
+}
+
+.switch:active input:checked + i::after { transform: translate3d(16px, 2px, 0); }
+
+.switch input { display: none; }
+
+.switch input:checked + i { background-color: #4B9DD7; }
+
+.switch input:checked + i::before { transform: translate3d(18px, 2px, 0) scale3d(0, 0, 0); }
+
+.switch input:checked + i::after { transform: translate3d(22px, 2px, 0); }