create separate sumorobot class

This commit is contained in:
Silver Kuusik 2018-07-08 20:51:45 +02:00 committed by GitHub
parent b03e7374a5
commit 93972a3972
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 63 additions and 557 deletions

View File

@ -1,595 +1,101 @@
/* disable enable hotkeys */
var hotkeys = true;
/* enable disable remote control */
var remoteControl = false;
/* the local/remote server URL */
//var robotServer = "10.42.0.1";
var robotServer = "ws.achex.ca:4010";
/* ace editor object */
var codingEditor = null;
/* read only ace editor object */
var readOnlyCodingEditor = null;
// sumorobot ID
var robotId = "";
/* the sumorobot code */
var sumocode = "";
/* the sumorobot object */
var sumorobot = null;
/* Blockly workspace */
var workspace = null;
/* the sumorobot state */
var sumostart = false;
/* disable / enable coding mode */
var codingEnabled = false;
/* disable / enable live stream */
var liveStreamVisible = false;
/* connection watchdog counter */
var watchdogCounter = 0;
/* control_if block id */
var controlBlockId = "";
/* last hightlighted block id */
var lastHighlighted = "";
/* block highlight WebSocket */
var blockHighlight = null;
var Sumorobot = function(wsUri) {
/* assign the WebSocket URI */
// Sumorobot constructor
var Sumorobot = function(wsUri, robotId) {
// Assign the WebSocket URI
this.wsUri = wsUri;
/* start connecting to the WebSocket */
this.connect();
/* to ping the robot */
// Assign the SumoRobot ID
this.robotId = robotId;
// To keep track of the WebSocket connection
this.watchdogTimer = 0;
// Timer to ping the SumoRobot
this.watchdogTimer = null;
// To store Blockly code
this.blocklyCode = "";
// Start connecting to the WebSocket
this.connect();
};
// Function to initiate the WebSocket connection
Sumorobot.prototype.connect = function() {
/* to have access to this object */
// To have access to this object inside events
var self = this;
this.websocket = new WebSocket(this.wsUri);
/* setup connection watchdog interval */
// Setup connection watchdog interval
setInterval(function() {
if (watchdogCounter == 0) {
if (self.watchdogCounter == 0) {
$("#battery").removeClass("connected");
$("#battery").html("Disconnected");
}
/* reset watchdog counter */
watchdogCounter = 0;
// Reset watchdog counter
self.watchdogCounter = 0;
}, 3000);
/* when the WebSocket gets connected */
// When the WebSocket gets connected
this.websocket.onopen = function(evt) {
console.log("INFO websocket connected");
// send authentication message
sumorobot.send('{"setID": "browser-' + robotId + '@00000514", "passwd": "salakala"}');
/* setup a timer to ping the robot */
// Send authentication packet
self.websocket.send(`{"setID": "browser-${self.robotId}@00000514", "passwd": "salakala"}`);
// Setup a timer to ping the robot
self.watchdogTimer = setInterval(function() {
/* send a ping to the robot */
//console.log("ping the robot")
self.send('{"to": "sumo-' + robotId + '@00000514", "cmd": "ping"}');
// Send a ping to the robot
self.send(self.robotId, "ping");
}, 500);
};
/* when the WebSocket closes */
// When the WebSocket closes
this.websocket.onclose = function(evt) {
console.log("INFO websocket disconnected");
/* clear the pinging */
// Clear the pinging
clearInterval(self.watchdogTimer);
/* Try to recnnect to the sumorobot */
// Try to recnnect to the sumorobot
self.connect();
};
/* when there is a message from the WebSocket */
// When there is a message from the WebSocket
this.websocket.onmessage = function(evt) {
/* when scope is received */
// When scope is received
var data = evt.data.replace(/'/g, '"').toLowerCase();
console.log(data);
// Get SumoRobot battery voltage
var battery = JSON.parse(data)["battery_voltage"];
$("#battery").html(battery + "V");
$("#battery").addClass("connected");
/* keep track of data packets */
/* to see if we have a connection to the robot */
watchdogCounter += 1;
// When sensor data received
if (battery) {
$("#battery").html(battery + "V");
$("#battery").addClass("connected");
}
// Count data received packets
self.watchdogCounter += 1;
};
/* when there is an WebSocket error */
// When there is an WebSocket error
this.websocket.onerror = function(err) {
console.log("ERROR websocket error: " + err);
};
};
Sumorobot.prototype.send = function(msg) {
/* ready state constants: CONNECTING 0, OPEN 1, CLOSING 2, CLOSED 3 */
/* https://developer.mozilla.org/en-US/docs/Web/API/WebSocket */
// Function to send WebSocket data
Sumorobot.prototype.send = function(msg, val) {
// Ready state constants: CONNECTING 0, OPEN 1, CLOSING 2, CLOSED 3
// https://developer.mozilla.org/en-US/docs/Web/API/WebSocket
if (this.websocket.readyState == 1) {
this.websocket.send(msg);
if (val === 'undefined') {
console.log("no val")
this.websocket.send(`{"to": "sumo-${this.robotId}@00000514", "cmd": "${msg}"}`);
} else {
console.log("val")
this.websocket.send(`{"to": "sumo-${this.robotId}@00000514", "cmd": "${msg}", "val": "${val}"}`);
}
}
};
// Function to close the WebSocket connection
Sumorobot.prototype.close = function() {
/* close the WebSocket connection */
this.websocket.close();
// When a WebSocket connection exists
if (this.websocket !== 'undefined') {
// Close the WebSocket connection
this.websocket.close();
}
};
/* function to set local storage */
function getLocalStorageItem(item) {
/* when the local storage doesn't exist, return empty string */
if (typeof(Storage) === "undefined") return "";
/* otherwise return item from the local storage*/
return localStorage.getItem(item);
}
// Function to get SumoRobot Blockly code
Sumorobot.prototype.getBlocklyCode = function() {
return this.blocklyCode;
};
/* function to set local storage */
function setLocalStorageItem(item, value) {
/* when local storage doesn't exist, return */
if (typeof(Storage) === "undefined") return;
/* otherwise set the item to the local storage */
localStorage.setItem(item, value)
}
window.onload = function() {
$("#robot-id").val(getLocalStorageItem("sumorobot.robotId"));
/* load read only ace editor */
readOnlyCodingEditor = ace.edit("readOnlyBlocklyCode");
/* set the style */
readOnlyCodingEditor.setTheme("ace/theme/textmate");
readOnlyCodingEditor.session.setMode("ace/mode/python");
readOnlyCodingEditor.session.setTabSize(2);
/* make as read only */
readOnlyCodingEditor.setReadOnly(true);
/* disable scrolling warning */
readOnlyCodingEditor.$blockScrolling = Infinity;
/* load ace editor */
codingEditor = ace.edit("blocklyCode");
/* set the style */
codingEditor.setTheme("ace/theme/textmate");
codingEditor.session.setMode("ace/mode/python");
codingEditor.session.setTabSize(2);
/* disable scrolling warning */
codingEditor.$blockScrolling = Infinity;
/* enable autocomplete */
ace.require("ace/ext/language_tools");
codingEditor.setOptions({
enableSnippets: true,
enableLiveAutocompletion: true,
enableBasicAutocompletion: true
});
/* add autocomplete keywords */
codingEditor.completers.push({
getCompletions: function(editor, session, pos, prefix, callback) {
callback(null, [
{value: "STOP", score: 1000, meta: "sumorobot"},
{value: "LEFT", score: 1000, meta: "sumorobot"},
{value: "RIGHT", score: 1000, meta: "sumorobot"},
{value: "SEARCH", score: 1000, meta: "sumorobot"},
{value: "FORWARD", score: 1000, meta: "sumorobot"},
{value: "BACKWARD", score: 1000, meta: "sumorobot"},
{value: "STATUS", score: 1000, meta: "sumorobot"},
{value: "LEFT_LINE", score: 1000, meta: "sumorobot"},
{value: "RIGHT_LINE", score: 1000, meta: "sumorobot"},
{value: "sumorobot", score: 1000, meta: "sumorobot"},
{value: "move", score: 1000, meta: "sumorobot"},
{value: "sleep", score: 1000, meta: "sumorobot"},
{value: "set_led", score: 1000, meta: "sumorobot"},
{value: "is_line", score: 1000, meta: "sumorobot"},
{value: "get_line", score: 1000, meta: "sumorobot"},
{value: "set_servo", score: 1000, meta: "sumorobot"},
{value: "is_opponent", score: 1000, meta: "sumorobot"},
{value: "calibrate_line", score: 1000, meta: "sumorobot"},
{value: "get_battery_voltage", score: 1000, meta: "sumorobot"},
{value: "get_opponent_distance", score: 1000, meta: "sumorobot"}
]);
}
});
/* set the code to the saved code from local storage or empty */
codingEditor.setValue(getLocalStorageItem("sumorobot.code") || "");
/* clear the selection after setting the value */
codingEditor.clearSelection();
/* add an change listener for the code editor */
codingEditor.on("change", function() {
/* when change occurs, save the new code to the localstorage */
setLocalStorageItem("sumorobot.code", codingEditor.getValue())
});
/* change the if block to be more cheerful */
Blockly.Msg.LOGIC_HUE = '#44CC00';
/* remote previous and next statement from control_if block */
Blockly.defineBlocksWithJsonArray([
{
"type": "controls_if",
"message0": "%{BKY_CONTROLS_IF_MSG_IF} %1",
"args0": [
{
"type": "input_value",
"name": "IF0",
"check": "Boolean"
}
],
"message1": "%{BKY_CONTROLS_IF_MSG_THEN} %1",
"args1": [
{
"type": "input_statement",
"name": "DO0"
}
],
"colour": "%{BKY_LOGIC_HUE}",
"helpUrl": "%{BKY_CONTROLS_IF_HELPURL}",
"mutator": "controls_if_mutator",
"extensions": ["controls_if_tooltip"]
}
]);
/* make control_if mutator icon bigger */
Blockly.Icon.prototype.renderIcon = function(cursorX) {
if (this.collapseHidden && this.block_.isCollapsed()) {
this.iconGroup_.setAttribute('display', 'none');
return cursorX;
}
this.iconGroup_.setAttribute('display', 'block');
var SIZE = 1.7;
var TOP_MARGIN = 2;
var LEFT_MARGIN = 5;
var width = this.SIZE;
if (this.block_.RTL) {
cursorX -= width;
}
this.iconGroup_.setAttribute('transform',
'translate(' + LEFT_MARGIN + ',' + TOP_MARGIN + ') scale(' + SIZE + ')');
this.computeIconLocation();
if (this.block_.RTL) {
cursorX -= Blockly.BlockSvg.SEP_SPACE_X;
} else {
cursorX += width + Blockly.BlockSvg.SEP_SPACE_X;
}
return cursorX;
};
/* when mouse click occures on Blockly workspace */
Blockly.utils.isRightButton = function(e) {
var target = e.target;
/* when control_if block is in use */
if (controlBlockId != "") {
/* when the user clicks anywhere outside the mutator and not on the mutator icon */
if (!$(target).is('.blocklyBubbleCanvas') && !$(target).parents().is('.blocklyBubbleCanvas')) {
if (!$(target).is('.blocklyIconGroup') && !$(target).parents().is('.blocklyIconGroup')) {
/* hide the mutator */
workspace.getBlockById(controlBlockId).mutator.setVisible(false);
}
}
}
/* disable right click on Blockly workspace */
return false;
};
Blockly.Blocks['sumorobot_sleep'] = {
init: function() {
this.setColour("#E64C00");
this.appendDummyInput()
.appendField("sleep")
.appendField(new Blockly.FieldTextInput('1000',
Blockly.FieldNumber.numberValidator), 'SLEEP');
this.setPreviousStatement(true);
this.setNextStatement(true);
}
};
Blockly.Blocks['sumorobot_move'] = {
init: function() {
var OPERATORS = [
['move stop', 'STOP'],
['move left', 'LEFT'],
['move right', 'RIGHT'],
['move search', 'SEARCH'],
['move forward', 'FORWARD'],
['move backward', 'BACKWARD']
];
this.setColour("#E60000");
var dropdown = new Blockly.FieldDropdown(OPERATORS);
this.appendDummyInput().appendField(dropdown, 'MOVE');
this.setPreviousStatement(true);
this.setNextStatement(true);
}
};
Blockly.Blocks['sumorobot_opponent'] = {
init: function() {
this.setColour("#0099E6");
this.appendDummyInput().appendField('opponent');
this.setOutput(true, 'Boolean');
}
};
Blockly.Blocks['sumorobot_line'] = {
init: function() {
var OPERATORS = [
['line left', 'LEFT'],
['line right', 'RIGHT']
];
this.setColour("#E6BF00");
var dropdown = new Blockly.FieldDropdown(OPERATORS);
this.appendDummyInput().appendField(dropdown, 'LINE');
this.setOutput(true, 'Boolean');
}
};
Blockly.Python['sumorobot_sleep'] = function(block) {
var code = 'sumorobot.sleep(' + parseFloat(block.getFieldValue('SLEEP')) + ', "' + block.id + '")\n';
return code;
};
Blockly.Python['sumorobot_move'] = function(block) {
var code = 'sumorobot.move(' + block.getFieldValue('MOVE') + ', "' + block.id + '")\n';
return code;
};
Blockly.Python['sumorobot_opponent'] = function(block) {
var code = 'sumorobot.is_opponent("' + block.id + '")';
return [code, Blockly.Python.ORDER_ATOMIC];
};
Blockly.Python['sumorobot_line'] = function(block) {
var code = 'sumorobot.is_line(' + block.getFieldValue('LINE') + ', "' + block.id + '")';
return [code, Blockly.Python.ORDER_ATOMIC];
};
/* inject Blockly */
var blocklyArea = document.getElementById('blocklyArea');
var blocklyDiv = document.getElementById('blocklyDiv');
workspace = Blockly.inject(blocklyDiv, {
scrollbars: false,
media: 'assets/blockly/media/',
trashcan: true,
sounds: true,
zoom: {
wheel: true,
controls: true,
startScale: 1.2
},
toolbox: document.getElementById('toolbox')
});
/* on Blockly resize */
var onresize = function(e) {
// compute the absolute coordinates and dimensions of blocklyArea.
var element = blocklyArea;
var x = 0;
var y = 0;
do {
x += element.offsetLeft;
y += element.offsetTop;
element = element.offsetParent;
} while (element);
/* position blocklyDiv over blocklyArea */
blocklyDiv.style.left = x + 'px';
blocklyDiv.style.top = y + 'px';
blocklyDiv.style.width = blocklyArea.offsetWidth + 'px';
blocklyDiv.style.height = blocklyArea.offsetHeight + 'px';
};
window.addEventListener('resize', onresize, false);
onresize();
Blockly.svgResize(workspace);
/* retrieve the blocks */
var xml = Blockly.Xml.textToDom(getLocalStorageItem("sumorobot.blockly"));
/* resume the blocks */
Blockly.Xml.domToWorkspace(xml, workspace);
/* on Blockly code change */
function onCodeChanged(event) {
/* if the if condition block was created */
if (event.type == Blockly.Events.CREATE && event.xml.getAttributeNode("type").nodeValue == "controls_if") {
/* remember the control_if block id */
controlBlockId = event.blockId;
/* get the control_if block object */
var block = workspace.getBlockById(event.blockId);
/* if the control_if block doesn't already have an else */
if (block.elseCount_ == 0) {
/* automatically add the else statement input */
block.elseCount_ = 1;
block.updateShape_();
}
/* if the if condition block was removed */
} else if (event.type == Blockly.Events.DELETE && event.oldXml.getAttributeNode("type").nodeValue == "controls_if") {
/* remove the control_if block id */
controlBlockId = "";
/* enable the if condition block */
workspace.updateToolbox(document.getElementById("toolbox"));
}
/* only process change and move commands */
if (event.type != Blockly.Events.CHANGE && event.type != Blockly.Events.MOVE) return;
/* generate code from the used blocks */
sumocode = Blockly.Python.workspaceToCode(workspace);
/* show the code in the ace editor, filter out block IDs */
readOnlyCodingEditor.setValue("\n" + sumocode.replace(/[,]?[ ]?"(.*?)"/g, ""));
readOnlyCodingEditor.clearSelection();
/* save the code to the local storage */
var xml = Blockly.Xml.workspaceToDom(workspace);
localStorage.setItem("sumorobot.blockly", Blockly.Xml.domToText(xml));
/* if control_if block is used */
if (controlBlockId != "") {
/* disable the if condition block */
workspace.updateToolbox(document.getElementById("toolbox_no_if"));
}
}
/* add a change listener to Blockly */
workspace.addChangeListener(onCodeChanged);
/* key down event */
$(document).keydown(function(e) {
/* if the hotkeys are disabled or the alt key is not pressed, don't use hotkeys */
if (hotkeys == false || e.altKey == false) return;
/* prevent typing in textfields */
e.preventDefault();
/* select the hotkey */
switch(e.which) {
case 32: // space bar
sumostart = !sumostart;
if (sumostart) {
$(".btn-start").addClass("hover");
$(".btn-start").click();
} else {
$(".btn-stop").addClass("hover");
$(".btn-stop").click();
}
break;
case 37: // left
if (remoteControl) sumorobot.send("left");
break;
case 38: // up
if (remoteControl) sumorobot.send("forward");
break;
case 39: // right
if (remoteControl) sumorobot.send("right");
break;
case 40: // down
if (remoteControl) sumorobot.send("backward");
break;
case 67: // c
$("#panel").toggle();
break;
case 76: // l
/* load the Mixer stream */
if ($("#stream").is(':empty')) {
$("#stream").html('<iframe src="https://mixer.com/embed/player/14551694"></iframe>');
}
$("#stream").toggle();
/* toggle live steam visible */
liveStreamVisible = !liveStreamVisible;
/* if not in coding mode */
if (codingEnabled == false) {
$("#readOnlyBlocklyCode").toggle();
}
break;
case 80: // p
$("#blocklyDiv").toggle();
$("#blocklyArea").toggle();
$("#blocklyCode").toggle();
if (liveStreamVisible == false) {
$("#readOnlyBlocklyCode").toggle();
}
/* toggle coding enabled */
codingEnabled = !codingEnabled;
if (codingEnabled) {
/* resize the coding editor */
codingEditor.resize();
/* focus, so the user can start coding */
codingEditor.focus();
}
break;
case 82: // r
/* toggle remote control */
remoteControl = !remoteControl;
break;
case 83: // s
$(".btn-stop").addClass("hover");
$(".btn-stop").click();
break;
case 84: // t
sumorobot.send("calibrate_line");
break;
case 87: // w
$(".btn-start").addClass("hover");
$(".btn-start").click();
break;
}
});
/* key up event */
$(document).keyup(function(e) {
/* if the hotkeys are disabled or the focused element is a textarea or text input, don't use hotkeys */
if (hotkeys == false || e.altKey == false) return;
/* remove hover from buttons */
$('.btn').removeClass('hover');
/* if arrow keys */
if (e.which == 37 || e.which == 38 || e.which == 39 || e.which == 40) {
if (remoteControl) sumorobot.send("stop");
}
});
/* start button listener */
$(".btn-start").click(function() {
sumostart = true;
/* if we are in coding mode */
if (codingEnabled) {
/* send the code from the textarea to the SumoRobot */
sumorobot.send('{"to": "sumo-' + robotId + '@00000514", "cmd": "code", "val": "' + codingEditor.getValue() + '"}');
/* otherwise when we are in Blockly mode */
} else {
/* send the code from the blocks to the SumoRobot */
sumorobot.send('{"to": "sumo-' + robotId + '@00000514", "cmd": "code", "val": "' + sumocode + '"}');
}
});
/* stop button listener */
$(".btn-stop").click(function() {
sumostart = false;
sumorobot.send('{"to": "sumo-' + robotId + '@00000514", "cmd": "stop"}');
workspace.highlightBlock(lastHighlighted, false);
});
/* enter listener on robot ID field */
$("#robot-id").keypress(function(e) {
if (e.which == 13) {
/* simulate robot GO button click */
$(".btn-robot-go").click();
}
});
/* robot number button listener */
$(".btn-robot-go").click(function() {
/* extract and validate the selected robot ID */
robotId = $("#robot-id").val().trim();
if (robotId === "" || /^([a-f0-9]{6})$/.test(robotId) == false) {
$("#robot-id, #robot-label").addClass("has-error");
return;
} else {
$("#robot-id, #robot-label").removeClass("has-error");
}
/* update robot IDs in local storage */
setLocalStorageItem("sumorobot.robotId", robotId);
/* in case there is a open connection */
if (sumorobot/* && blockHighlight*/) {
/* close the connections */
sumorobot.close();
//blockHighlight.close();
}
/* connect to the selected robots WebSocket */
sumorobot = new Sumorobot("ws://" + robotServer);
/* connect to the other block highlight WebSocket */
/*blockHighlight = new WebSocket("ws://" + robotServer + ":80/p2p/browser/" + robotId + "-highlight/");
// when there is a message from the WebSocket
blockHighlight.onmessage = function(evt) {
// when scope is received
if (evt.data.length == 20 && sumostart) {
workspace.highlightBlock(evt.data);
lastHighlighted = evt.data;
}
};*/
/* hide the configuration panel */
$("#panel").hide();
});
/* set a click listener on the document */
$(document).click(function(e) {
var target = e.target;
/* when control_if block is in use */
if (controlBlockId != "") {
/* when the user clicks anywhere outside the mutator and not on the mutator icon */
if (!$(target).is('.blocklyBubbleCanvas') && !$(target).parents().is('.blocklyBubbleCanvas')) {
if (!$(target).is('.blocklyIconGroup') && !$(target).parents().is('.blocklyIconGroup')) {
/* hide the mutator */
workspace.getBlockById(controlBlockId).mutator.setVisible(false);
}
}
}
});
}
// Function to set SumoRobot Blockly code
Sumorobot.prototype.setBlocklyCode = function(blocklyCode) {
this.blocklyCode = blocklyCode;
};