Loading files
This file is pinned to the selected release. Download the original to open it in your design tool.
25e0ce228This file is pinned to the selected release. Download the original to open it in your design tool.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<!-- load these links without specifying http or https -->
<link rel="stylesheet" type="text/css" href="scripts/jquery-ui.css"/>
<link rel="stylesheet" type="text/css" href="scripts/bootstrap.min.css"/>
<link rel="stylesheet" type="text/css" href="scripts/font-awesome.min.css"/>
<style>
*{
font-family: Verdana, Arial, sans-serif;
}
a:link {
color: #000;
text-decoration: none;
}
a:visited {
color: #000;
}
a:hover {
color: #33F;
}
.button {
background: -webkit-linear-gradient(top, #008dfd 0, #0370ea 100%);
border: 1px solid #076bd2;
border-radius: 3px;
color: #fff;
display: none;
font-size: 13px;
font-weight: bold;
line-height: 1.3;
padding: 8px 25px;
text-align: center;
text-shadow: 1px 1px 1px #076bd2;
letter-spacing: normal;
}
.center {
padding: 10px;
text-align: center;
}
.final {
color: black;
padding-right: 3px;
}
.cmd_err {
color: red;
}
.info {
font-size: 14px;
text-align: center;
color: #777;
display: none;
}
.right {
float: right;
}
.vcenter {
display: inline-block;
vertical-align: middle;
float: none;
}
#headline {
font-size: 40px;
font-weight: 300;
}
#info {
font-size: 20px;
text-align: center;
color: #777;
visibility: hidden;
}
#results {
font-size: 14px;
font-weight: bold;
border: 1px solid #ddd;
padding: 15px;
text-align: left;
min-height: 100px;
}
<!--.navbar-custom a {
color: white;
background-color: black;
}-->
#menu001.navbar-default .navbar-brand {
color: rgba(119, 119, 119, 1);
}
#menu001.navbar-default {
font-size: 20px;
background-color: rgba(224, 224, 224, 1);
border-width: 1px;
border-radius: 4px;
}
#menu001.navbar-default .navbar-nav>li>a {
color: rgba(119, 119, 119, 1);
background-color: rgba(248, 248, 248, 0);
padding-right:50px;
}
#menu001.navbar-default .navbar-nav>li>a:hover,
#menu001.navbar-default .navbar-nav>li>a:focus {
color: rgba(51, 51, 51, 1);
background-color: rgba(248, 248, 248, 0);
}
#menu001.navbar-default .navbar-nav>.active>a,
#menu001.navbar-default .navbar-nav>.active>a:hover,
#menu001.navbar-default .navbar-nav>.active>a:focus {
color: rgba(85, 85, 85, 1);
background-color: rgba(231, 231, 231, 1);
}
#menu001.navbar-default .navbar-toggle {
border-color: #ddd;
}
#menu001.navbar-default .navbar-toggle:hover,
#menu001.navbar-default .navbar-toggle:focus {
background-color: #ddd;
}
#menu001.navbar-default .navbar-toggle .icon-bar {
background-color: #888;
}
#menu001.navbar-default .navbar-toggle:hover .icon-bar,
#menu001.navbar-default .navbar-toggle:focus .icon-bar {
background-color: #888;
}
#robot-url {
font-size: 25px;
}
#url {
font-size: 25px;
}
</style>
</div>
</head>
<body onload="init()">
<div class="page-header" style="text-align:center;margin-bottom:0" role="navigation">
<h1><b>Robot Control </b></h1>
</div>
<div id="menu001" class="navbar navbar-default" role="navigation" >
<div class="container-fluid">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-menubuilder">
<span class="sr-only">Toggle navigation</span><span class="icon-bar"></span><span class="icon-bar"></span><span class="icon-bar"></span>
</button>
</div>
</div>
</div>
<div class="container-fluid">
<form role="form" class="form-horizontal" autocomplete="on" onsubmit="connect()">
<div id="robot-url" class="form-group form-group-lg">
<label for="robotUrlEntry" class="control-label col-xs-3">Robot URL:</label>
<div class="col-xs-6">
<input type="url" name="url" id="robotUrlEntry" class="form-control input-lg"
placeholder="https://" />
</div>
<div class="col-xs-3">
<button type="button" id="connectButton" class="btn btn-primary btn-lg" style="font-size:25" onclick="connect()">
<strong>Connect</strong></button>
</div>
</div>
</form>
</div>
<div id="speed-label"></div>
<div id="speed-slider"></div>
<div class="container-fluid">
<div class="row">
<div class="col-xs-4">
</div>
<div class="col-xs-4" style="text-align:center">
<p><a class="fa fa-arrow-up fa-5x" id="arrowUp" onclick="arrowUp()" data-toggle="tooltip" data-placement="left" title="Forward"></a>
</p>
</div>
</div>
<div class="row">
<div class="col-xs-4" style="text-align:center">
<p><a class="fa fa-rotate-left fa-5x vcenter" onclick="arrowLeft() "data-toggle="tooltip" data-placement="left" title="Rotate Left"></a>
</p>
</div>
<div class="col-xs-4" style="text-align:center">
<span id='start_button' class="fa-stack fa-4x vcenter"
onclick="startButton(event)" alt="Start">
<i id='mic-bg' class="fa fa-circle fa-stack-2x" style="color:#A8A8A8;"></i>
<i id='mic' class="fa fa-microphone fa-stack-1x fa-inverse"></i>
<i id='mic-slash' class="fa fa-microphone-slash fa-stack-1x
fa-inverse" style="display:none;"></i>
</span>
</div>
<div class="col-xs-4 vcenter" style="text-align:center">
<p><a class="fa fa-rotate-right fa-5x" onclick="arrowRight()"data-toggle="tooltip" data-placement="left" title="Rotate Right" ></a>
</p>
</div>
</div>
<div class="row">
<div class="col-xs-4">
</div>
<div class="col-xs-4 vcenter" style="text-align:center">
<p><a class="fa fa-arrow-down fa-5x" onclick="arrowDown()" data-toggle="tooltip" data-placement="left" title="Back"></a>
</p>
</div>
</div>
<div class="row">
<div class="col-xs-2">
</div>
<div class="col-xs-8 vcenter" style="text-align:center">
<button type="button" class="btn btn-danger btn-lg btn-block" style="font-size:25" onclick="stopButton()">
<strong>Stop</strong></button>
</div>
</div>
<div id="info" >
<p id="info_none"><br><br></p>
<p id="info_start">Click on the microphone and begin speaking.</p>
<p id="info_speak_now">Speak now.<br><br></p>
<p id="info_no_speech">No speech was detected. You may need to adjust your
<a href="//support.google.com/chrome/bin/answer.py?hl=en&answer=1407892">
microphone settings</a>.</p>
<p id="info_no_microphone" style="display:none">
No microphone was found. Check that one is installed and that
<a href="//support.google.com/chrome/bin/answer.py?hl=en&answer=1407892">
microphone settings</a> are correct.</p>
<p id="info_allow">Click the "Allow" button to enable your microphone. Using the https version helps avoid this.</p>
<p id="info_denied">Permission to use microphone was denied.</p>
<p id="info_blocked">Permission to use microphone is blocked. To change, go to chrome://settings/contentExceptions#media-stream</p>
<p id="info_upgrade">This browser doesn't support the Web Speech API. Use <a href="//www.google.com/chrome">Chrome</a> version 25 or later.</p>
</div>
<div class="modal" id="helpModal" tabindex="-1" role="dialog" aria-labelledby="basicModal" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
<h4 class="modal-title" id="usingSpeech"><strong>Using Speech Commands</strong></h4>
</div>
<div class="modal-body">
<p id="commandHeader"><strong>Commands</strong></p>
<p>forward, keep going, go ahead, go straight, go</p>
<p>reverse, back, go back</p>
<p>forward/reverse etc., ____ meters/centimeters/feet</p>
<p>turn right</p>
<p>turn left</p>
<p>rotate right</p>
<p>rotate left</p>
<p>stop, halt</p>
<p>faster, speed up</p>
<p>slower, slow down</p>
<p>waypoint ____ (waypoint name)</p>
<p>go to ____ (waypoint name)</p>
<p>remove waypoint ____ (waypoint name)</p>
<p>list waypoints</p>
<p>go home</p>
<p>help</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
<div class="modal" id="aboutModal" tabindex="-1" role="dialog" aria-labelledby="basicModal" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
<h4 class="modal-title" id="aboutSpeech">About Speech Commands</h4>
</div>
<div class="modal-body">
<p>Speech Commands was written for the Ubiquity Robotics robots. It should support any ROS robot.</p>
<p>Author: Joe Landau jrlandau@gmail.com</p>
<p>Repository: https://github.com/UbiquityRobotics/speech_commands</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
<div id="dash">
<div id="speed_span" "Speed factor 1.00" class="final"></div>
</div>
<div style="text-align: center; align-content: center; align-items: center">
<img src="http://192.168.0.106:8080/stream?topic=/camera/rgb/image_raw"></img>
</div>
<div id="results" style="visibility: collapse">
Command Log:
<div style="height: 100px; overflow: auto;">
<table id="commandLog">
</table>
</div>
<br>
</div>
<script type="text/javascript" src="scripts/jquery.min.js"></script>
<script type="text/javascript" src="scripts/bootstrap.min.js"></script>
<script type="text/javascript" src="scripts/jquery-ui.min.js"></script>
<script type="text/javascript" src="scripts/eventemitter2.min.js"></script>
<script type="text/javascript" src="scripts/roslib.min.js"></script>
<script type="text/javascript" src="scripts/bootbox.min.js"></script>
<script type="text/javascript">
var connected=false;
var recognizing = false;
var recognition;
var total_recognized = 0;
var noRestartReco;
var startTimestamp;
var ros; // this will be the connection to Ros
var topicName = '/robot/cmd_vel'; // topic name for the UR robots
var speedFactor = 1.0; // multiplies or divides speed to go faster or slower
var linearSpeed = 0.5, angularSpeed = 1.0; // initial speed
var linearRepeat = 1, angularRepeat = 1; // number of times to repeat command
var repeatInterval = 500; // wait time between repeats, in ms
var robotUrl;
var muted = false;
var wakeup = ["robot"];
var useWakeup = false;
function setMicInactive() {micBg.style.color = "Gray";
micSlash.style.display = "none";
}
function setMicActive() {micBg.style.color="#00cc00"; // green
micSlash.style.display = "none";
}
function setMicOff() {micBg.style.color = "Gray";
micSlash.style.display = "inline";
}
function addLog(text, textColor) {
var table = document.getElementById("commandLog");
var row = table.insertRow(0);
var cell1 = row.insertCell(0);
if (textColor) {
cell1.style.color = "red";
}
cell1.innerHTML = text;
}
/**
* Setup GUI elements when the page is loaded.
*/
function init() {
var temp;
micBg = document.getElementById("mic-bg");
mic = document.getElementById("mic");
micSlash = document.getElementById("mic-slash");
if (localStorage.firstResultOK == undefined) {
localStorage.firstResultOK = 0;
}
if (localStorage.otherResultOK == undefined) {
localStorage.otherResultOK = 0;
}
if (localStorage.robotUrl != undefined) {
temp = localStorage.robotUrl; // use the last robot address
} else {
temp = "wss://" + location.hostname + ":9090"; //guess at it
}
document.getElementById("robotUrlEntry").value = temp;
if (window.SpeechSynthesisUtterance === undefined) {
muted = true;
}
showInfo ("info_none");
}
function say (words) {
var wasRecognizing = false;
var stowabool;
if (muted === false) {
stowabool = noRestartReco;
if (recognizing) {
wasRecognizing = true;
noRestartReco = true; //test
recognition.stop ();
}
var u = new SpeechSynthesisUtterance();
u.text = words;
u.lang = 'en-US';
u.rate = 1.1;
u.default = true;
u.localService = true;
u.onend = function(event) {
if (wasRecognizing) {
recognition.start ();
}
noRestartReco = stowabool;
}
speechSynthesis.speak(u);
}
}
function connect() {
var connectButton;
if (connected) { // disconnect
ros.close();
} else {
robotUrl = document.getElementById("robotUrlEntry").value.trim();
if (robotUrl == '') {
bootbox.alert ("Please supply the robot's URL and port");
return;
}
robotUrl = robotUrl.replace("https:", "wss:");
robotUrl = robotUrl.replace("http:", "ws:");
if ((robotUrl.slice (0,5) != "wss://") && (robotUrl.slice (0,4) != "ws://") &&
(robotUrl.charAt(robotUrl.length - 5) != ":")) {
var r = bootbox.alert
("The robot's URL should begin with http, https, ws, or wss, " +
"and end with a port number, like ':9090'.");
return;
}
ros = new ROSLIB.Ros({ // Connecting to ROS.
url: robotUrl
});
}
ros.on('connection', function() {
localStorage.robotUrl = robotUrl;
connectButton = document.getElementById("connectButton");
connectButton.innerHTML = "Disconnect";
connectButton.style.background="#00cc00"; // green
say ('connected');
connected = true;
console.log ('Connected to websocket server.');
});
ros.on('error', function(error) {
console.log (error);
say ('Darn. We failed to connect.');
//none of the following work...
//alert (error.stack);
//alert (error.message);
//alert (JSON.stringify(error));
bootbox.alert ('Error connecting to websocket server. ');
});
ros.on('close', function() {
if (connected) { // throw away a second call
connected = false;
connectButton = document.getElementById("connectButton");
connectButton.style.background = "#006dcc";
connectButton.innerHTML = "Connect"
say ('connection closed');
console.log('Connection to websocket server closed.');
}
});
}
function startRecognition () {
if (!('webkitSpeechRecognition' in window)) {
showInfo ('info_upgrade');
} else {
recognition = new webkitSpeechRecognition();
recognition.continuous = false;
recognition.interimResults = false;
recognition.maxAlternatives = 10;
showInfo('info_start');
recognition.onstart = function() {
recognizing = true;
showInfo('info_speak_now');
// console.log('recognition.onstart');
setMicActive ();
};
recognition.onerror = function(event) {
console.log('recognition.onerror' + ' ' + event.error);
if (event.error == 'no-speech') {
setMicInactive ();
showInfo('info_no_speech');
noRestartReco = false;
}
if (event.error == 'audio-capture') {
setMicInactive ();
showInfo('info_no_microphone');
noRestartReco = true;
}
if (event.error == 'not-allowed') {
if (event.timeStamp - startTimestamp < 100) {
showInfo('info_blocked');
} else {
showInfo('info_denied');
}
noRestartReco = true;
}
}
recognition.onend = function() {
// console.log('recognition.onend, norestart ' + noRestartReco);
recognizing = false;
if (noRestartReco) {
return;
}
showInfo('');
restartReco();
}
recognition.onresult = function(event) {
console.log('recognition.onresult');
function getDistance (quantity, what) {
var howmany;
howmany = Number(quantity);
if (isNaN(howmany)) {
if (quantity == "to" || quantity == "too") {
howmany = 2
} else if (quantity == "for") {
howmany = 4
} else {
return 0;
}
}
if (what == "meters" || what == "meter") {
return (howmany);
} else if (what == "centimeters" || what == "centimeter") {
return howmany * 0.01;
} else if (what == "feet" || what == "foot") {
return howmany * 0.3048; // converts feet to meters
} else if (what == "degrees" || what == "degree") {
return howmany * Math.PI / 180; // convert to radians
} else {
return 0;
}
}
var commands = ''
// linear x and y movement and angular z movement
var x = 0;
var y = 0;
var z = 0;
var commandFound = false;
var result;
var topCandidate = "";
var allResults = "";
var dist = 0;
if (event.results.length > 0) { // we have an array of recognition candidates
result = event.results[0];
// topCandidate = result[0].transcript
testAllCandidates:
for (var i = 0; i < result.length; ++i) {
candidate = result[i].transcript.toLowerCase().trim();
var words = candidate.match(/[-\w]+/g); // parses candidate to array of words
if (useWakeup) {
if (wakeup.indexOf (words[0]) >= 0) { // if the first word is a wakeup word
words.splice (0,1); // remove it
if (i == 0) {
topCandidate = words.join(' ');
}
} else {
continue;
}
}
if (words.length >= 2) {
if (words[0] == 'go' && words[1] != 'to' && words[1] != 'home') {
words.splice (0,1) // remove superfluous "go"
}
}
commandFound = true;
testCandidate: switch (words [0]){
case 'forward':
case 'foreword':
case 'keep going':
case 'ahead':
case 'straight':
case 'go':
if (words.length == 1) {
x = linearSpeed;
sendTwistMessage (x, z);
} else if (words.length == 3) {
dist = getDistance (words[1], words[2]); // accept meters, translate feet --> meters
commandFound = (dist > 0);
if (dist > 0) {
moveRobotFromPose (dist, 0); // move dist meters
}
} else {
commandFound = false;
}
break testCandidate;
case "reverse":
case "back":
if (words.length == 1) {
x = -linearSpeed;
sendTwistMessage (x, z);
} else if (words.length == 3) {
dist = -getDistance (words[1], words[2]); // accept meters, feet --> meter
commandFound = (dist < 0);
if (dist < 0) {
moveRobotFromPose (dist, 0); // move dist meters
}
} else {commandFound = false;
}
break testCandidate;
case "rotate":
if (words.length == 2 ) {
dist = angularSpeed;
} else if (words.length == 4) {
dist = getDistance (words[2], words[3]); // accept number of degrees
} else {
commandFound = false;
break testCandidate;
}
if (dist <= 0) {
commandFound = false;
break testCandidate;
}
rotswitch: switch (words [1]) {
case "right":
//z = -dist;
moveRobotFromPose (0, -dist);
break rotswitch;
case "left":
moveRobotFromPose (0, dist);
//z = dist;
break rotswitch;
default:
commandFound = false;
break testCandidate;
}
// sendTwistMessage (x, z);
break testCandidate;
case "turn":
turnswitch: switch (words [1]) {
case "right":
z = -angularSpeed;
break turnswitch;
case "left":
z = angularSpeed;
break turnswitch;
default:
commandfound = false;
break testCandidate;
}
x = linearSpeed;
sendTwistMessage (x, z);
break testCandidate;
case "stop":
case "halt":
sendTwistMessage (0, 0);
break testCandidate;
case "faster":
speedFactor *= 1.1;
speed_span.innerHTML = "Speed factor " + speedFactor.toFixed(2);
break testCandidate;
case "speed":
if (words [1] == "up") {
speedFactor *= 1.1;
speed_span.innerHTML = "Speed factor " + speedFactor.toFixed(2);
} else {
commandFound = false;
}
break testCandidate;
case "slower":
speedFactor /= 1.1;
speed_span.innerHTML = "Speed factor " + speedFactor.toFixed(2);
break testCandidate;
case "slow":
if (words [1] == "down") {
speedFactor /= 1.1;
speed_span.innerHTML = "Speed factor " + speedFactor.toFixed(2);
} else {
commandFound = false;
}
break testCandidate;
case "help":
$('#helpModal').modal('show');
break testAllCandidates;
default:
commandFound = false;
break testCandidate;
}
// it may yet be a waypoint command
if (!commandFound) {
if (words && words.length > 1) {
if (words [0] == "waypoint") { // it is a waypoint command, to set a waypoint
commandFound = true; // prevent the error msg
var waypoint = words.slice(1).join(" ");
bootbox.dialog({
message: waypoint,
title: "Please confirm the waypoint name ",
closeButton: false,
buttons: {
danger: {
label: "No",
className: "btn-danger",
callback: function() {
}
},
success: {
label: "OK",
className: "btn-success",
callback: function() {
setWaypoint (waypoint);
}
}
}
});
} else if (words.length > 2 && words[0] == "go" && words[1] == "to") { // go to waypoint
commandFound = true;
goToWaypoint (words.slice(2).join(" "));
} else if (words.length > 2 && words[0] == "remove" && words[1] == "waypoint") { // remove waypoint
commandFound = true;
SetWaypointZero (words.slice(2).join(" "));
} else if (words.length == 2 && words[0] == "list" && words[1] == "waypoints") { // list the waypoints
commandFound = true;
listWaypoints ();
} else if (words.length == 2 && words[0] == "go" && words[1] == "home") { // go home
commandFound = true;
goToWaypoint ("home");
}
}
}
allResults += " " + candidate;
if (commandFound === true) {
break testAllCandidates;
}
}
console.log (allResults);
if (commandFound) { // publish the command
commands = candidate + " (alt. #" + (i+1) + " of " + result.length + ") " + commands;
commands = commands.slice(0, 50);
//final_span.innerHTML = "Commands ["+ total_recognized + "]: " + commands;
//cmd_err_span.innerHTML = "";
total_recognized++;
addLog (commands);
// Research: Keep count of how often we used the first result
if (i==0) {
localStorage.firstResultOK = Number(localStorage.firstResultOK) + 1;
} else {
localStorage.otherResultOK = Number(localStorage.otherResultOK) + 1;
}
console.log ("First answer recognition rate is " + ((100 * Number(localStorage.firstResultOK)) /
(Number(localStorage.firstResultOK) + Number(localStorage.otherResultOK))).toFixed(2) + "%");
} else if (topCandidate != "") {
addLog (topCandidate.toLowerCase() + " is not recognized as a command", "red");
}
}
} // end of onresult
}
} // end of function startRecpgnition
/*
var teleop = new KEYBOARDTELEOP.Teleop({
ros: ros,
topic: '/base_controller/command'
});
// Create a UI slider using JQuery UI.
$('#speed-slider').slider({
range: 'min',
min: 0,
max: 100,
value: 90,
slide: function(event, ui) {
// Change the speed label.
$('#speed-label').html('Speed: ' + ui.value + '%');
// Scale the speed.
teleop.scale = (ui.value / 100.0);
}
});
// Set the initial speed .
$('#speed-label').html('Speed: ' + ($('#speed-slider').slider('value')) + '%');
teleop.scale = ($('#speed-slider').slider('value') / 100.0);
}
*/
function showInfo(s) {
if (s) {
for (var child = info.firstChild; child; child = child.nextSibling) {
if (child.style) {
child.style.display = child.id == s ? 'inline' : 'none';
}
}
info.style.visibility = 'visible';
} else {
info.style.visibility = 'hidden';
}
}
function startButton(event) {
console.log('startButton event');
if (recognizing) {
recognition.stop();
console.log('recognition stopped')
noRestartReco = true;
setMicInactive()
showInfo('info_none');
return;
}
if (!(recognition || 0)) {
startRecognition ();
}
if (recognition || 0) {
recognition.lang = "en-US";
recognition.start();
noRestartReco = false;
//final_span.innerHTML = '';
//cmd_err_span.innerHTML = '';
setMicOff ()
showInfo('info_allow');
// showButtons('none');
startTimestamp = event.timeStamp;
}
}
function restartReco() {
// console.log('restart recognition');
recognition.start();
noRestartReco = false;
recognizing = true;
setMicActive ();
}
// var current_style;
//
// function showButtons(style) {
// if (style == current_style) {
// return;
// }
// current_style = style;
// copy_button.style.display = style;
// email_button.style.display = style;
// copy_info.style.display = 'none';
// email_info.style.display = 'none';
// }
function sendTwistMessage(xMove, zMove) {
xMove = xMove / 10000;
zMove = zMove / 200;
console.log ("sending twist x:" + xMove + " z:" + zMove);
var cmdVel = new ROSLIB.Topic({
ros : ros,
name : topicName,
messageType : 'geometry_msgs/Twist'
});
var twist = new ROSLIB.Message({
linear: {
x: xMove*speedFactor,
y: 0.0,
z: 0.0
},
angular: {
x: 0.0,
y: 0.0,
z: zMove*speedFactor
}
});
var reps = Math.max (1, Math.abs (twist.linear.x) > 0 ? linearRepeat : (Math.abs (twist.angular.z) > 0 ? angularRepeat : 1));
if (typeof cmdVel.ros != "undefined") { // this would be if we are not connected
publishCmd ();
}
function publishCmd() {
cmdVel.publish (twist);
if (reps > 1) {
setTimeout (publishCmd, repeatInterval);
reps = reps - 1;
}
}
}
function arrowUp () {
sendTwistMessage (linearSpeed, 0.0);
}
function arrowDown () {
sendTwistMessage (-linearSpeed, 0.0);
}
function arrowRight () {
sendTwistMessage (0.0, -angularSpeed);
}
function arrowLeft () {
sendTwistMessage (0.0, angularSpeed);
}
function stopButton () {
sendTwistMessage (0.0, 0.0);
}
// ----------------------------------------------------------------------
// Waypoints
// Waypoints are stored as parameters using the rosparam functions. They are <name value> pairs--both strings.
// The values are obtained by stringify from the location--that is, the robot pose.
// A value of 0 indicates that the waypoint has been removed.
// ----------------------------------------------------------------------
// ----------------------------------------------------------------------
// Get the value of a waypoint parameter
// -----------------------------------------------
function getWaypointValue(paramname) {
return new Promise(function(resolve, reject) {
var waypoint = new ROSLIB.Param({
ros : ros,
name : ''
});
waypoint.name = paramname;
waypoint.get (function(value) {
if (value !== "0") {
resolve (paramname); // it is an undeleted waypoint
// console.log (paramname + " has a value");
}
else {
resolve ("0") // it is an undeleted waypoint
// console.log (paramname + " has no value");
}
});
});
}
// ----------------------------------------------------------------------
// List the waypoints
// ----------------------------------------------------------------------
function listWaypoints () {
var count = 0;
var output = "";
var promises = [];
var waypoint = new ROSLIB.Param({
ros : ros,
name : ''
});
ros.getParams(function(params) { // first get the list of ROS params
// console.log("Params: " + params);
if (params.length == 0) {
say ("No parameters were found, let alone waypoints.");
} else { // look at all the params
for (var i = 0; i < params.length; i++) { // for each one: if a waypoint, get the value
if (params[i].search ("/waypoint/") == 0) { // "/waypoint/" is found at string [0]
promises.push(getWaypointValue(params[i]));
count++
}
}
if (count == 0) {
say ("No waypoints were found");
} else {
Promise.all(promises).then(function(waypoints) {
var counter = 0;
waypoints.forEach(function(data) {
if (data != "0") { // this would indicate a waypoint that has been removed
counter++;
output = output + ", " + data.substring(10);
}
});
if (counter == 0) {
say ("No waypoints were found");
} else if (counter == 1) {
say ("The only waypoint is " + output);
} else {
say ("The waypoints are " + output);
}
}).catch(function(err){
console.log(err);
});
}
}
});
}
function goToWaypoint (waypointName) {
var waypoint = new ROSLIB.Param({
ros : ros,
name : ''
});
waypoint.name = "waypoint/" + waypointName;
waypoint.get(function(value) {
if (!value) {
say ('Waypoint ' + waypointName + ' was not found');
// alert ('Waypoint ' + waypointName + ' was not found');
}
else {
console.log('Go to waypoint ' + waypointName + ': ' + value);
if (value == "0") {
say ('Waypoint ' + waypointName + ' has been removed');
} else {
value = value.replace ('translation', 'position'); // convert tf pose to geometry
value = value.replace ('rotation', 'orientation');
waypointPose = JSON.parse(value);
moveRobotToPose (waypointPose);
}
}
});
}
function setWaypoint (waypointName) {
// ----------------------------------------------------------------------
// Sets a rosparam to contain the waypoint
// ----------------------------------------------------------------------
var waypoint = new ROSLIB.Param({
ros : ros,
name : "waypoint/" + waypointName
});
function setWaypointParam (location) {
console.log ("Set waypoint " + waypoint.name + ": " + location);
waypoint.set(location);
}
// console.log ("getting the current pose");
getPose (setWaypointParam);
}
function SetWaypointZero (waypointName) {
// ----------------------------------------------------------------------
// Sets a rosparam to string zero, effectively removing it
// ----------------------------------------------------------------------
var waypoint = new ROSLIB.Param({
ros : ros,
name : "waypoint/" + waypointName
});
// console.log ("Set waypoint " + waypoint.name + ": 0 ");
waypoint.set("0");
}
function getPose(callbackPosition) {
// ----------------------------------------------------------------------
// Subscribing to the robot's Pose-- this method uses tfClient
// Calls the callback with the stringified pose
// ----------------------------------------------------------------------
// A ROSLIB.TFClient object is used to subscribe to TFs from ROS. The fixedFrame
// is the frame all requested transforms will be relative to.
// The thresholds are the amount a TF must change in order to be republished.
var tfClient = new ROSLIB.TFClient({
ros : ros,
fixedFrame : 'map',
angularThres : 0.01, // threshold--smaller movements won't be reported
transThres : 0.01
});
var msgString;
// We subscribe to the TF between the fixed frame ('map') and the 'base_link' frame.
// Any transforms between these two frames greater than the specified threshold will
// trigger the callback. The message returned is a ROS TF message.
tfClient.subscribe('base_link', function(message) {
tfClient.unsubscribe('base_link'); // we only need this once
msgString = JSON.stringify(message);
console.log ("tfClient pose in " + tfClient.fixedFrame + ": " + msgString);
callbackPosition (msgString);
/*
// Formats the pose.
// var now = new Date();
var translation = 'x: ' + message.translation.x
+ ', y: ' + message.translation.y
+ ', z: 0.0';
var rotation = 'x: ' + message.rotation.x
+ ', y: ' + message.rotation.y
+ ', z: ' + message.rotation.z
+ ', w: ' + message.rotation.w; >/
console.log ('Received message on ' + tfClient.name + ': #' + message.header.seq);
console.log (msgstring);
*/
/* format for insertion into a table
$('#poses > tbody > tr:first').after('<tr>'
+ '<td>' + now.toLocaleTimeString() + '</td>'
+ '<td>' + position + '</td>'
+ '<td>' + orientation + '</td>');
*/
});
}
function moveRobotToPose (movePose) {
var prevStatus = "";
var moveToPoseClient = new ROSLIB.ActionClient({
// object with following keys: * ros - the ROSLIB.Ros connection handle * serverName - the action server name * actionName - the action message name * timeout - the timeout length when connecting to the action server
ros : ros,
serverName : 'move_base',
actionName : 'move_base_msgs/MoveBaseAction'
});
var goal = new ROSLIB.Goal({
actionClient : moveToPoseClient,
goalMessage : {
target_pose : {
header : {
frame_id : '/map'
},
pose : movePose // move_base_msg
}
}
});
goal.on('status', function(status) {
statusString = 'Move to pose status: ' + JSON.stringify(status);
if (statusString !== prevStatus) {
prevStatus = statusString;
if (status.status == 4) {
say (status.text);
}
console.log (statusString);
}
// moveClient.cancel (); this does not stop the damn messages anyhow
});
goal.send();
console.log ('moveRobotToPose goal sent');
}
function moveRobotFromPose (distance, angle) {
var statusCount = 0;
var prevStatus = "";
var moveClient = new ROSLIB.ActionClient({
ros : ros,
serverName : 'move_base',
actionName : 'move_base_msgs/MoveBaseAction'
});
function yawToQuaternion(yaw) {
return { x : 0,
y : 0,
z : Math.sin (yaw/2),
w : Math.cos (yaw/2)
};
}
var goal = new ROSLIB.Goal({
actionClient : moveClient,
goalMessage : {
target_pose : {
header : {
frame_id : '/base_link', // '/base_footprint', doesn't seem to work on Loki, tho it does on Stage
},
pose : {
position : {
x : distance,
y : 0,
z : 0
},
orientation : yawToQuaternion (angle)
}
}
}
});
goal.on('status', function(status) {
statusCount++;
statusString = 'Move robot status: ' + JSON.stringify(status);
if (statusString !== prevStatus) {
prevStatus = statusString;
if (status.status == 4) {
say (status.text);
}
console.log (statusCount + ": " + statusString);
}
// moveClient.cancel (); this does not stop the damn messages
});
/******** This never seems to be called!
goal.on('result', function(result) {
console.log ('Move robot result: ' + JSON.stringify(result));
console.log ("Result: " + JSON.stringify (result));
moveClient.cancel ();
});
******************/
goal.send();
}
/*
function getOdometry (callbackPosition) {
// ----------------------------------------------------------------------
// Subscribing to the robot's Pose-- this is one method
// ----------------------------------------------------------------------
// The ROSLIB.Topic handles subscribing and publishing a ROS topic. This
// topic interacts with the odom topic, published by the robot.
var odomTopic = new ROSLIB.Topic({
ros : ros,
name : 'odom',
messageType : 'nav_msgs/Odometry'
});
// Subscribes to the robot's odom topic, which includes the pose. When rosbridge receives the pose
// message from ROS, it forwards the message to roslibjs, which calls this callback.
odomTopic.subscribe(function(message) {
// Formats the pose.
// var now = new Date();
//TODO this is where we should place the robot command to move to the desired location.
var position = 'x: ' + message.pose.pose.position.x
+ ', y: ' + message.pose.pose.position.y
+ ', z: 0.0';
var orientation = 'x: ' + message.pose.pose.orientation.x
+ ', y: ' + message.pose.pose.orientation.y
+ ', z: ' + message.pose.pose.orientation.z
+ ', w: ' + message.pose.pose.orientation.w;
odomTopic.unsubscribe();
console.log ('Received message on ' + odomTopic.name + ': #' + message.header.seq);
console.log (position);
console.log (orientation);
callbackPosition ();
// $('#poses > tbody > tr:first').after('<tr>'
// + '<td>' + now.toLocaleTimeString() + '</td>'
// + '<td>' + position + '</td>'
// + '<td>' + orientation + '</td>');
});
}
*/
function mute () {
if (muted === true) {
muted = false;
document.getElementById("muteButton").innerHTML = "Mute";
} else {
muted = true;
document.getElementById("muteButton").innerHTML = "Unmute";
}
}
function toggleWakeup () {
useWakeup = !useWakeup;
setWakeupButton ()
}
function setWakeupButton () {
if (useWakeup === false) {
document.getElementById("wakeupButton").innerHTML = 'Require wakeup word "' + wakeup[0] + '"';
document.getElementById("commandHeader").innerHTML = "<strong>Commands</strong>";
} else {
document.getElementById("wakeupButton").innerHTML = "Don't use wakeup word";
document.getElementById("commandHeader").innerHTML = '<strong>Commands--must be preceeded by the word "Robot"</strong>';
}
}
</script>
</body>
</html>