forked from marva/sumorobot-web
448 lines
17 KiB
JavaScript
Executable File
448 lines
17 KiB
JavaScript
Executable File
/* the server URL */
|
|
//var robotServer = "10.42.0.1";
|
|
var robotServer = "iot.koodur.com";
|
|
/* enable disable remote control */
|
|
var remoteControl = false;
|
|
/* the sumorobot state */
|
|
var sumostart = false;
|
|
/* the sumorobot object */
|
|
var sumorobot = null;
|
|
/* Blockly workspace */
|
|
var workspace = null;
|
|
/* last hightlighted block id */
|
|
var highlighted = "";
|
|
/* disable enable hotkeys */
|
|
var hotkeys = true;
|
|
/* the sumorobot code */
|
|
var sumocode = "";
|
|
/* block highlight WebSocket */
|
|
var block_highlight = null;
|
|
|
|
var Sumorobot = function(wsUri) {
|
|
/* assign the WebSocket URI */
|
|
this.wsUri = wsUri;
|
|
/* start connecting to the WebSocket */
|
|
this.connect();
|
|
/* to ping the robot */
|
|
this.watchdogTimer = null;
|
|
};
|
|
|
|
Sumorobot.prototype.connect = function() {
|
|
/* to have access to this object */
|
|
var self = this;
|
|
this.websocket = new WebSocket(this.wsUri);
|
|
/* when the WebSocket gets connected */
|
|
this.websocket.onopen = function(evt) {
|
|
console.log("INFO websocket connected");
|
|
/* setup a timer to ping the robot */
|
|
self.watchdogTimer = setInterval(function() {
|
|
/* send a ping to the robot */
|
|
//console.log("ping the robot")
|
|
self.send("ping");
|
|
}, 2000);
|
|
};
|
|
/* when the WebSocket closes */
|
|
this.websocket.onclose = function(evt) {
|
|
console.log("INFO websocket disconnected");
|
|
/* clear the pinging */
|
|
clearInterval(self.watchdogTimer);
|
|
/* Try to recnnect to the sumorobot */
|
|
self.connect();
|
|
};
|
|
/* when there is a message from the WebSocket */
|
|
this.websocket.onmessage = function(evt) {
|
|
/* when scope is received */
|
|
var data = evt.data.replace(/'/g, '"').toLowerCase();
|
|
console.log(data);
|
|
var battery = JSON.parse(data)["battery_voltage"];
|
|
battery = Math.round((((battery - 3.0) * 100) / (4.2 - 3.0)));
|
|
$("#battery").html(battery + "%");
|
|
//$("#bar").html("Battery");
|
|
$("#bar").addClass("connected");
|
|
};
|
|
/* 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 */
|
|
if (this.websocket.readyState == 1) {
|
|
this.websocket.send(msg);
|
|
}
|
|
};
|
|
|
|
window.onload = function() {
|
|
/* function to update the control panel */
|
|
function updateControlPanel() {
|
|
/* hide all buttons and text fields */
|
|
$(".robot-id, .robot-nr, .btn-robot-nr").hide();
|
|
/* show the first button and text field */
|
|
$(".robot-id:eq(0), .robot-nr:eq(0), .btn-robot-nr:eq(0)").show();
|
|
|
|
/* adjust the buttons and text fields to be in the middle */
|
|
$(".input-group, .btn-group-robot").css("width", "20%");
|
|
$(".input-group, .btn-group-robot").css("margin-left", "40%");
|
|
|
|
/* hide the robot add button */
|
|
$(".btn-robot-add").hide();
|
|
|
|
/* populate robots IDs and buttons */
|
|
for (var i = 0; i < 5; i++) {
|
|
var id = getLocalStorageItem("sumorobot.robotID" + i);
|
|
if (id) {
|
|
$(".robot-id:eq(" + i + ")").val(id);
|
|
$(".robot-id:eq(" + i + "), .robot-nr:eq(" + i + "), .btn-robot-nr:eq(" + i + ")").show();
|
|
$(".input-group, .btn-group-robot").css("width", 20 + (i * 20) + "%");
|
|
$(".input-group, .btn-group-robot").css("margin-left", 40 - (i * 10) + "%");
|
|
} else {
|
|
/* when no robots yet added */
|
|
if (i != 0) {
|
|
/* show the robot add button */
|
|
$(".btn-robot-add").show();
|
|
$(".btn-group-robot").css("width", 20 + (i * 20) + "%");
|
|
$(".input-group, .btn-group-robot").css("margin-left", 40 - (i * 10) + "%");
|
|
/* add click listener to the robot add button */
|
|
$(".btn-robot-add").click(function() {
|
|
$(".robot-id:eq(" + i + "), .robot-nr:eq(" + i + "), .btn-robot-nr:eq(" + i + ")").show();
|
|
$(".input-group, .btn-group-robot").css("width", 20 + (i * 20) + "%");
|
|
$(".input-group, .btn-group-robot").css("margin-left", 40 - (i * 10) + "%");
|
|
$(this).hide();
|
|
});
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* load the control panel */
|
|
updateControlPanel();
|
|
|
|
/* remove previous and next statement from if block */
|
|
Blockly.Blocks.controls_if.init = function() {
|
|
this.setHelpUrl(Blockly.Msg.CONTROLS_IF_HELPURL);
|
|
this.setColour(Blockly.Blocks.logic.HUE);
|
|
this.appendValueInput("IF0").setCheck("Boolean").appendField(Blockly.Msg.CONTROLS_IF_MSG_IF);
|
|
this.appendStatementInput("DO0").appendField(Blockly.Msg.CONTROLS_IF_MSG_THEN);
|
|
this.setPreviousStatement(0);
|
|
this.setNextStatement(0);
|
|
this.setMutator(new Blockly.Mutator(["controls_if_elseif","controls_if_else"]));
|
|
var a = this;
|
|
this.setTooltip(function() {
|
|
if (a.elseifCount_ || a.elseCount_) {
|
|
if(!a.elseifCount_ && a.elseCount_) return Blockly.Msg.CONTROLS_IF_TOOLTIP_2;
|
|
if(a.elseifCount_ && !a.elseCount_) return Blockly.Msg.CONTROLS_IF_TOOLTIP_3;
|
|
if(a.elseifCount_ && a.elseCount_) return Blockly.Msg.CONTROLS_IF_TOOLTIP_4
|
|
} else return Blockly.Msg.CONTROLS_IF_TOOLTIP_1;
|
|
return "";
|
|
});
|
|
this.elseCount_=this.elseifCount_ = 0;
|
|
};
|
|
/* magnify the tool icon on the if block */
|
|
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;
|
|
};
|
|
|
|
/* change to python code */
|
|
Blockly.Sumorobot['controls_if'] = function(block) {
|
|
// If/elseif/else condition.
|
|
var n = 0;
|
|
var argument = Blockly.Sumorobot.valueToCode(block, 'IF' + n,
|
|
Blockly.Sumorobot.ORDER_NONE) || 'False';
|
|
var branch = Blockly.Sumorobot.statementToCode(block, 'DO' + n);
|
|
var code = 'if ' + argument + ':\n' + branch;
|
|
if (branch === '') {
|
|
code += ' pass\n';
|
|
}
|
|
for (n = 1; n <= block.elseifCount_; n++) {
|
|
argument = Blockly.Sumorobot.valueToCode(block, 'IF' + n,
|
|
Blockly.Sumorobot.ORDER_NONE) || 'False';
|
|
branch = Blockly.Sumorobot.statementToCode(block, 'DO' + n);
|
|
code += 'elif ' + argument + ':\n' + branch;
|
|
if (branch === '') {
|
|
code += ' pass\n';
|
|
}
|
|
}
|
|
if (block.elseCount_) {
|
|
branch = Blockly.Sumorobot.statementToCode(block, 'ELSE');
|
|
code += 'else:\n' + branch;
|
|
if (branch === '') {
|
|
code += ' pass';
|
|
}
|
|
}
|
|
return code + '\n';
|
|
};
|
|
|
|
Blockly.Sumorobot['sumorobot_delay'] = function(block) {
|
|
var code = 'sumorobot.sleep(' + parseFloat(block.getFieldValue('DELAY')) + ', "' + block.id + '")\n';
|
|
return code;
|
|
};
|
|
|
|
Blockly.Sumorobot['sumorobot_move'] = function(block) {
|
|
var code = 'sumorobot.move(' + block.getFieldValue('MOVE') + ', "' + block.id + '")\n';
|
|
return code;
|
|
};
|
|
|
|
Blockly.Sumorobot['sumorobot_enemy'] = function(block) {
|
|
var code = 'sumorobot.is_enemy("' + block.id + '")';
|
|
return [code, Blockly.Sumorobot.ORDER_ATOMIC];
|
|
};
|
|
|
|
Blockly.Sumorobot['sumorobot_line'] = function(block) {
|
|
var code = 'sumorobot.is_line(' + block.getFieldValue('LINE') + ', "' + block.id + '")';
|
|
return [code, Blockly.Sumorobot.ORDER_ATOMIC];
|
|
};
|
|
|
|
/* change the if block to be more cheerful */
|
|
Blockly.Blocks.logic.HUE = '#44CC00';
|
|
var blocklyArea = document.getElementById('blocklyArea');
|
|
var blocklyDiv = document.getElementById('blocklyDiv');
|
|
workspace = Blockly.inject(blocklyDiv, {
|
|
scrollbars: false,
|
|
media: '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);
|
|
|
|
/* 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 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)
|
|
}
|
|
|
|
/* retrieve the blocks */
|
|
var xml = Blockly.Xml.textToDom(getLocalStorageItem("sumorobot.currentProgram"));
|
|
/* resume the blocks */
|
|
Blockly.Xml.domToWorkspace(xml, workspace);
|
|
|
|
/* on blockly code change */
|
|
function onCodeChanged(event) {
|
|
/* 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.Sumorobot.workspaceToCode(workspace);
|
|
|
|
/* show the code to the user, filter out block IDs */
|
|
document.getElementById("blocklyCode").value = sumocode.replace(/[,]?[ ]?"(.*?)"/g, "");
|
|
|
|
/* save the code to the local storage */
|
|
var xml = Blockly.Xml.workspaceToDom(workspace);
|
|
localStorage.setItem("sumorobot.currentProgram", Blockly.Xml.domToText(xml));
|
|
|
|
/* if the if condition block is used */
|
|
if (sumocode.indexOf("if") != -1) {
|
|
/* disable the if condition block */
|
|
$("block[type=controls_if]").replaceWith("<block type='controls_if' disabled='true'></block>");
|
|
workspace.updateToolbox(document.getElementById("toolbox"));
|
|
} else {
|
|
/* enable the if condition block */
|
|
$("block[type=controls_if]").replaceWith("<block type='controls_if'></block>");
|
|
workspace.updateToolbox(document.getElementById("toolbox"));
|
|
}
|
|
}
|
|
|
|
/* add a change listener to Blockly */
|
|
workspace.addChangeListener(onCodeChanged);
|
|
|
|
/* key down event */
|
|
$(document).keydown(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.target).is("textarea") || $(e.target).is("[type=text]") || $(e.target).is("[class=blocklyHtmlInput]")) return;
|
|
/* 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 49: // 1
|
|
$(".btn-robot-nr:eq(0)").click();
|
|
break;
|
|
case 50: // 2
|
|
$(".btn-robot-nr:eq(1)").click();
|
|
break;
|
|
case 51: // 3
|
|
$(".btn-robot-nr:eq(2)").click();
|
|
break;
|
|
case 52: // 4
|
|
$(".btn-robot-nr:eq(3)").click();
|
|
break;
|
|
case 53: // 5
|
|
$(".btn-robot-nr:eq(4)").click();
|
|
break;
|
|
case 67: // c
|
|
updateControlPanel();
|
|
$("#panel").toggle();
|
|
break;
|
|
case 76: // l
|
|
$("#stream").toggle();
|
|
case 82: // r
|
|
$("#remote-control").click();
|
|
break;
|
|
case 83: // s
|
|
$(".btn-stop").addClass("hover");
|
|
$(".btn-stop").click();
|
|
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.target).is("textarea") || $(e.target).is("[type=text]") || $(e.target).is("[class=blocklyHtmlInput]")) 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-close").click(function() {
|
|
$("#stream").hide();
|
|
});
|
|
|
|
/* start button listener */
|
|
$(".btn-start").click(function() {
|
|
sumostart = true;
|
|
sumorobot.send("start:" + sumocode.replace(/sumorobot./g, ""));
|
|
});
|
|
|
|
/* stop button listener */
|
|
$(".btn-stop").click(function() {
|
|
sumostart = false;
|
|
sumorobot.send("stop");
|
|
workspace.highlightBlock(highlighted, false);
|
|
});
|
|
|
|
/* remote control checkbox listener */
|
|
$("#remote-control").click(function() {
|
|
remoteControl = $("#remote-control").is(":checked");
|
|
});
|
|
|
|
/* robot number button listener */
|
|
$(".btn-robot-nr").click(function() {
|
|
/* extract and validate the selected robot ID */
|
|
var index = $(".btn-robot-nr").index($(this));
|
|
var robotID = $(".robot-id:eq(" + index + ")").val();
|
|
if (robotID.trim() === "") {
|
|
$(".robot-nr:eq(" + index + "), .robot-id:eq(" + index + ")").addClass("has-error");
|
|
return;
|
|
} else {
|
|
$(".robot-nr:eq(" + index + "), .robot-id:eq(" + index + ")").removeClass("has-error");
|
|
}
|
|
/* highlight the selected robot button */
|
|
$(".btn-robot-nr").removeClass("btn-selected");
|
|
$(this).addClass("btn-selected");
|
|
/* update robot IDs in local storage */
|
|
setLocalStorageItem("sumorobot.robotID" + index, robotID);
|
|
/* connect to the selected robots WebSocket */
|
|
sumorobot = new Sumorobot("ws://" + robotServer + ":80/p2p/browser/" + robotID + "/");
|
|
/* connect to the other block highlight WebSocket */
|
|
block_highlight = new WebSocket("ws://" + robotServer + ":80/p2p/browser/" + robotID + "-highlight/");
|
|
/* when there is a message from the WebSocket */
|
|
block_highlight.onmessage = function(evt) {
|
|
/* when scope is received */
|
|
if (evt.data.length == 20 && sumostart) {
|
|
workspace.highlightBlock(evt.data);
|
|
highlighted = evt.data;
|
|
}
|
|
};
|
|
/* hide the configuration panel */
|
|
$("#panel").hide();
|
|
});
|
|
|
|
$("#stream").prepend('<iframe width="100%" height="91%" allowfullscreen="true" src="https://mixer.com/embed/player/14551694"></iframe>');
|
|
|
|
/* try to close if block bubble canvas */
|
|
/*$(document).click(function(e) {
|
|
var target = e.target;
|
|
if (!$(target).is('.blocklyBubbleCanvas') && !$(target).parents().is('.blocklyBubbleCanvas')) {
|
|
if (!$(target).is('.blocklyIconGroup') && !$(target).parents().is('.blocklyIconGroup'))
|
|
$('.blocklyBubbleCanvas').empty();
|
|
}
|
|
});*/
|
|
}
|