diff --git a/CMakeLists.txt b/CMakeLists.txt index a59c9bb..7d72a0b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 2.8.3) -project(rosbridge_gui) +project(aescape_lab_ui) ## Find catkin macros and libraries ## if COMPONENTS list like find_package(catkin REQUIRED COMPONENTS xyz) @@ -82,7 +82,7 @@ find_package(catkin REQUIRED COMPONENTS ## DEPENDS: system dependencies of this project that dependent projects also need catkin_package( # INCLUDE_DIRS include -# LIBRARIES rosbridge_gui +# LIBRARIES aescape_lab_ui # CATKIN_DEPENDS roscpp rospy std_msgs # DEPENDS system_lib ) @@ -99,8 +99,8 @@ include_directories( ) ## Declare a cpp library -# add_library(rosbridge_gui -# src/${PROJECT_NAME}/rosbridge_gui.cpp +# add_library(aescape_lab_ui +# src/${PROJECT_NAME}/aescape_lab_ui.cpp # ) ## Declare a cpp executable @@ -108,7 +108,7 @@ include_directories( ## Add cmake target dependencies of the executable/library ## as an example, message headers may need to be generated before nodes -# add_dependencies(rosbridge_gui_node rosbridge_gui_generate_messages_cpp) +# add_dependencies(aescape_lab_ui_node aescape_lab_ui_generate_messages_cpp) ## Specify libraries to link a library or executable target against @@ -127,7 +127,7 @@ include_directories( # ) ## Mark executables and/or libraries for installation -# install(TARGETS rosbridge_gui rosbridge_gui_node +# install(TARGETS aescape_lab_ui aescape_lab_ui_node # ARCHIVE DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION} # LIBRARY DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION} # RUNTIME DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION} @@ -152,7 +152,7 @@ include_directories( ############# ## Add gtest based cpp test target and link libraries -# catkin_add_gtest(${PROJECT_NAME}-test test/test_rosbridge_gui.cpp) +# catkin_add_gtest(${PROJECT_NAME}-test test/test_aescape_lab_ui.cpp) # if(TARGET ${PROJECT_NAME}-test) # target_link_libraries(${PROJECT_NAME}-test ${PROJECT_NAME}) # endif() diff --git a/README.md b/README.md index 14d65d4..006a1fd 100644 --- a/README.md +++ b/README.md @@ -1,51 +1,19 @@ -# ROSBRIDGE GUI +# Aescape Lab UI -The purpose of this project is an example of how to write a web page that can be accessed by any device on the same network as the host computer by accessing the IP of that host computer. +## To install +``` +sudo apt install npm -y +sudo npm install http-server -g +sudo ln -s /usr/bin/nodejs /usr/bin/node +``` -The best way to contribute to this project is to make a new brach from the master branch called the name of the robot that it is used for. This will also alow maximum colaberation between labs. This can be accomplished by going to the tabs Commits->Branches then select "New Branch" - -### Quick Start Guide - -If you are connecting to a remote computer that is running the roscore, you need to run the following lines in the terminal: - - sudo route add 192.168.2.1 gw - export ROS_MASTER_URI=http://:11311 - export ROS_IP= - -Make sure you have the web_video_server installed: - - sudo apt-get install ros-indigo-web-video-server - -Make sure you have ROSBridge installed: - - sudo apt-get install ros-indigo-rosbridge-suite - -Then run the bash file in the working directory by running the following in the terminal. - - . launch.bash - -#### Just display the webpage - -If you just want to test the webpage, navigate to the working directory in the terminal and type - - python -m SimpleHTTPServer - -Then open http://localhost:8000/. or yourIPAddress:8000 - -# Making Changes - -Feel free to clone this project, make a new branch and use it as a base for your own GUI. - -You can easily customise the "Data Display" pannel by changing the HTML "data\_dsisplay\data\_display.html" file. - -All ROS related Javascript should go in the "data\_display/js/ros\_scripts.js" file. -All functions that are used to update the GUI should go in the "data\_display/js/update_guis.js" file. - -DO NOT MAKE CHANGES OUTISDE OF THIS DIECTORY AS IT WILL CAUSE MERG PROBLEMS. - -By using ROSBridge, this GUI will attempt to display relevant information over the web for any device. - -# Screen Shots - -Screen shots are in the root working directory +## To run +Set your master to be what you want - default is `phoebe`. +Then run +``` +roslaunch aescape_lab_ui lab_ui.launch +``` +## To access +Use Hamachi network: +http://titan.aescape.co:8000 \ No newline at end of file diff --git a/audio/beep-04.mp3 b/audio/beep-04.mp3 deleted file mode 100644 index a71eb4d..0000000 Binary files a/audio/beep-04.mp3 and /dev/null differ diff --git a/audio/beep-07.wav b/audio/beep-07.wav deleted file mode 100644 index 015e1f6..0000000 Binary files a/audio/beep-07.wav and /dev/null differ diff --git a/backend/js/ros_scripts.js b/backend/js/ros_scripts.js index 4b05717..477b52a 100644 --- a/backend/js/ros_scripts.js +++ b/backend/js/ros_scripts.js @@ -7,7 +7,32 @@ ros.recording = false; ros.connected = false; // ros.connectionName = 'ws://192.168.1.105:9090'; -ros.connectionName = 'ws://localhost:9090'; + +function getQueryVariable(variable) +{ + var query = window.location.search.substring(1); + var vars = query.split("&"); + for (var i=0;i -1) - { - // We know that we have already found that topic so we don't need to remove it - } - else - { - // That topic is no longer being published so we need ro remove it - ros.topics.splice(topic,1); - } - } - - for (var topic =0; topic < fresh_topics.length; topic ++ ) - { - if (ros.topics.indexOfTopic(fresh_topics[topic]) > -1) - { - // We know that we have already found that topic so we don't need to re-add it - } - else - { - // We do not have that topic so we need to add it - // create the new topic - var new_topic = new Topic(fresh_topics[topic]); - // make sure to put in in the list in the order - ros.topics.push(new_topic); - - } - } - - ros.topics.sort(function(a, b) { - if ( a.name < b.name ) - return -1; - if ( a.name > b.name ) - return 1; - return 0; - }) - -}; - - -ros.getTopicsList = function() -{ - // var topicList = []; - return ros.topics; - -} - - // attept to connect to the ros master from the IP given orgrab it from the form ros.attemptConnection = function(ipAddress) { + ros.close(); if( typeof ipAddress !== "undefined") { ros.connectionName = ipAddress; @@ -184,6 +154,7 @@ ros.attemptConnection = function(ipAddress) } console.log('Connection = ' + ros.connectionName); ros.connect(ros.connectionName); + getMasterName(); } function toggleRecording() @@ -210,3 +181,22 @@ function toggleRecording() } } } + + +function getMasterName() +{ + var service = new ROSLIB.Service({ + ros : ros, + name : '/rosapi/service_host', + serviceType : 'rosapi/ServiceHost' + }); + + var request = new ROSLIB.ServiceRequest({ + service: '/rosout/get_loggers' // Only the master hosts this service. + }); + + service.callService(request, function(result) { + console.log('Master Name = ' + result.host); + document.getElementById("MasterName").innerHTML = result.host; + }); +} diff --git a/backend/js/update_guis.js b/backend/js/update_guis.js index 0764ada..aa4f1e4 100644 --- a/backend/js/update_guis.js +++ b/backend/js/update_guis.js @@ -1,99 +1,27 @@ -$("#myImage").click ( function (evt) { - - var jThis = $(this); - var offsetFromParent = jThis.position (); - var topThickness = (jThis.outerHeight(true) - jThis.height() ) / 2; - var leftThickness = (jThis.outerWidth (true) - jThis.width () ) / 2; - - //--- (x,y) coordinates of the mouse click relative to the image. - var x = evt.pageX - offsetFromParent.left - leftThickness; - var y = evt.pageY - offsetFromParent.top - topThickness; - - ReportDims (); - $('#rez').append ('

User clicked at: ' + x + ', ' + y + ' (x,y).

') -} ); - - -function ReportDims () { - w = $("#myImage").width (); - h = $("#myImage").height (); - $('#rez').text ('The image is ' + w + ' by ' + h + ' (w by h).'); -} - -ReportDims (); - -function updateVoltage(voltage) -{ - - var voltage_min = 9.5; - var voltage_max = 12.5; - var voltage_range = voltage_max - voltage_min; - - var voltage_percentage = (voltage - voltage_min) / voltage_range * 100; - - var voltage_string = ""; - voltage_string = voltage_string.concat(voltage_percentage); - voltage_string = voltage_string.substring(0,4); - voltage_string = voltage_string.concat('%'); - - document.getElementById("VoltageDisplay").style.width = voltage_string; - document.getElementById("VoltageDisplay").innerHTML = voltage_string; - - if (voltage_percentage < 50 && voltage_percentage > 25 ) { - document.getElementById("VoltageDisplay").className = "progress-bar progress-bar-warning" - } - else if ( voltage_percentage <= 25) - { - document.getElementById("VoltageDisplay").className = "progress-bar progress-bar-danger" - var sound = document.getElementById("audio"); - sound.play() - console.log("Pay Sound") - } - else - { - document.getElementById("VoltageDisplay").className = "progress-bar progress-bar-success" - } -}; - function updateTopicsGUI() { - ros.getTopics(ros.buildTopicList); + ros.getTopics(function(result) + { + ros.topics = result; + }); - var topics = ros.getTopicsList(); + var topics = ros.topics; if(topics != null){ var innerHTML = ""; for (var i = 0; i < topics.length; i++ ) { - innerHTML = innerHTML.concat(generateTopicCheckbox(topics[i])); + innerHTML = innerHTML.concat(topics[i]); + innerHTML = innerHTML.concat("
"); } document.getElementById("ROSTopics").innerHTML = innerHTML; } }; -function generateTopicCheckbox(topic) -{ - var str = "
"; - var checked_str = "unchecked"; - if (topic.bag == true) - { - checked_str = "checked" - } - str =str.concat("
"); - return str; -}; - -function toggleToBag(topic_name) -{ - // if($.inArray(topic_name, ros.topics)) - ros.topics[ros.topics.indexOfTopic(topic_name)].bag = !ros.topics[ros.topics.indexOfTopic(topic_name)].bag ; -} function updateNodesGUI() { @@ -113,19 +41,18 @@ function updateNodesGUI() { innerHTML = innerHTML.concat(nodes[i]); innerHTML = innerHTML.concat("
"); - } document.getElementById("ROSNodes").innerHTML = innerHTML; } }; -window.setInterval(function(){ +// window.setInterval(function(){ - updateTopicsGUI(); - updateNodesGUI(); +// updateTopicsGUI(); +// updateNodesGUI(); -}, 500); +// }, 5000); function validateForm() { diff --git a/coordinator/coordinator.html b/coordinator/coordinator.html new file mode 100644 index 0000000..7a14021 --- /dev/null +++ b/coordinator/coordinator.html @@ -0,0 +1,186 @@ + +
+
+ Select Operation Mode +
+
+
+ + + + + +
+
+ Safety Monitor Status: +
+

