diff --git a/assets/js/sumorobot.js b/assets/js/sumorobot.js index 3083729..1f19b67 100755 --- a/assets/js/sumorobot.js +++ b/assets/js/sumorobot.js @@ -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(''); - } - $("#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; +};