Running

+
+
+

Stopped

+
+
+ +
+
+
+ + +
+
+
+ Franka Arm Status +
+
+
+ Current Robot Status: + +
+

Other

+
+
+

Idle

+
+
+

Move

+
+
+

Guiding

+
+
+

Reflex

+
+
+

User Stopped

+
+
+

Automatic Error Recovery

+
+
+ +
+
+
+
+ Robotiq +
+
+
+ Current Robotiq Values: +
+

X: 0.0

+
+
+

Y: 0.0

+
+
+

Z: 0.0

+
+
+
+ +
+
+
+
+
+ Teaching Mode Operations +
+
+ + + + + + +
+
+ +
+
+ Execution Mode Operations +
+ + +
+ + + + + + Last Bag Playing: + + None + +
+ + + +
+
+ Bagfile name must not start with "/" +
+ + + + +
+
+
+ + +
+ +
+ + \ No newline at end of file diff --git a/coordinator/js/ros_scripts.js b/coordinator/js/ros_scripts.js new file mode 100644 index 0000000..a12e2a8 --- /dev/null +++ b/coordinator/js/ros_scripts.js @@ -0,0 +1,261 @@ + + + +/////////////////////////////////////////////////////////////////////////////////// +// Publishers +/////////////////////////////////////////////////////////////////////////////////// + + +//////////////////////////////////////////////////////////////// +// Topics +//////////////////////////////////////////////////////////////// + + + +//////////////////////////////////////////////////////////////// +// Subscribers +//////////////////////////////////////////////////////////////// + +// Operation Mode +var modeStatus = new ROSLIB.Topic({ + ros : ros, + name : '/aescape/mode/status', + messageType : 'std_msgs/String', + throttle_rate : 500 // 2Hz +}); + +modeStatus.subscribe(function(message) { + document.getElementById("stoppedModeButton").className = "btn btn-secondary" + document.getElementById("standbyModeButton").className = "btn btn-primary" + document.getElementById("readyModeButton").className = "btn btn-primary" + document.getElementById("teachingModeButton").className = "btn btn-primary" + document.getElementById("executionModeButton").className = "btn btn-primary" + + document.getElementById("recordingStartButton").disabled = true + document.getElementById("executionStartButton").disabled = true + + + + if (message.data == "stopped") + { + document.getElementById("stoppedModeButton").className = "btn btn-warning" + } + else if (message.data == "standby") + { + document.getElementById("standbyModeButton").className = "btn btn-primary btn-success" + } + else if (message.data == "teach") + { + document.getElementById("teachingModeButton").className = "btn btn-primary btn-success" + } + else if (message.data == "execution") + { + document.getElementById("executionModeButton").className = "btn btn-primary btn-success" + } + else if (message.data == "ready") + { + document.getElementById("readyModeButton").className = "btn btn-primary btn-success" + document.getElementById("recordingStartButton").disabled = false + document.getElementById("executionStartButton").disabled = false + } +}); + +// Safety Status +var safetyStatusTopic = new ROSLIB.Topic({ + ros : ros, + name : '/aescape/mode/safety_status', + messageType : 'std_msgs/String', + throttle_rate : 500 // 2Hz +}); + +safetyStatusTopic.subscribe(function(message) { + document.getElementById("safetyRunning").className = 'label label-default'; + document.getElementById("safetyStopped").className = 'label label-default'; + + if (message.data == "stopped") + { + document.getElementById("safetyStopped").className = 'label label-danger'; + } + else if (message.data == "running") + { + document.getElementById("safetyRunning").className = 'label label-success'; + } +}); + + +// Recording Bag +var recordingBagTopic = new ROSLIB.Topic({ + ros : ros, + name : '/aescape/bags/recording_status', + messageType : 'std_msgs/String' +}); + +recordingBagTopic.subscribe(function(message) { + if (message.data == "stopped") + { + document.getElementById("RecordingStatusLabel").innerHTML = 'Not Running'; + document.getElementById("RecordingStatusLabel").className = 'label label-warning'; + } + else if (message.data == "running") + { + document.getElementById("RecordingStatusLabel").innerHTML = 'RUNNING!'; + document.getElementById("RecordingStatusLabel").className = 'label label-success'; + } +}); + +// Executing Bag +var executingBagTopic = new ROSLIB.Topic({ + ros : ros, + name : '/aescape/bags/execution_status', + messageType : 'std_msgs/String' +}); + +executingBagTopic.subscribe(function(message) { + if (message.data == "stopped") + { + document.getElementById("ExecutionStatusLabel").innerHTML = 'Not Running'; + document.getElementById("ExecutionStatusLabel").className = 'label label-warning'; + } + else if (message.data == "running") + { + document.getElementById("ExecutionStatusLabel").innerHTML = 'RUNNING!'; + document.getElementById("ExecutionStatusLabel").className = 'label label-success'; + } +}); + + +// Last Bag +var bagPlayingTopic = new ROSLIB.Topic({ + ros : ros, + name : '/aescape/bags/last_played', + messageType : 'std_msgs/String' +}); + +bagPlayingTopic.subscribe(function(message) { + document.getElementById("lastbagText").innerHTML = message.data +}); + +// FrankaState +var frankaStatus = new ROSLIB.Topic({ + ros : ros, + name : '/franka_state_controller/franka_states', + messageType : 'franka_msgs/FrankaState', + throttle_rate : 500 // 2Hz +}); + +frankaStatus.subscribe(function(message) { + document.getElementById("frankaModeOther").className = "label label-default" + document.getElementById("frankaModeIdle").className = "label label-default" + document.getElementById("frankaModeMove").className = "label label-default" + document.getElementById("frankaModeGuiding").className = "label label-default" + document.getElementById("frankaModeReflex").className = "label label-default" + document.getElementById("frankaModeUserStopped").className = "label label-default" + document.getElementById("frankaModeErrorRecovery").className = "label label-default" + document.getElementById("fixFrankaButton").className = "btn btn-primary" + + if (message.robot_mode == 0) + { + document.getElementById("frankaModeOther").className = "label label-warning" + document.getElementById("fixFrankaButton").className = "btn btn-danger" + } + else if (message.robot_mode == 1) + { + document.getElementById("frankaModeIdle").className = "label label-warning" + document.getElementById("fixFrankaButton").className = "btn btn-danger" + } + else if (message.robot_mode == 2) + { + document.getElementById("frankaModeMove").className = "label label-warning" + document.getElementById("fixFrankaButton").className = "btn btn-success" + } + else if (message.robot_mode == 3) + { + document.getElementById("frankaModeGuiding").className = "label label-warning" + document.getElementById("fixFrankaButton").className = "btn btn-default" + } + else if (message.robot_mode == 4) + { + document.getElementById("frankaModeReflex").className = "label label-warning" + document.getElementById("fixFrankaButton").className = "btn btn-danger" + + } + else if (message.robot_mode == 5) + { + document.getElementById("frankaModeUserStopped").className = "label label-warning" + document.getElementById("fixFrankaButton").className = "btn btn-default" + } + else if (message.robot_mode == 6) + { + document.getElementById("frankaModeErrorRecovery").className = "label label-warning" + document.getElementById("fixFrankaButton").className = "btn btn-default" + } +}); + + +// Robotiq Data +var robotiqDataTopic = new ROSLIB.Topic({ + ros : ros, + name : '/robotiq_ft_wrench', + messageType : 'geometry_msgs/WrenchStamped', + throttle_rate : 500 // 2Hz +}); + +robotiqDataTopic.subscribe(function(message) { + var force = message.wrench.force + document.getElementById("robotiqX").innerHTML = force.x.toFixed(1) + document.getElementById("robotiqY").innerHTML = force.y.toFixed(1) + document.getElementById("robotiqZ").innerHTML = force.z.toFixed(1) + + if ((Math.abs(force.x) > 0.5) || (Math.abs(force.y) > 0.5) || (Math.abs(force.z) > 0.5)) + { + document.getElementById("calibrateButton").className = "btn btn-danger" + } else { + document.getElementById("calibrateButton").className = "btn btn-default" + } +}); + +//////////////////////////////////////////////////////////////// +// Services +//////////////////////////////////////////////////////////////// + + +function triggerService(serviceName) +{ + var service = new ROSLIB.Service({ + ros : ros, + name : serviceName, + serviceType : 'std_srvs/Trigger' + }); + + var request = new ROSLIB.ServiceRequest({}); + + service.callService(request, function(result) { + console.log('Result for service call on ' + + serviceName + + ': ' + + result.sum); + }); +} + + +function triggerMessageService(serviceName, textInput) +{ + var text = document.getElementById(textInput).value + + var service = new ROSLIB.Service({ + ros : ros, + name : serviceName, + serviceType : 'demobot.TriggerMessage' + }); + + var request = new ROSLIB.ServiceRequest({ + message : text + }); + + service.callService(request, function(result) { + console.log('Result for service call on ' + + serviceName + + ': ' + + result.sum); + }); +} \ No newline at end of file diff --git a/coordinator/js/update_guis.js b/coordinator/js/update_guis.js new file mode 100644 index 0000000..e69de29 diff --git a/data/bagfile_2015-07-01-09-17-42.bag.active b/data/bagfile_2015-07-01-09-17-42.bag.active deleted file mode 100644 index 347c1e6..0000000 Binary files a/data/bagfile_2015-07-01-09-17-42.bag.active and /dev/null differ diff --git a/data/test_bag_2015-07-01-12-38-32.bag b/data/test_bag_2015-07-01-12-38-32.bag deleted file mode 100644 index 4ca6e05..0000000 Binary files a/data/test_bag_2015-07-01-12-38-32.bag and /dev/null differ diff --git a/data_display/data_display.html b/data_display/data_display.html index 4cba54d..b1396b2 100644 --- a/data_display/data_display.html +++ b/data_display/data_display.html @@ -42,61 +42,3 @@
- - - - - - - - - -
- -
- Example Pannel Heading -
- - -
- - -
-
- -
-
-
-
- No data recieved yet.
-
-
-
-
-
- - - - - -
-
- Camera Image -
-
-
- No Camera Image -
-
-
- - diff --git a/data_display/js/update_guis.js b/data_display/js/update_guis.js index 9de9459..e69de29 100644 --- a/data_display/js/update_guis.js +++ b/data_display/js/update_guis.js @@ -1,44 +0,0 @@ -function sendRosMessageExample() -{ - publishStringExample(String(document.getElementById("sendRosMessageExample").value)) -} - -function update_sendRosMessageExample(msg) -{ - document.getElementById("recievedMessageDiv").innerHTML = msg -} - -function update_ProgressBar(value) -{ - -// This would be a great way of getting a float or int from a rosTopic and displaying it. -// I would suggest storing the data as a data field in the ros_scripts then updating it every 500 or 1000 ms rather than updating the gui every time you recieve a message - var num_string = ""; - num_string = num_string.concat(value); - num_string = num_string.substring(0,4); - num_string = num_string.concat("%"); - - doc = document.getElementById("progressBar1"); - - doc.style.width = num_string; - doc.innerHTML = num_string; - - if (value < 50 && value > 25 ) { - doc.className = "progress-bar progress-bar-warning"; - } - else if ( value <= 25) - { - doc.className = "progress-bar progress-bar-danger"; - } - else - { - doc.className = "progress-bar progress-bar-success"; - } -} - - -window.setInterval(function(){ - - update_ProgressBar(Math.random() * 100); - -}, 1000); diff --git a/data_screenshot.jpg b/data_screenshot.jpg deleted file mode 100644 index 55b063f..0000000 Binary files a/data_screenshot.jpg and /dev/null differ diff --git a/include/js/roslib.js b/include/js/roslib.js index 645e59f..b7ce072 100644 --- a/include/js/roslib.js +++ b/include/js/roslib.js @@ -1,43 +1,1564 @@ -(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o + * + * 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); -function ToObject(val) { - if (val == null) { +},{}],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){ +var bundleFn = arguments[3]; +var sources = arguments[4]; +var cache = arguments[5]; + +var stringify = JSON.stringify; + +module.exports = function (fn, options) { + var wkey; + var cacheKeys = Object.keys(cache); + + for (var i = 0, l = cacheKeys.length; i < l; i++) { + var key = cacheKeys[i]; + var exp = cache[key].exports; + // Using babel as a transpiler to use esmodule, the export will always + // be an object with the default export as a property of it. To ensure + // the existing api and babel esmodule exports are both supported we + // check for both + if (exp === fn || exp && exp.default === fn) { + wkey = key; + break; + } + } + + if (!wkey) { + wkey = Math.floor(Math.pow(16, 8) * Math.random()).toString(16); + var wcache = {}; + for (var i = 0, l = cacheKeys.length; i < l; i++) { + var key = cacheKeys[i]; + wcache[key] = key; + } + sources[wkey] = [ + 'function(require,module,exports){' + fn + '(self); }', + wcache + ]; + } + var skey = Math.floor(Math.pow(16, 8) * Math.random()).toString(16); + + var scache = {}; scache[wkey] = wkey; + sources[skey] = [ + 'function(require,module,exports){' + + // try to call default if defined to also support babel esmodule exports + 'var f = require(' + stringify(wkey) + ');' + + '(f.default ? f.default : f)(self);' + + '}', + scache + ]; + + var workerSources = {}; + resolveSources(skey); + + function resolveSources(key) { + workerSources[key] = true; + + for (var depPath in sources[key][1]) { + var depKey = sources[key][1][depPath]; + if (!workerSources[depKey]) { + resolveSources(depKey); + } + } + } + + var src = '(' + bundleFn + ')({' + + Object.keys(workerSources).map(function (key) { + return stringify(key) + ':[' + + sources[key][0] + + ',' + stringify(sources[key][1]) + ']' + ; + }).join(',') + + '},{},[' + stringify(skey) + '])' + ; + + var URL = window.URL || window.webkitURL || window.mozURL || window.msURL; + + var blob = new Blob([src], { type: 'text/javascript' }); + if (options && options.bare) { return blob; } + var workerUrl = URL.createObjectURL(blob); + var worker = new Worker(workerUrl); + worker.objectURL = workerUrl; + return worker; +}; + +},{}],6:[function(require,module,exports){ /** + * @fileOverview * @author Russell Toris - rctoris@wpi.edu */ +/** + * If you use roslib in a browser, all the classes will be exported to a global variable called ROSLIB. + * + * If you use nodejs, this is the variable you get when you require('roslib') + */ var ROSLIB = this.ROSLIB || { - REVISION : '0.15.0' + REVISION : '1.0.1' }; var assign = require('object-assign'); @@ -55,18 +1576,19 @@ assign(ROSLIB, require('./urdf')); module.exports = ROSLIB; -},{"./actionlib":8,"./core":17,"./math":22,"./tf":25,"./urdf":37,"object-assign":1}],4:[function(require,module,exports){ +},{"./actionlib":12,"./core":21,"./math":26,"./tf":29,"./urdf":41,"object-assign":3}],7:[function(require,module,exports){ (function (global){ global.ROSLIB = require('./RosLib'); }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) -},{"./RosLib":3}],5:[function(require,module,exports){ +},{"./RosLib":6}],8:[function(require,module,exports){ /** + * @fileOverview * @author Russell Toris - rctoris@wpi.edu */ var Topic = require('../core/Topic'); var Message = require('../core/Message'); -var EventEmitter2 = require('./../util/shim/EventEmitter2.js').EventEmitter2; +var EventEmitter2 = require('eventemitter2').EventEmitter2; /** * An actionlib action client. @@ -91,25 +1613,28 @@ function ActionClient(options) { 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 - var feedbackListener = new Topic({ + this.feedbackListener = new Topic({ ros : this.ros, name : this.serverName + '/feedback', messageType : this.actionName + 'Feedback' }); - var statusListener = new Topic({ + this.statusListener = new Topic({ ros : this.ros, name : this.serverName + '/status', messageType : 'actionlib_msgs/GoalStatusArray' }); - var resultListener = new Topic({ + this.resultListener = new Topic({ ros : this.ros, name : this.serverName + '/result', messageType : this.actionName + 'Result' @@ -132,34 +1657,40 @@ function ActionClient(options) { this.cancelTopic.advertise(); // subscribe to the status topic - 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); - } + 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 - 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); - } - }); + 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 - resultListener.subscribe(function(resultMessage) { - var goal = that.goals[resultMessage.status.goal_id.id]; + 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 (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) { @@ -181,14 +1712,116 @@ ActionClient.prototype.cancel = function() { this.cancelTopic.publish(cancelMessage); }; -module.exports = ActionClient; -},{"../core/Message":9,"../core/Topic":16,"./../util/shim/EventEmitter2.js":38}],6:[function(require,module,exports){ /** + * 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":13,"../core/Topic":20,"eventemitter2":2}],9:[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":13,"../core/Topic":20,"eventemitter2":2}],10:[function(require,module,exports){ +/** + * @fileOverview * @author Russell Toris - rctoris@wpi.edu */ var Message = require('../core/Message'); -var EventEmitter2 = require('./../util/shim/EventEmitter2.js').EventEmitter2; +var EventEmitter2 = require('eventemitter2').EventEmitter2; /** * An actionlib goal goal is associated with an action server. @@ -271,14 +1904,15 @@ Goal.prototype.cancel = function() { }; module.exports = Goal; -},{"../core/Message":9,"./../util/shim/EventEmitter2.js":38}],7:[function(require,module,exports){ +},{"../core/Message":13,"eventemitter2":2}],11:[function(require,module,exports){ /** + * @fileOverview * @author Laura Lindzey - lindzey@gmail.com */ var Topic = require('../core/Topic'); var Message = require('../core/Message'); -var EventEmitter2 = require('./../util/shim/EventEmitter2.js').EventEmitter2; +var EventEmitter2 = require('eventemitter2').EventEmitter2; /** * An actionlib action server client. @@ -479,19 +2113,22 @@ SimpleActionServer.prototype.setPreempted = function() { }; module.exports = SimpleActionServer; -},{"../core/Message":9,"../core/Topic":16,"./../util/shim/EventEmitter2.js":38}],8:[function(require,module,exports){ +},{"../core/Message":13,"../core/Topic":20,"eventemitter2":2}],12:[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":11,"../mixin":23,"./ActionClient":5,"./Goal":6,"./SimpleActionServer":7}],9:[function(require,module,exports){ + +},{"../core/Ros":15,"../mixin":27,"./ActionClient":8,"./ActionListener":9,"./Goal":10,"./SimpleActionServer":11}],13:[function(require,module,exports){ /** + * @fileoverview * @author Brandon Alexander - baalexander@gmail.com */ @@ -508,8 +2145,9 @@ function Message(values) { } module.exports = Message; -},{"object-assign":1}],10:[function(require,module,exports){ +},{"object-assign":3}],14:[function(require,module,exports){ /** + * @fileoverview * @author Brandon Alexander - baalexander@gmail.com */ @@ -591,19 +2229,21 @@ Param.prototype.delete = function(callback) { }; module.exports = Param; -},{"./Service":12,"./ServiceRequest":13}],11:[function(require,module,exports){ +},{"./Service":16,"./ServiceRequest":17}],15:[function(require,module,exports){ /** + * @fileoverview * @author Brandon Alexander - baalexander@gmail.com */ -var WebSocket = require('./../util/shim/WebSocket.js'); +var WebSocket = require('ws'); +var WorkerSocket = require('../util/workerSocket'); var socketAdapter = require('./SocketAdapter.js'); var Service = require('./Service'); var ServiceRequest = require('./ServiceRequest'); var assign = require('object-assign'); -var EventEmitter2 = require('./../util/shim/EventEmitter2.js').EventEmitter2; +var EventEmitter2 = require('eventemitter2').EventEmitter2; /** * Manages connection to the server and all interactions with ROS. @@ -616,14 +2256,19 @@ var EventEmitter2 = require('./../util/shim/EventEmitter2.js').EventEmitter2; * * - a service response came from rosbridge with the given ID * * @constructor - * @param options - possible keys include: - * * url (optional) - the WebSocket URL for rosbridge (can be specified later with `connect`) + * @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', 'workersocket' (default), 'socket.io' or RTCPeerConnection instance controlling how the connection is created in `connect`. + * * transportOptions (optional) - the options to use use when creating a connection. Currently only used if `transportLibrary` is RTCPeerConnection. */ function Ros(options) { options = options || {}; this.socket = null; this.idCounter = 0; this.isConnected = false; + this.transportLibrary = options.transportLibrary || 'websocket'; + this.transportOptions = options.transportOptions || {}; if (typeof options.groovyCompatibility === 'undefined') { this.groovyCompatibility = true; @@ -646,10 +2291,29 @@ Ros.prototype.__proto__ = EventEmitter2.prototype; /** * Connect to the specified WebSocket. * - * @param url - WebSocket URL for Rosbridge + * @param url - WebSocket URL or RTCDataChannel label for Rosbridge */ Ros.prototype.connect = function(url) { - this.socket = assign(new WebSocket(url), socketAdapter(this)); + if (this.transportLibrary === 'socket.io') { + this.socket = assign(io(url, {'force new connection': true}), socketAdapter(this)); + this.socket.on('connect', this.socket.onopen); + this.socket.on('data', this.socket.onmessage); + this.socket.on('close', this.socket.onclose); + this.socket.on('error', this.socket.onerror); + } else if (this.transportLibrary.constructor.name === 'RTCPeerConnection') { + this.socket = assign(this.transportLibrary.createDataChannel(url, this.transportOptions), socketAdapter(this)); + } else if (this.transportLibrary === 'websocket') { + if (!this.socket || this.socket.readyState === WebSocket.CLOSED) { + var sock = new WebSocket(url); + sock.binaryType = 'arraybuffer'; + this.socket = assign(sock, socketAdapter(this)); + } + } else if (this.transportLibrary === 'workersocket') { + this.socket = assign(new WorkerSocket(url), socketAdapter(this)); + } else { + throw 'Unknown transportLibrary: ' + this.transportLibrary.toString(); + } + }; /** @@ -695,13 +2359,64 @@ Ros.prototype.authenticate = function(mac, client, dest, rand, t, level, end) { 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() { - that.socket.send(messageJson); + emitter(messageJson); }); } else { - that.socket.send(messageJson); + 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); + }); } }; @@ -711,7 +2426,7 @@ Ros.prototype.callOnConnection = function(message) { * @param callback function with params: * * topics - Array of topic names */ -Ros.prototype.getTopics = function(callback) { +Ros.prototype.getTopics = function(callback, failedCallback) { var topicsClient = new Service({ ros : this, name : '/rosapi/topics', @@ -719,10 +2434,20 @@ Ros.prototype.getTopics = function(callback) { }); var request = new ServiceRequest(); - - topicsClient.callService(request, function(result) { - callback(result.topics); - }); + if (typeof failedCallback === 'function'){ + topicsClient.callService(request, + function(result) { + callback(result); + }, + function(message){ + failedCallback(message); + } + ); + }else{ + topicsClient.callService(request, function(result) { + callback(result); + }); + } }; /** @@ -732,7 +2457,7 @@ Ros.prototype.getTopics = function(callback) { * @param callback function with params: * * topics - Array of topic names */ -Ros.prototype.getTopicsForType = function(topicType, callback) { +Ros.prototype.getTopicsForType = function(topicType, callback, failedCallback) { var topicsForTypeClient = new Service({ ros : this, name : '/rosapi/topics_for_type', @@ -742,10 +2467,20 @@ Ros.prototype.getTopicsForType = function(topicType, callback) { var request = new ServiceRequest({ type: topicType }); - - topicsForTypeClient.callService(request, function(result) { - callback(result.topics); - }); + 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); + }); + } }; /** @@ -754,7 +2489,7 @@ Ros.prototype.getTopicsForType = function(topicType, callback) { * @param callback - function with the following params: * * services - array of service names */ -Ros.prototype.getServices = function(callback) { +Ros.prototype.getServices = function(callback, failedCallback) { var servicesClient = new Service({ ros : this, name : '/rosapi/services', @@ -762,10 +2497,20 @@ Ros.prototype.getServices = function(callback) { }); var request = new ServiceRequest(); - - servicesClient.callService(request, function(result) { - callback(result.services); - }); + 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); + }); + } }; /** @@ -775,7 +2520,7 @@ Ros.prototype.getServices = function(callback) { * @param callback function with params: * * topics - Array of service names */ -Ros.prototype.getServicesForType = function(serviceType, callback) { +Ros.prototype.getServicesForType = function(serviceType, callback, failedCallback) { var servicesForTypeClient = new Service({ ros : this, name : '/rosapi/services_for_type', @@ -785,10 +2530,86 @@ Ros.prototype.getServicesForType = function(serviceType, callback) { 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); + }); + } +}; - 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); + }); + } }; /** @@ -797,7 +2618,7 @@ Ros.prototype.getServicesForType = function(serviceType, callback) { * @param callback - function with the following params: * * nodes - array of node names */ -Ros.prototype.getNodes = function(callback) { +Ros.prototype.getNodes = function(callback, failedCallback) { var nodesClient = new Service({ ros : this, name : '/rosapi/nodes', @@ -805,10 +2626,55 @@ Ros.prototype.getNodes = function(callback) { }); 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); + }); + } +}; - 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); + }); + } }; /** @@ -817,26 +2683,37 @@ Ros.prototype.getNodes = function(callback) { * @param callback function with params: * * params - array of param names. */ -Ros.prototype.getParams = function(callback) { +Ros.prototype.getParams = function(callback, failedCallback) { var paramsClient = new Service({ ros : this, name : '/rosapi/get_param_names', serviceType : 'rosapi/GetParamNames' }); - var request = new ServiceRequest(); - paramsClient.callService(request, function(result) { - callback(result.names); - }); + 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) { +Ros.prototype.getTopicType = function(topic, callback, failedCallback) { var topicTypeClient = new Service({ ros : this, name : '/rosapi/topic_type', @@ -845,9 +2722,54 @@ Ros.prototype.getTopicType = function(topic, callback) { var request = new ServiceRequest({ topic: topic }); - topicTypeClient.callService(request, function(result) { - callback(result.type); + + 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); + }); + } }; /** @@ -857,7 +2779,7 @@ Ros.prototype.getTopicType = function(topic, callback) { * * details - Array of the message detail * @param message - String of a topic type */ -Ros.prototype.getMessageDetails = function(message, callback) { +Ros.prototype.getMessageDetails = function(message, callback, failedCallback) { var messageDetailClient = new Service({ ros : this, name : '/rosapi/message_details', @@ -866,9 +2788,21 @@ Ros.prototype.getMessageDetails = function(message, callback) { var request = new ServiceRequest({ type: message }); - messageDetailClient.callService(request, function(result) { - callback(result.typedefs); - }); + + 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); + }); + } }; /** @@ -906,7 +2840,6 @@ Ros.prototype.decodeTypeDefs = function(defs) { if (sub) { var subResult = decodeTypeDefsRec(sub, hints); if (arrayLen === -1) { - typeDefDict[fieldName] = subResult; } else { typeDefDict[fieldName] = [subResult]; @@ -919,19 +2852,22 @@ Ros.prototype.decodeTypeDefs = function(defs) { } return typeDefDict; }; - + return decodeTypeDefsRec(defs[0], defs); }; module.exports = Ros; -},{"./../util/shim/EventEmitter2.js":38,"./../util/shim/WebSocket.js":39,"./Service":12,"./ServiceRequest":13,"./SocketAdapter.js":15,"object-assign":1}],12:[function(require,module,exports){ +},{"../util/workerSocket":47,"./Service":16,"./ServiceRequest":17,"./SocketAdapter.js":19,"eventemitter2":2,"object-assign":3,"ws":43}],16:[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. @@ -947,10 +2883,14 @@ function Service(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. + * 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: @@ -959,6 +2899,10 @@ function Service(options) { * * 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) { @@ -977,14 +2921,72 @@ Service.prototype.callService = function(request, callback, failedCallback) { op : 'call_service', id : serviceCallId, service : this.name, + type: this.serviceType, args : request }; this.ros.callOnConnection(call); }; -module.exports = Service; -},{"./ServiceResponse":14}],13:[function(require,module,exports){ /** + * 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":17,"./ServiceResponse":18,"eventemitter2":2}],17:[function(require,module,exports){ +/** + * @fileoverview * @author Brandon Alexander - balexander@willowgarage.com */ @@ -1001,8 +3003,9 @@ function ServiceRequest(values) { } module.exports = ServiceRequest; -},{"object-assign":1}],14:[function(require,module,exports){ +},{"object-assign":3}],18:[function(require,module,exports){ /** + * @fileoverview * @author Brandon Alexander - balexander@willowgarage.com */ @@ -1019,69 +3022,32 @@ function ServiceResponse(values) { } module.exports = ServiceResponse; -},{"object-assign":1}],15:[function(require,module,exports){ -(function (global){ +},{"object-assign":3}],19:[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 Canvas = require('./../util/shim/canvas.js'); -var Image = Canvas.Image || global.Image; -var WebSocket = require('./../util/shim/WebSocket.js'); - -/** - * 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. - * - * @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.data; +var decompressPng = require('../util/decompressPng'); +var CBOR = require('cbor-js'); +var typedArrayTagger = require('../util/cborTypedArrayTags'); +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) { @@ -1089,14 +3055,44 @@ function SocketAdapter(client) { 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; @@ -1107,6 +3103,7 @@ function SocketAdapter(client) { * 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; @@ -1117,6 +3114,7 @@ function SocketAdapter(client) { * 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); @@ -1127,13 +3125,19 @@ function SocketAdapter(client) { * topic, service, or param. * * @param message - the raw JSON message from rosbridge. + * @memberof SocketAdapter */ - onmessage: function onMessage(message) { - var data = JSON.parse(typeof message === 'string' ? message : message.data); - if (data.op === 'png') { - decompressPng(data, handleMessage); + 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 { - handleMessage(data); + var message = JSON.parse(typeof data === 'string' ? data : data.data); + handlePng(message, handleMessage); } } }; @@ -1141,13 +3145,13 @@ function SocketAdapter(client) { module.exports = SocketAdapter; -}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) -},{"./../util/shim/WebSocket.js":39,"./../util/shim/canvas.js":40}],16:[function(require,module,exports){ +},{"../util/cborTypedArrayTags":42,"../util/decompressPng":45,"cbor-js":1}],20:[function(require,module,exports){ /** + * @fileoverview * @author Brandon Alexander - baalexander@gmail.com */ -var EventEmitter2 = require('./../util/shim/EventEmitter2.js').EventEmitter2; +var EventEmitter2 = require('eventemitter2').EventEmitter2; var Message = require('./Message'); /** @@ -1162,11 +3166,12 @@ var Message = require('./Message'); * * 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' + * * 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 || {}; @@ -1179,12 +3184,14 @@ function Topic(options) { 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 !== undefined ? options.reconnect_on_close : true; // Check for valid compression types if (this.compression && this.compression !== 'png' && - this.compression !== 'none') { + 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 @@ -1194,6 +3201,27 @@ function Topic(options) { } 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)); }; @@ -1215,7 +3243,8 @@ Topic.prototype.subscribe = function(callback) { if (this.subscribeId) { return; } this.ros.on(this.name, this._messageCallback); this.subscribeId = 'subscribe:' + this.name + ':' + (++this.ros.idCounter); - this.ros.callOnConnection({ + + this.callForSubscribeAndAdvertise({ op: 'subscribe', id: this.subscribeId, type: this.messageType, @@ -1244,6 +3273,9 @@ Topic.prototype.unsubscribe = function(callback) { 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', @@ -1253,6 +3285,7 @@ Topic.prototype.unsubscribe = function(callback) { this.subscribeId = null; }; + /** * Registers as a publisher for the topic. */ @@ -1261,7 +3294,7 @@ Topic.prototype.advertise = function() { return; } this.advertiseId = 'advertise:' + this.name + ':' + (++this.ros.idCounter); - this.ros.callOnConnection({ + this.callForSubscribeAndAdvertise({ op: 'advertise', id: this.advertiseId, type: this.messageType, @@ -1270,6 +3303,13 @@ Topic.prototype.advertise = function() { queue_size: this.queue_size }); this.isAdvertised = true; + + if(!this.reconnect_on_close) { + var that = this; + this.ros.on('close', function() { + that.isAdvertised = false; + }); + } }; /** @@ -1279,6 +3319,9 @@ 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', @@ -1311,7 +3354,7 @@ Topic.prototype.publish = function(message) { module.exports = Topic; -},{"./../util/shim/EventEmitter2.js":38,"./Message":9}],17:[function(require,module,exports){ +},{"./Message":13,"eventemitter2":2}],21:[function(require,module,exports){ var mixin = require('../mixin'); var core = module.exports = { @@ -1326,8 +3369,9 @@ var core = module.exports = { mixin(core.Ros, ['Param', 'Service', 'Topic'], core); -},{"../mixin":23,"./Message":9,"./Param":10,"./Ros":11,"./Service":12,"./ServiceRequest":13,"./ServiceResponse":14,"./Topic":16}],18:[function(require,module,exports){ +},{"../mixin":27,"./Message":13,"./Param":14,"./Ros":15,"./Service":16,"./ServiceRequest":17,"./ServiceResponse":18,"./Topic":20}],22:[function(require,module,exports){ /** + * @fileoverview * @author David Gossow - dgossow@willowgarage.com */ @@ -1371,9 +3415,36 @@ Pose.prototype.clone = function() { return new Pose(this); }; -module.exports = Pose; -},{"./Quaternion":19,"./Vector3":21}],19:[function(require,module,exports){ /** + * 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":23,"./Vector3":25}],23:[function(require,module,exports){ +/** + * @fileoverview * @author David Gossow - dgossow@willowgarage.com */ @@ -1465,8 +3536,9 @@ Quaternion.prototype.clone = function() { module.exports = Quaternion; -},{}],20:[function(require,module,exports){ +},{}],24:[function(require,module,exports){ /** + * @fileoverview * @author David Gossow - dgossow@willowgarage.com */ @@ -1498,8 +3570,9 @@ Transform.prototype.clone = function() { }; module.exports = Transform; -},{"./Quaternion":19,"./Vector3":21}],21:[function(require,module,exports){ +},{"./Quaternion":23,"./Vector3":25}],25:[function(require,module,exports){ /** + * @fileoverview * @author David Gossow - dgossow@willowgarage.com */ @@ -1566,7 +3639,7 @@ Vector3.prototype.clone = function() { }; module.exports = Vector3; -},{}],22:[function(require,module,exports){ +},{}],26:[function(require,module,exports){ module.exports = { Pose: require('./Pose'), Quaternion: require('./Quaternion'), @@ -1574,7 +3647,7 @@ module.exports = { Vector3: require('./Vector3') }; -},{"./Pose":18,"./Quaternion":19,"./Transform":20,"./Vector3":21}],23:[function(require,module,exports){ +},{"./Pose":22,"./Quaternion":23,"./Transform":24,"./Vector3":25}],27:[function(require,module,exports){ /** * Mixin a feature to the core/Ros prototype. * For example, mixin(Ros, ['Topic'], {Topic: }) @@ -1593,8 +3666,9 @@ module.exports = function(Ros, classes, features) { }); }; -},{}],24:[function(require,module,exports){ +},{}],28:[function(require,module,exports){ /** + * @fileoverview * @author David Gossow - dgossow@willowgarage.com */ @@ -1619,6 +3693,9 @@ var Transform = require('../math/Transform'); * * 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 || {}; @@ -1635,6 +3712,8 @@ function TFClient(options) { secs: secs, nsecs: nsecs }; + this.serverName = options.serverName || '/tf2_web_republisher'; + this.repubServiceName = options.repubServiceName || '/republish_tfs'; this.currentGoal = false; this.currentTopic = false; @@ -1643,13 +3722,15 @@ function TFClient(options) { // Create an Action client this.actionClient = this.ros.ActionClient({ - serverName : '/tf2_web_republisher', - actionName : 'tf2_web_republisher/TFSubscriptionAction' + serverName : this.serverName, + actionName : 'tf2_web_republisher/TFSubscriptionAction', + omitStatus : true, + omitResult : true }); // Create a Service client this.serviceClient = this.ros.Service({ - name: '/republish_tfs', + name: this.repubServiceName, serviceType: 'tf2_web_republisher/RepublishTFs' }); } @@ -1794,9 +3875,19 @@ TFClient.prototype.unsubscribe = function(frameID, callback) { } }; +/** + * Unsubscribe and unadvertise all topics associated with this TFClient. + */ +TFClient.prototype.dispose = function() { + this.actionClient.dispose(); + if (this.currentTopic) { + this.currentTopic.unsubscribe(); + } +}; + module.exports = TFClient; -},{"../actionlib/ActionClient":5,"../actionlib/Goal":6,"../core/Service.js":12,"../core/ServiceRequest.js":13,"../math/Transform":20}],25:[function(require,module,exports){ +},{"../actionlib/ActionClient":8,"../actionlib/Goal":10,"../core/Service.js":16,"../core/ServiceRequest.js":17,"../math/Transform":24}],29:[function(require,module,exports){ var Ros = require('../core/Ros'); var mixin = require('../mixin'); @@ -1805,8 +3896,9 @@ var tf = module.exports = { }; mixin(Ros, ['TFClient'], tf); -},{"../core/Ros":11,"../mixin":23,"./TFClient":24}],26:[function(require,module,exports){ +},{"../core/Ros":15,"../mixin":27,"./TFClient":28}],30:[function(require,module,exports){ /** + * @fileOverview * @author Benjamin Pitzer - ben.pitzer@gmail.com * @author Russell Toris - rctoris@wpi.edu */ @@ -1835,8 +3927,9 @@ function UrdfBox(options) { } module.exports = UrdfBox; -},{"../math/Vector3":21,"./UrdfTypes":35}],27:[function(require,module,exports){ +},{"../math/Vector3":25,"./UrdfTypes":39}],31:[function(require,module,exports){ /** + * @fileOverview * @author Benjamin Pitzer - ben.pitzer@gmail.com * @author Russell Toris - rctoris@wpi.edu */ @@ -1858,8 +3951,9 @@ function UrdfColor(options) { } module.exports = UrdfColor; -},{}],28:[function(require,module,exports){ +},{}],32:[function(require,module,exports){ /** + * @fileOverview * @author Benjamin Pitzer - ben.pitzer@gmail.com * @author Russell Toris - rctoris@wpi.edu */ @@ -1880,11 +3974,16 @@ function UrdfCylinder(options) { } module.exports = UrdfCylinder; -},{"./UrdfTypes":35}],29:[function(require,module,exports){ +},{"./UrdfTypes":39}],33:[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. * @@ -1895,18 +3994,82 @@ module.exports = UrdfCylinder; 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; -},{}],30:[function(require,module,exports){ +},{"../math/Pose":22,"../math/Quaternion":23,"../math/Vector3":25}],34:[function(require,module,exports){ /** + * @fileOverview * @author Benjamin Pitzer - ben.pitzer@gmail.com * @author Russell Toris - rctoris@wpi.edu */ @@ -1933,8 +4096,9 @@ function UrdfLink(options) { } module.exports = UrdfLink; -},{"./UrdfVisual":36}],31:[function(require,module,exports){ +},{"./UrdfVisual":40}],35:[function(require,module,exports){ /** + * @fileOverview * @author Benjamin Pitzer - ben.pitzer@gmail.com * @author Russell Toris - rctoris@wpi.edu */ @@ -1982,8 +4146,9 @@ UrdfMaterial.prototype.assign = function(obj) { module.exports = UrdfMaterial; -},{"./UrdfColor":27,"object-assign":1}],32:[function(require,module,exports){ +},{"./UrdfColor":31,"object-assign":3}],36:[function(require,module,exports){ /** + * @fileOverview * @author Benjamin Pitzer - ben.pitzer@gmail.com * @author Russell Toris - rctoris@wpi.edu */ @@ -2018,8 +4183,9 @@ function UrdfMesh(options) { } module.exports = UrdfMesh; -},{"../math/Vector3":21,"./UrdfTypes":35}],33:[function(require,module,exports){ +},{"../math/Vector3":25,"./UrdfTypes":39}],37:[function(require,module,exports){ /** + * @fileOverview * @author Benjamin Pitzer - ben.pitzer@gmail.com * @author Russell Toris - rctoris@wpi.edu */ @@ -2027,7 +4193,7 @@ module.exports = UrdfMesh; var UrdfMaterial = require('./UrdfMaterial'); var UrdfLink = require('./UrdfLink'); var UrdfJoint = require('./UrdfJoint'); -var DOMParser = require('xmlshim').DOMParser; +var DOMParser = require('xmldom').DOMParser; // See https://developer.mozilla.org/docs/XPathResult#Constants var XPATH_FIRST_ORDERED_NODE_TYPE = 9; @@ -2057,7 +4223,7 @@ function UrdfModel(options) { // Initialize the model with the given XML node. // Get the robot tag - var robotXml = xmlDoc.evaluate('//robot', xmlDoc, null, XPATH_FIRST_ORDERED_NODE_TYPE, null).singleNodeValue; + var robotXml = xmlDoc.documentElement; // Get the robot name this.name = robotXml.getAttribute('name'); @@ -2114,8 +4280,9 @@ function UrdfModel(options) { module.exports = UrdfModel; -},{"./UrdfJoint":29,"./UrdfLink":30,"./UrdfMaterial":31,"xmlshim":2}],34:[function(require,module,exports){ +},{"./UrdfJoint":33,"./UrdfLink":34,"./UrdfMaterial":35,"xmldom":46}],38:[function(require,module,exports){ /** + * @fileOverview * @author Benjamin Pitzer - ben.pitzer@gmail.com * @author Russell Toris - rctoris@wpi.edu */ @@ -2135,7 +4302,7 @@ function UrdfSphere(options) { } module.exports = UrdfSphere; -},{"./UrdfTypes":35}],35:[function(require,module,exports){ +},{"./UrdfTypes":39}],39:[function(require,module,exports){ module.exports = { URDF_SPHERE : 0, URDF_BOX : 1, @@ -2143,8 +4310,9 @@ module.exports = { URDF_MESH : 3 }; -},{}],36:[function(require,module,exports){ +},{}],40:[function(require,module,exports){ /** + * @fileOverview * @author Benjamin Pitzer - ben.pitzer@gmail.com * @author Russell Toris - rctoris@wpi.edu */ @@ -2271,7 +4439,7 @@ function UrdfVisual(options) { } module.exports = UrdfVisual; -},{"../math/Pose":18,"../math/Quaternion":19,"../math/Vector3":21,"./UrdfBox":26,"./UrdfCylinder":28,"./UrdfMaterial":31,"./UrdfMesh":32,"./UrdfSphere":34}],37:[function(require,module,exports){ +},{"../math/Pose":22,"../math/Quaternion":23,"../math/Vector3":25,"./UrdfBox":30,"./UrdfCylinder":32,"./UrdfMaterial":35,"./UrdfMesh":36,"./UrdfSphere":38}],41:[function(require,module,exports){ module.exports = require('object-assign')({ UrdfBox: require('./UrdfBox'), UrdfColor: require('./UrdfColor'), @@ -2284,19 +4452,291 @@ module.exports = require('object-assign')({ UrdfVisual: require('./UrdfVisual') }, require('./UrdfTypes')); -},{"./UrdfBox":26,"./UrdfColor":27,"./UrdfCylinder":28,"./UrdfLink":30,"./UrdfMaterial":31,"./UrdfMesh":32,"./UrdfModel":33,"./UrdfSphere":34,"./UrdfTypes":35,"./UrdfVisual":36,"object-assign":1}],38:[function(require,module,exports){ -(function (global){ -module.exports = { - EventEmitter2: global.EventEmitter2 +},{"./UrdfBox":30,"./UrdfColor":31,"./UrdfCylinder":32,"./UrdfLink":34,"./UrdfMaterial":35,"./UrdfMesh":36,"./UrdfModel":37,"./UrdfSphere":38,"./UrdfTypes":39,"./UrdfVisual":40,"object-assign":3}],42:[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 }; -}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) -},{}],39:[function(require,module,exports){ -(function (global){ -module.exports = global.WebSocket; -}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) -},{}],40:[function(require,module,exports){ + +/** + * 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; +} + +},{}],43:[function(require,module,exports){ +module.exports = typeof window !== 'undefined' ? window.WebSocket : WebSocket; + +},{}],44:[function(require,module,exports){ /* global document */ module.exports = function Canvas() { return document.createElement('canvas'); }; -},{}]},{},[4]); +},{}],45:[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":44}],46:[function(require,module,exports){ +exports.DOMImplementation = window.DOMImplementation; +exports.XMLSerializer = window.XMLSerializer; +exports.DOMParser = window.DOMParser; + +},{}],47:[function(require,module,exports){ +var work = require('webworkify'); +var workerSocketImpl = require('./workerSocketImpl'); + +function WorkerSocket(uri) { + this.socket_ = work(workerSocketImpl); + + this.socket_.addEventListener('message', this.handleWorkerMessage_.bind(this)); + + this.socket_.postMessage({ + uri: uri, + }); +} + +WorkerSocket.prototype.handleWorkerMessage_ = function(ev) { + var data = ev.data; + if (data instanceof ArrayBuffer || typeof data === 'string') { + // binary or JSON message from rosbridge + this.onmessage(ev); + } else { + // control message from the wrapped WebSocket + var type = data.type; + if (type === 'close') { + this.onclose(null); + } else if (type === 'open') { + this.onopen(null); + } else if (type === 'error') { + this.onerror(null); + } else { + throw 'Unknown message from workersocket'; + } + } +}; + +WorkerSocket.prototype.send = function(data) { + this.socket_.postMessage(data); +}; + +WorkerSocket.prototype.close = function() { + this.socket_.postMessage({ + close: true + }); +}; + +module.exports = WorkerSocket; + +},{"./workerSocketImpl":48,"webworkify":5}],48:[function(require,module,exports){ +var WebSocket = WebSocket || require('ws'); + +module.exports = function(self) { + var socket = null; + + function handleSocketMessage(ev) { + var data = ev.data; + + if (data instanceof ArrayBuffer) { + // binary message, transfer for speed + self.postMessage(data, [data]); + } else { + // JSON message, copy string + self.postMessage(data); + } + } + + function handleSocketControl(ev) { + self.postMessage({type: ev.type}); + } + + self.addEventListener('message', function(ev) { + var data = ev.data; + + if (typeof data === 'string') { + // JSON message from ROSLIB + socket.send(data); + } else { + // control message + if (data.hasOwnProperty('close')) { + socket.close(); + socket = null; + } else if (data.hasOwnProperty('uri')) { + var uri = data.uri; + + socket = new WebSocket(uri); + socket.binaryType = 'arraybuffer'; + + socket.onmessage = handleSocketMessage; + socket.onclose = handleSocketControl; + socket.onopen = handleSocketControl; + socket.onerror = handleSocketControl; + } else { + throw 'Unknown message to WorkerSocket'; + } + } + }); +}; + +},{"ws":43}]},{},[7]); diff --git a/include/js/roslib.min.js b/include/js/roslib.min.js index 211b51d..96c6e26 100644 --- a/include/js/roslib.min.js +++ b/include/js/roslib.min.js @@ -1 +1,2 @@ -!function a(b,c,d){function e(g,h){if(!c[g]){if(!b[g]){var i="function"==typeof require&&require;if(!h&&i)return i(g,!0);if(f)return f(g,!0);var j=new Error("Cannot find module '"+g+"'");throw j.code="MODULE_NOT_FOUND",j}var k=c[g]={exports:{}};b[g][0].call(k.exports,function(a){var c=b[g][1][a];return e(c?c:a)},k,k.exports,a,b,c,d)}return c[g].exports}for(var f="function"==typeof require&&require,g=0;gb.secs?!1:a.secs0&&(this.minval=parseFloat(b[0].getAttribute("lower")),this.maxval=parseFloat(b[0].getAttribute("upper")))}b.exports=d},{}],30:[function(a,b,c){function d(a){this.name=a.xml.getAttribute("name"),this.visuals=[];for(var b=a.xml.getElementsByTagName("visual"),c=0;c0&&(this.textureFilename=b[0].getAttribute("filename"));var c=a.xml.getElementsByTagName("color");c.length>0&&(this.color=new e({xml:c[0]}))}var e=a("./UrdfColor");d.prototype.isLink=function(){return null===this.color&&null===this.textureFilename};var f=a("object-assign");d.prototype.assign=function(a){return f(this,a)},b.exports=d},{"./UrdfColor":27,"object-assign":1}],32:[function(a,b,c){function d(a){this.scale=null,this.type=f.URDF_MESH,this.filename=a.xml.getAttribute("filename");var b=a.xml.getAttribute("scale");if(b){var c=b.split(" ");this.scale=new e({x:parseFloat(c[0]),y:parseFloat(c[1]),z:parseFloat(c[2])})}}var e=a("../math/Vector3"),f=a("./UrdfTypes");b.exports=d},{"../math/Vector3":21,"./UrdfTypes":35}],33:[function(a,b,c){function d(a){a=a||{};var b=a.xml,c=a.string;if(this.materials={},this.links={},this.joints={},c){var d=new h;b=d.parseFromString(c,"text/xml")}var j=b.evaluate("//robot",b,null,i,null).singleNodeValue;this.name=j.getAttribute("name");for(var k=j.childNodes,l=0;l0){for(var A=z[0],B=null,C=0;C0&&(this.material=new j({xml:F[0]}))}var e=a("../math/Pose"),f=a("../math/Vector3"),g=a("../math/Quaternion"),h=a("./UrdfCylinder"),i=a("./UrdfBox"),j=a("./UrdfMaterial"),k=a("./UrdfMesh"),l=a("./UrdfSphere");b.exports=d},{"../math/Pose":18,"../math/Quaternion":19,"../math/Vector3":21,"./UrdfBox":26,"./UrdfCylinder":28,"./UrdfMaterial":31,"./UrdfMesh":32,"./UrdfSphere":34}],37:[function(a,b,c){b.exports=a("object-assign")({UrdfBox:a("./UrdfBox"),UrdfColor:a("./UrdfColor"),UrdfCylinder:a("./UrdfCylinder"),UrdfLink:a("./UrdfLink"),UrdfMaterial:a("./UrdfMaterial"),UrdfMesh:a("./UrdfMesh"),UrdfModel:a("./UrdfModel"),UrdfSphere:a("./UrdfSphere"),UrdfVisual:a("./UrdfVisual")},a("./UrdfTypes"))},{"./UrdfBox":26,"./UrdfColor":27,"./UrdfCylinder":28,"./UrdfLink":30,"./UrdfMaterial":31,"./UrdfMesh":32,"./UrdfModel":33,"./UrdfSphere":34,"./UrdfTypes":35,"./UrdfVisual":36,"object-assign":1}],38:[function(a,b,c){(function(a){b.exports={EventEmitter2:a.EventEmitter2}}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{}],39:[function(a,b,c){(function(a){b.exports=a.WebSocket}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{}],40:[function(a,b,c){b.exports=function(){return document.createElement("canvas")}},{}]},{},[4]); \ No newline at end of file +!function(){function a(b,c,d){function e(g,h){if(!c[g]){if(!b[g]){var i="function"==typeof require&&require;if(!h&&i)return i(g,!0);if(f)return f(g,!0);var j=new Error("Cannot find module '"+g+"'");throw j.code="MODULE_NOT_FOUND",j}var k=c[g]={exports:{}};b[g][0].call(k.exports,function(a){var c=b[g][1][a];return e(c||a)},k,k.exports,a,b,c,d)}return c[g].exports}for(var f="function"==typeof require&&require,g=0;g>2,f=0;f>6),d.push(128|63&g)):g<55296?(d.push(224|g>>12),d.push(128|g>>6&63),d.push(128|63&g)):(g=(1023&g)<<10,g|=1023&a.charCodeAt(++b),g+=65536,d.push(240|g>>18),d.push(128|g>>12&63),d.push(128|g>>6&63),d.push(128|63&g))}return m(3,d.length),i(d);default:var j;if(Array.isArray(a))for(j=a.length,m(4,j),b=0;b>5!==a)throw"Invalid indefinite length element";return c}function s(a,b){for(var c=0;c>10),a.push(56320|1023&d))}}function t(){var a,e,f=l(),g=f>>5,m=31&f;if(7===g)switch(m){case 25:return i();case 26:return j();case 27:return k()}if(e=q(m),e<0&&(g<2||6=0;)o+=e,n.push(h(e));var u=new Uint8Array(o),v=0;for(a=0;a=0;)s(w,e);else s(w,e);return String.fromCharCode.apply(null,w);case 4:var x;if(e<0)for(x=[];!p();)x.push(t());else for(x=new Array(e),a=0;a0&&f._listeners.length>this._maxListeners&&(f._listeners.warned=!0,g.call(this,f._listeners.length,h))):f._listeners=b,!0;h=a.shift()}return!0}var k=Array.isArray?Array.isArray:function(a){return"[object Array]"===Object.prototype.toString.call(a)},l=10;h.EventEmitter2=h,h.prototype.delimiter=".",h.prototype.setMaxListeners=function(a){a!==d&&(this._maxListeners=a,this._conf||(this._conf={}),this._conf.maxListeners=a)},h.prototype.event="",h.prototype.once=function(a,b){return this._once(a,b,!1)},h.prototype.prependOnceListener=function(a,b){return this._once(a,b,!0)},h.prototype._once=function(a,b,c){return this._many(a,1,b,c),this},h.prototype.many=function(a,b,c){return this._many(a,b,c,!1)},h.prototype.prependMany=function(a,b,c){return this._many(a,b,c,!0)},h.prototype._many=function(a,b,c,d){function e(){return 0===--b&&f.off(a,e),c.apply(this,arguments)}var f=this;if("function"!=typeof c)throw new Error("many only accepts instances of Function");return e._origin=c,this._on(a,e,d),f},h.prototype.emit=function(){this._events||e.call(this);var a=arguments[0];if("newListener"===a&&!this.newListener&&!this._events.newListener)return!1;var b,c,d,f,g,h=arguments.length;if(this._all&&this._all.length){if(g=this._all.slice(),h>3)for(b=new Array(h),f=0;f3)for(b=new Array(h-1),f=1;f3)for(b=new Array(j),f=1;f3)for(b=new Array(j-1),f=1;f0&&this._events[a].length>this._maxListeners&&(this._events[a].warned=!0,g.call(this,this._events[a].length,a))):this._events[a]=b,this)},h.prototype.off=function(a,b){function c(a){if(a!==d){var b=Object.keys(a);for(var e in b){var f=b[e],g=a[f];g instanceof Function||"object"!=typeof g||null===g||(Object.keys(g).length>0&&c(a[f]),0===Object.keys(g).length&&delete a[f])}}}if("function"!=typeof b)throw new Error("removeListener only takes instances of Function");var e,f=[];if(this.wildcard){var g="string"==typeof a?a.split(this.delimiter):a.slice();f=i.call(this,null,g,this.listenerTree,0)}else{if(!this._events[a])return this;e=this._events[a],f.push({_listeners:e})}for(var h=0;h0){for(b=this._all,c=0,d=b.length;c1)for(var c=1;cb.secs)&&(a.secs0&&(this.parent=b[0].getAttribute("link"));var c=a.xml.getElementsByTagName("child");c.length>0&&(this.child=c[0].getAttribute("link"));var d=a.xml.getElementsByTagName("limit");d.length>0&&(this.minval=parseFloat(d[0].getAttribute("lower")),this.maxval=parseFloat(d[0].getAttribute("upper")));var h=a.xml.getElementsByTagName("origin");if(0===h.length)this.origin=new e;else{var i=h[0].getAttribute("xyz"),j=new f;i&&(i=i.split(" "),j=new f({x:parseFloat(i[0]),y:parseFloat(i[1]),z:parseFloat(i[2])}));var k=h[0].getAttribute("rpy"),l=new g;if(k){k=k.split(" ");var m=parseFloat(k[0]),n=parseFloat(k[1]),o=parseFloat(k[2]),p=m/2,q=n/2,r=o/2,s=Math.sin(p)*Math.cos(q)*Math.cos(r)-Math.cos(p)*Math.sin(q)*Math.sin(r),t=Math.cos(p)*Math.sin(q)*Math.cos(r)+Math.sin(p)*Math.cos(q)*Math.sin(r),u=Math.cos(p)*Math.cos(q)*Math.sin(r)-Math.sin(p)*Math.sin(q)*Math.cos(r),v=Math.cos(p)*Math.cos(q)*Math.cos(r)+Math.sin(p)*Math.sin(q)*Math.sin(r);l=new g({x:s,y:t,z:u,w:v}),l.normalize()}this.origin=new e({position:j,orientation:l})}}var e=a("../math/Pose"),f=a("../math/Vector3"),g=a("../math/Quaternion");b.exports=d},{"../math/Pose":22,"../math/Quaternion":23,"../math/Vector3":25}],34:[function(a,b,c){function d(a){this.name=a.xml.getAttribute("name"),this.visuals=[];for(var b=a.xml.getElementsByTagName("visual"),c=0;c0&&(this.textureFilename=b[0].getAttribute("filename"));var c=a.xml.getElementsByTagName("color");c.length>0&&(this.color=new e({xml:c[0]}))}var e=a("./UrdfColor");d.prototype.isLink=function(){return null===this.color&&null===this.textureFilename};var f=a("object-assign");d.prototype.assign=function(a){return f(this,a)},b.exports=d},{"./UrdfColor":31,"object-assign":3}],36:[function(a,b,c){function d(a){this.scale=null,this.type=f.URDF_MESH,this.filename=a.xml.getAttribute("filename");var b=a.xml.getAttribute("scale");if(b){var c=b.split(" ");this.scale=new e({x:parseFloat(c[0]),y:parseFloat(c[1]),z:parseFloat(c[2])})}}var e=a("../math/Vector3"),f=a("./UrdfTypes");b.exports=d},{"../math/Vector3":25,"./UrdfTypes":39}],37:[function(a,b,c){function d(a){a=a||{};var b=a.xml,c=a.string;if(this.materials={},this.links={},this.joints={},c){var d=new h;b=d.parseFromString(c,"text/xml")}var i=b.documentElement;this.name=i.getAttribute("name");for(var j=i.childNodes,k=0;k0){for(var A=z[0],B=null,C=0;C0&&(this.material=new j({xml:F[0]}))}var e=a("../math/Pose"),f=a("../math/Vector3"),g=a("../math/Quaternion"),h=a("./UrdfCylinder"),i=a("./UrdfBox"),j=a("./UrdfMaterial"),k=a("./UrdfMesh"),l=a("./UrdfSphere");b.exports=d},{"../math/Pose":22,"../math/Quaternion":23,"../math/Vector3":25,"./UrdfBox":30,"./UrdfCylinder":32,"./UrdfMaterial":35,"./UrdfMesh":36,"./UrdfSphere":38}],41:[function(a,b,c){b.exports=a("object-assign")({UrdfBox:a("./UrdfBox"),UrdfColor:a("./UrdfColor"),UrdfCylinder:a("./UrdfCylinder"),UrdfLink:a("./UrdfLink"),UrdfMaterial:a("./UrdfMaterial"),UrdfMesh:a("./UrdfMesh"),UrdfModel:a("./UrdfModel"),UrdfSphere:a("./UrdfSphere"),UrdfVisual:a("./UrdfVisual")},a("./UrdfTypes"))},{"./UrdfBox":30,"./UrdfColor":31,"./UrdfCylinder":32,"./UrdfLink":34,"./UrdfMaterial":35,"./UrdfMesh":36,"./UrdfModel":37,"./UrdfSphere":38,"./UrdfTypes":39,"./UrdfVisual":40,"object-assign":3}],42:[function(a,b,c){"use strict";function d(){j||(j=!0,console.warn("CBOR 64-bit integer array values may lose precision. No further warnings."))}function e(a){d();for(var b=a.byteLength,c=a.byteOffset,e=b/8,f=a.buffer.slice(c,c+b),g=new Uint32Array(f),h=new Array(e),j=0;j + Lab UI + +
-
-

ROS Display

-
- - - -
- -
- ROS Bag -
-
-
-
- Bagfile name must not start with "/" -
- - - - -
-
-
-
+
+

Lab UI

- - - - +
+
+ Master: +
+
+ + +
+
+
-
- - - -
- - - - - +
+
- - +
+ +
@@ -132,18 +126,53 @@ Tutorials can be found here: http://www.w3schools.com/bootstrap/default.asp
-
+
+
+ Predefined ROSBridge Connections +
+
+ + + + +
+
+ +
- ROS Topics. Checked topics will be bagged when "Start" is selected. + ROS Topics.
-
+
N/A
@@ -151,7 +180,7 @@ Tutorials can be found here: http://www.w3schools.com/bootstrap/default.asp
- ROS Nodes + ROS Nodes.
diff --git a/launch.bash b/launch.bash deleted file mode 100644 index 8958e3d..0000000 --- a/launch.bash +++ /dev/null @@ -1,26 +0,0 @@ -#!/bin/bash - - -kill_child_processes() { - isTopmost=$1 - curPid=$2 - childPids=`ps -o pid --no-headers --ppid ${curPid}` - for childPid in $childPids - do - kill_child_processes 0 $childPid - done - if [ $isTopmost -eq 0 ]; then - kill -9 $curPid 2> /dev/null - fi -} - -# Ctrl-C trap. Catches INT signal -trap "kill_child_processes 1 $$; exit 0" INT - -chmod a+x src/bagger.py - -x-terminal-emulator -e "roslaunch rosbridge_gui all.launch" & -x-terminal-emulator -e "python -m SimpleHTTPServer 8000" & -x-terminal-emulator -e "rosrun web_video_server web_video_server" - - diff --git a/launch/all.launch b/launch/all.launch deleted file mode 100644 index 759dad6..0000000 --- a/launch/all.launch +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/launch/bagger.launch b/launch/bagger.launch deleted file mode 100644 index 45f048c..0000000 --- a/launch/bagger.launch +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/launch/lab_ui.launch b/launch/lab_ui.launch new file mode 100755 index 0000000..fa83e32 --- /dev/null +++ b/launch/lab_ui.launch @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/launch/talker.launch b/launch/talker.launch deleted file mode 100644 index 51399f1..0000000 --- a/launch/talker.launch +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/package.xml b/package.xml index ff79b60..45bb55b 100644 --- a/package.xml +++ b/package.xml @@ -1,13 +1,13 @@ - rosbridge_gui + aescape_lab_ui 0.0.0 - The rosbridge_gui package + The aescape_lab_ui package - glen + David @@ -41,6 +41,8 @@ rospy std_msgs + web_video_server + rosbridge_server diff --git a/rosbridge_connection_screenshot.jpg b/rosbridge_connection_screenshot.jpg deleted file mode 100644 index cf7e4c6..0000000 Binary files a/rosbridge_connection_screenshot.jpg and /dev/null differ diff --git a/scripts/webserver.sh b/scripts/webserver.sh new file mode 100755 index 0000000..7fc6331 --- /dev/null +++ b/scripts/webserver.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash + +cd $(rospack find aescape_lab_ui) +# python3 -m http.server 8000 + +# Node webserver: https://www.npmjs.com/package/http-server +http-server -p 8000 \ No newline at end of file diff --git a/src/bagger.py b/src/bagger.py deleted file mode 100755 index 74977f7..0000000 --- a/src/bagger.py +++ /dev/null @@ -1,49 +0,0 @@ -#!/usr/bin/env python -import rospy -from std_msgs.msg import String -import rospkg -import subprocess -import os -import signal - -class Bagger(object): - - def __init__(self): - rospy.init_node("bagger", anonymous=True) - rospy.Subscriber("bag_publisher", String, self.callback) - self.pub = rospy.Publisher("bag_notifier", String, queue_size=10) - self.proc = None - rospack = rospkg.RosPack() - self.data_path = os.path.join(rospack.get_path("rosbridge_gui"), "data") - - def callback(self, msg): - if msg.data == "STOP" and self.proc is not None: - os.killpg(self.proc.pid, signal.SIGINT) - self.pub.publish("STOPPED") - else: - msg_data = msg.data.split() - self.pub.publish(str(len(msg_data))) - self.pub.publish(str(msg_data)) - if len(msg_data) == 0: # We only got the name of the bag file and not any topics - bag_file_name = os.path.join(self.data_path, "bag_file") - self.proc = subprocess.Popen(["rosbag", "record", - "--all", "-o", bag_file_name], preexec_fn=os.setsid) - self.pub.publish("STARTED") - elif len(msg_data) == 1: # We only got the name of the bag file and not any topics - bag_file_name = os.path.join(self.data_path, msg_data) - self.proc = subprocess.Popen(["rosbag", "record", - "--all", "-o", bag_file_name], preexec_fn=os.setsid) - self.pub.publish("STARTED") - else: #we posibly have a bag file name and a list of topics - if msg_data[0].startswith("/"): #then we know there is no bag file name - msg_data = ["bagfile"] + msg_data - msg_data[0] = os.path.join(self.data_path, msg_data[0]) - processList = ["rosbag", "record", "-o"] + msg_data - self.pub.publish(" ".join(processList)) - self.proc = subprocess.Popen( processList, preexec_fn=os.setsid) - self.pub.publish("STARTED") - - -if __name__ == '__main__': - Bagger() - rospy.spin() diff --git a/src/talker.py b/src/talker.py deleted file mode 100755 index b4bc6f6..0000000 --- a/src/talker.py +++ /dev/null @@ -1,20 +0,0 @@ -#!/usr/bin/env python -# license removed for brevity -import rospy -from std_msgs.msg import String - -def talker(): - pub = rospy.Publisher('chatter', String, queue_size=10) - rospy.init_node('talker', anonymous=True) - rate = rospy.Rate(10) # 10hz - while not rospy.is_shutdown(): - hello_str = "hello world %s" % rospy.get_time() - rospy.loginfo(hello_str) - pub.publish(hello_str) - rate.sleep() - -if __name__ == '__main__': - try: - talker() - except rospy.ROSInterruptException: - pass diff --git a/vision/js/ros_scripts.js b/vision/js/ros_scripts.js new file mode 100644 index 0000000..1233562 --- /dev/null +++ b/vision/js/ros_scripts.js @@ -0,0 +1,104 @@ + + + +/////////////////////////////////////////////////////////////////////////////////// +// Publishers +/////////////////////////////////////////////////////////////////////////////////// + + +//////////////////////////////////////////////////////////////// +// Topics +//////////////////////////////////////////////////////////////// + + + +//////////////////////////////////////////////////////////////// +// Subscribers +//////////////////////////////////////////////////////////////// + +// Operation Mode +var modeStatus = new ROSLIB.Topic({ + ros : ros, + name : '/aescape/mode/status', + messageType : 'std_msgs/String' +}); + +modeStatus.subscribe(function(message) { + document.getElementById("stoppedModeButton").className = "btn btn-secondary" + document.getElementById("standbyModeButton").className = "btn btn-primary" + document.getElementById("teachingModeButton").className = "btn btn-primary" + document.getElementById("executionModeButton").className = "btn btn-primary" + + + if (message.data === "stopped") { + document.getElementById("stoppedModeButton").className = "btn btn-warning" + } else if (message.data === "standby") + { + document.getElementById("standbyModeButton").className = "btn btn-primary btn-success" + } else if (message.data === "teach") + { + document.getElementById("teachingModeButton").className = "btn btn-primary btn-success" + } else if (message.data === "execution") + { + document.getElementById("executionModeButton").className = "btn btn-primary btn-success" + } +}); + +// Playing Bag +var bagPlayingTopic = new ROSLIB.Topic({ + ros : ros, + name : '/aescape/bags/playing', + messageType : 'std_msgs/String' +}); + +bagPlayingTopic.subscribe(function(message) { + document.getElementById("bagPlayingText").innerHTML = message.data +}); + + + +//////////////////////////////////////////////////////////////// +// Services +//////////////////////////////////////////////////////////////// + + +function triggerService(serviceName) +{ + var service = new ROSLIB.Service({ + ros : ros, + name : serviceName, + serviceType : 'std_srvs/Trigger' + }); + + var request = new ROSLIB.ServiceRequest({}); + + service.callService(request, function(result) { + console.log('Result for service call on ' + + serviceName + + ': ' + + result.sum); + }); +} + + +function triggerMessageService(serviceName, textInput) +{ + var text = document.getElementById(textInput).value + + var service = new ROSLIB.Service({ + ros : ros, + name : serviceName, + serviceType : 'demobot.TriggerMessage' + }); + + var request = new ROSLIB.ServiceRequest({ + message : text + }); + + service.callService(request, function(result) { + console.log('Result for service call on ' + + serviceName + + ': ' + + result.sum); + }); +} \ No newline at end of file diff --git a/vision/js/update_guis.js b/vision/js/update_guis.js new file mode 100644 index 0000000..e69de29 diff --git a/vision/vision.html b/vision/vision.html new file mode 100644 index 0000000..613197d --- /dev/null +++ b/vision/vision.html @@ -0,0 +1,122 @@ + + + + + + +
+
+ Camera Views +
+
+ Webcam +
+ No Camera Image +
+ Realsense Red +
+ No Camera Image +
+ Realsense Green +
+ No Camera Image +
+ Thermal +
+ No Camera Image +
+
+
+ + \ No newline at end of file