From 20eb5c804bc17c7e7b767c14df2dbd5193ffba58 Mon Sep 17 00:00:00 2001 From: Christopher Willis-Ford Date: Tue, 2 Aug 2016 00:49:34 -0700 Subject: [PATCH] Update scratch_extensions, support Device Manager --- index.html | 1 + scratch_extensions/picoExtension.js | 99 +++--- scratch_extensions/scratch_deviceManager.js | 91 ++++++ scratch_extensions/scratch_ext.js | 262 +++++++++++----- scratch_extensions/scratch_nmh.js | 1 + scratch_extensions/scratch_plugin.js | 4 +- scratch_extensions/wedoExtension.js | 315 +++++++++++--------- 7 files changed, 505 insertions(+), 268 deletions(-) create mode 100644 scratch_extensions/scratch_deviceManager.js diff --git a/index.html b/index.html index 5b066a4..90f5335 100644 --- a/index.html +++ b/index.html @@ -680,6 +680,7 @@ + diff --git a/scratch_extensions/picoExtension.js b/scratch_extensions/picoExtension.js index 5001f02..dd2afe7 100644 --- a/scratch_extensions/picoExtension.js +++ b/scratch_extensions/picoExtension.js @@ -4,7 +4,7 @@ // // This is an extension for development and testing of the Scratch Javascript Extension API. -(function(ext) { +(function (ext) { var device = null; var rawData = null; @@ -30,24 +30,24 @@ 'resistance-D': 0 }; - ext.resetAll = function(){}; - // Hats / triggers - ext.whenSensorConnected = function(which) { + ext.whenSensorConnected = function (which) { return getSensorPressed(which); }; - ext.whenSensorPass = function(which, sign, level) { + ext.whenSensorPass = function (which, sign, level) { if (sign == '<') return getSensor(which) < level; return getSensor(which) > level; }; // Reporters - ext.sensorPressed = function(which) { + ext.sensorPressed = function (which) { return getSensorPressed(which); }; - ext.sensor = function(which) { return getSensor(which); }; + ext.sensor = function (which) { + return getSensor(which); + }; // Private logic function getSensorPressed(which) { @@ -65,6 +65,7 @@ } var inputArray = []; + function processData() { var bytes = new Uint8Array(rawData); @@ -74,10 +75,10 @@ // Right now there's no guarantee that our 18 bytes start at the beginning of a message. // Maybe we should treat the data as a stream of 2-byte packets instead of 18-byte packets. // That way we could just check the high bit of each byte to verify that we're aligned. - for(var i=0; i<9; ++i) { - var hb = bytes[i*2] & 127; + for (var i = 0; i < 9; ++i) { + var hb = bytes[i * 2] & 127; var channel = hb >> 3; - var lb = bytes[i*2+1] & 127; + var lb = bytes[i * 2 + 1] & 127; inputArray[channel] = ((hb & 7) << 7) + lb; } @@ -87,17 +88,17 @@ watchdog = null; } - for(var name in inputs) { + for (var name in inputs) { var v = inputArray[channels[name]]; - if(name == 'light') { + if (name == 'light') { v = (v < 25) ? 100 - v : Math.round((1023 - v) * (75 / 998)); } - else if(name == 'sound') { + else if (name == 'sound') { //empirically tested noise sensor floor - v = Math.max(0, v - 18) - v = (v < 50) ? v / 2 : + v = Math.max(0, v - 18); + v = (v < 50) ? v / 2 : //noise ceiling - 25 + Math.min(75, Math.round((v - 50) * (75 / 580))); + 25 + Math.min(75, Math.round((v - 50) * (75 / 580))); } else { v = (100 * v) / 1023; @@ -110,22 +111,22 @@ rawData = null; } - function appendBuffer( buffer1, buffer2 ) { - var tmp = new Uint8Array( buffer1.byteLength + buffer2.byteLength ); - tmp.set( new Uint8Array( buffer1 ), 0 ); - tmp.set( new Uint8Array( buffer2 ), buffer1.byteLength ); + function appendBuffer(buffer1, buffer2) { + var tmp = new Uint8Array(buffer1.byteLength + buffer2.byteLength); + tmp.set(new Uint8Array(buffer1), 0); + tmp.set(new Uint8Array(buffer2), buffer1.byteLength); return tmp.buffer; } // Extension API interactions var potentialDevices = []; - ext._deviceConnected = function(dev) { + ext._deviceConnected = function (dev) { potentialDevices.push(dev); if (!device) { tryNextDevice(); } - } + }; function tryNextDevice() { // If potentialDevices is empty, device will be undefined. @@ -133,24 +134,28 @@ device = potentialDevices.shift(); if (device) { - device.open({ stopBits: 0, bitRate: 38400, ctsFlowControl: 0 }, deviceOpened); + device.open({stopBits: 0, bitRate: 38400, ctsFlowControl: 0}, deviceOpened); } } var poller = null; var watchdog = null; + function deviceOpened(dev) { if (!dev) { // Opening the port failed. tryNextDevice(); return; } - device.set_receive_handler(function(data) { + device.set_receive_handler(function (data) { //console.log('Received: ' + data.byteLength); - if(!rawData || rawData.byteLength == 18) rawData = new Uint8Array(data); - else rawData = appendBuffer(rawData, data); + if (!rawData || rawData.byteLength == 18) { + rawData = new Uint8Array(data); + } else { + rawData = appendBuffer(rawData, data); + } - if(rawData.byteLength >= 18) { + if (rawData.byteLength >= 18) { //console.log(rawData); processData(); //device.send(pingCmd.buffer); @@ -160,10 +165,10 @@ // Tell the PicoBoard to send a input data every 50ms var pingCmd = new Uint8Array(1); pingCmd[0] = 1; - poller = setInterval(function() { + poller = setInterval(function () { device.send(pingCmd.buffer); }, 50); - watchdog = setTimeout(function() { + watchdog = setTimeout(function () { // This device didn't get good data in time, so give up on it. Clean up and then move on. // If we get good data then we'll terminate this watchdog. clearInterval(poller); @@ -173,26 +178,26 @@ device = null; tryNextDevice(); }, 250); - }; - - ext._deviceRemoved = function(dev) { - if(device != dev) return; - if(poller) poller = clearInterval(poller); - device = null; - }; - - ext._shutdown = function() { - if(device) device.close(); - if(poller) poller = clearInterval(poller); - device = null; - }; - - ext._getStatus = function() { - if(!device) return {status: 1, msg: 'PicoBoard disconnected'}; - if(watchdog) return {status: 1, msg: 'Probing for PicoBoard'}; - return {status: 2, msg: 'PicoBoard connected'}; } + ext._deviceRemoved = function (dev) { + if (device != dev) return; + if (poller) poller = clearInterval(poller); + device = null; + }; + + ext._shutdown = function () { + if (device) device.close(); + if (poller) poller = clearInterval(poller); + device = null; + }; + + ext._getStatus = function () { + if (!device) return {status: 1, msg: 'PicoBoard disconnected'}; + if (watchdog) return {status: 1, msg: 'Probing for PicoBoard'}; + return {status: 2, msg: 'PicoBoard connected'}; + }; + var descriptor = { blocks: [ ['h', 'when %m.booleanSensor', 'whenSensorConnected', 'button pressed'], diff --git a/scratch_extensions/scratch_deviceManager.js b/scratch_extensions/scratch_deviceManager.js new file mode 100644 index 0000000..a50a7e2 --- /dev/null +++ b/scratch_extensions/scratch_deviceManager.js @@ -0,0 +1,91 @@ +// Communicate with the Scratch Device Manager through Socket.IO +window.ScratchDeviceManager = new (function () { + var self = this; + + // device-manager.scratch.mit.edu = 127.0.0.1 + self.deviceManagerHost = 'https://device-manager.scratch.mit.edu:3030'; + + // work around https://github.com/socketio/socket.io-client/issues/812 + function connectNamespace(namespace) { + return io(self.deviceManagerHost + namespace, {forceNew: true}); + } + + self.wedo2_list = function (callback) { + $.ajax(self.deviceManagerHost + '/wedo2/list', { + dataType: 'text', + success: function (data, textStatus, jqXHR) { + var deviceList = JSON.parse(data); + if (deviceList.constructor == Array) { + callback(deviceList); + } + } + }); + }; + + // TODO: handle multiple devices + self.wedo2_open = function (deviceId, callback) { + var socket = connectNamespace('/wedo2'); + var pluginDevice = new RawWeDo2(deviceId, socket); + socket.on('deviceWasOpened', function (event) { + callback(pluginDevice); + }); + socket.emit('open', {deviceId: deviceId}); + }; + + function RawWeDo2(deviceId, socket) { + var WeDo = this; + var eventHandlers = {}; + + WeDo.close = function() { + socket.close(); + }; + + WeDo.setMotorOn = function(motorIndex, power) { + socket.emit('motorOn', {motorIndex:motorIndex, power:power}); + }; + + WeDo.setMotorOff = function(motorIndex) { + socket.emit('motorOff', {motorIndex:motorIndex}); + }; + + WeDo.setMotorBrake = function(motorIndex) { + socket.emit('motorBrake', {motorIndex:motorIndex}); + }; + + WeDo.setLED = function(rgb) { + socket.emit('setLED', {rgb:rgb}); + }; + + WeDo.playTone = function(tone, durationMs) { + socket.emit('playTone', {tone:tone, ms:durationMs}); + }; + + WeDo.stopTone = function() { + socket.emit('stopTone'); + }; + + function setHandler(eventName, handler) { + if (eventHandlers.hasOwnProperty(eventName)) { + var oldHandler = eventHandlers[eventName]; + if (oldHandler) { + socket.removeListener(eventName, oldHandler); + } + } + if (handler) { + socket.on(eventName, handler); + } + eventHandlers[eventName] = handler; + } + + // function handler(event) { access event.sensorName and event.sensorValue } + WeDo.setSensorHandler = function (handler) { + setHandler('sensorChanged', handler); + }; + + WeDo.setDeviceWasClosedHandler = function (handler) { + // TODO: resolve this ambiguity + setHandler('disconnect', handler); + setHandler('deviceWasClosed', handler); + }; + } +})(); diff --git a/scratch_extensions/scratch_ext.js b/scratch_extensions/scratch_ext.js index 40506f8..a05ba26 100644 --- a/scratch_extensions/scratch_ext.js +++ b/scratch_extensions/scratch_ext.js @@ -14,8 +14,8 @@ window.ScratchExtensions = new (function () { var poller = null; var lib = this; - var isOffline = (Scratch && Scratch.FlashApp && Scratch.FlashApp.ASobj && - Scratch.FlashApp.ASobj.isOffline && Scratch.FlashApp.ASobj.isOffline()); + var isOffline = Scratch && Scratch.FlashApp && Scratch.FlashApp.ASobj && + Scratch.FlashApp.ASobj.isOffline && Scratch.FlashApp.ASobj.isOffline(); var pluginAvailable = function () { return !!window.ArrayBuffer && !!( isOffline || @@ -45,20 +45,23 @@ window.ScratchExtensions = new (function () { }; Scratch.FlashApp.ASobj.ASloadExtension(extObj); - if (deviceSpec) { - if (!plugin) { - if (pluginAvailable()) { - // createDevicePlugin() will eventually call checkPolling() if it succeeds - setTimeout(createDevicePlugin, 10); - } else if (window.ScratchPlugin.useActiveX) { - JSsetProjectBanner('Sorry, your version of Internet Explorer is not supported. Please upgrade to version 10 or 11.'); - } - } - else { - // Second hardware-using project in the same tab - checkPolling(); - } - } + if (deviceSpec) { + if (!plugin) { + if (pluginAvailable()) { + // createDevicePlugin() will eventually call checkPolling() if it succeeds + setTimeout(createDevicePlugin, 10); + } else if (ScratchDeviceManager) { + // No plugin is NBD if we're using the SDM + checkPolling(); + } else if (window.ScratchPlugin.useActiveX) { + JSsetProjectBanner('Sorry, your version of Internet Explorer is not supported. Please upgrade to version 10 or 11.'); + } + } + else { + // Second hardware-using project in the same tab + checkPolling(); + } + } return true; }; @@ -81,14 +84,20 @@ window.ScratchExtensions = new (function () { }; lib.unregister = function (name) { - try { handlers[name]._shutdown(); } catch (e) { } + try { + handlers[name]._shutdown(); + } catch (e) { + } delete handlers[name]; delete blockDefs[name]; delete menuDefs[name]; delete deviceSpecs[name]; }; - lib.canAccessDevices = function () { return pluginAvailable(); }; + lib.canAccessDevices = function () { + return pluginAvailable(); + }; + lib.getReporter = function (ext_name, reporter, args) { return handlers[ext_name][reporter].apply(handlers[ext_name], args); }; @@ -96,7 +105,7 @@ window.ScratchExtensions = new (function () { lib.getReporterAsync = function (ext_name, reporter, args, job_id) { var callback = function (retval) { Scratch.FlashApp.ASobj.ASextensionReporterDone(ext_name, job_id, retval); - } + }; args.push(callback); handlers[ext_name][reporter].apply(handlers[ext_name], args); }; @@ -113,24 +122,50 @@ window.ScratchExtensions = new (function () { lib.runAsync = function (ext_name, command, args, job_id) { var callback = function () { Scratch.FlashApp.ASobj.ASextensionCallDone(ext_name, job_id); - } + }; args.push(callback); handlers[ext_name][command].apply(handlers[ext_name], args); }; lib.getStatus = function (ext_name) { - if (!(ext_name in handlers)) - return { status: 0, msg: 'Not loaded' }; + if (!(ext_name in handlers)) { + return {status: 0, msg: 'Not loaded'}; + } - if (ext_name in deviceSpecs && !pluginAvailable()) - return { status: 0, msg: 'Missing browser plugin' }; + if (ext_name in deviceSpecs) { + switch (deviceSpecs[ext_name].type) { + case 'wedo2': + if (!ScratchDeviceManager) { + return {status: 0, msg: 'Missing Scratch Device Manager'}; + } + break; + default: + if (!pluginAvailable()) { + return {status: 0, msg: 'Missing browser plugin'}; + } + break; + } + } return handlers[ext_name]._getStatus(); }; + lib.stop = function (ext_name) { + var ext = handlers[ext_name]; + if (ext._stop) { + ext._stop(); + } + else if (ext.resetAll) { // old, undocumented call + ext.resetAll(); + } + }; + lib.notify = function (text) { - if (window.JSsetProjectBanner) JSsetProjectBanner(text); - else alert(text); + if (window.JSsetProjectBanner) { + JSsetProjectBanner(text); + } else { + alert(text); + } }; lib.resetPlugin = function () { @@ -143,47 +178,67 @@ window.ScratchExtensions = new (function () { }); function shutdown() { - for (var extName in handlers) + for (var extName in handlers) { handlers[extName]._shutdown(); + } handlers = {}; stopPolling(); } function checkDevices() { var awaitingSpecs = {}; - for (var ext_name in deviceSpecs) + var ext_name; + for (ext_name in deviceSpecs) { if (!devices[ext_name]) { var spec = deviceSpecs[ext_name]; if (spec.type == 'hid') { if (!awaitingSpecs['hid']) awaitingSpecs['hid'] = {}; awaitingSpecs['hid'][spec.vendor + '_' + spec.product] = ext_name; } - else if (spec.type == 'serial') + else if (spec.type == 'serial') { awaitingSpecs['serial'] = ext_name; + } + else if (spec.type == 'wedo2') { + awaitingSpecs['wedo2'] = ext_name; + } + } + } + + if (plugin) { + if (awaitingSpecs['hid']) { + plugin.hid_list(function (deviceList) { + var hidList = awaitingSpecs['hid']; + for (var i = 0; i < deviceList.length; i++) { + var ext_name = hidList[deviceList[i]["vendor_id"] + '_' + deviceList[i]["product_id"]]; + if (ext_name) { + handlers[ext_name]._deviceConnected(new HidDevice(deviceList[i], ext_name)); + } + } + }); } - if (awaitingSpecs['hid']) { - plugin.hid_list(function (deviceList) { - var hidList = awaitingSpecs['hid']; - for (var i = 0; i < deviceList.length; i++) { - var ext_name = hidList[deviceList[i]["vendor_id"] + '_' + deviceList[i]["product_id"]]; - if (ext_name) - handlers[ext_name]._deviceConnected(new hidDevice(deviceList[i], ext_name)); + if (awaitingSpecs['serial']) { + ext_name = awaitingSpecs['serial']; + plugin.serial_list(function (deviceList) { + for (var i = 0; i < deviceList.length; i++) { + handlers[ext_name]._deviceConnected(new SerialDevice(deviceList[i], ext_name)); + } + }); + } + } + + if (ScratchDeviceManager && awaitingSpecs['wedo2']) { + ext_name = awaitingSpecs['wedo2']; + ScratchDeviceManager.wedo2_list(function(deviceList) { + for (var i = 0; i < deviceList.length; ++i) { + handlers[ext_name]._deviceConnected(new WeDo2Device(deviceList[i].id, ext_name)); } }); } - if (awaitingSpecs['serial']) { - var ext_name = awaitingSpecs['serial']; - plugin.serial_list(function (deviceList) { - for (var i = 0; i < deviceList.length; i++) { - handlers[ext_name]._deviceConnected(new serialDevice(deviceList[i], ext_name)); - } - }); - } - - if (!shouldLookForDevices()) + if (!shouldLookForDevices()) { stopPolling(); + } } function checkPolling() { @@ -198,9 +253,11 @@ window.ScratchExtensions = new (function () { } function shouldLookForDevices() { - for (var ext_name in deviceSpecs) - if (!devices[ext_name]) + for (var ext_name in deviceSpecs) { + if (!devices[ext_name]) { return true; + } + } return false; } @@ -223,7 +280,8 @@ window.ScratchExtensions = new (function () { // Not IE: try NPAPI var pluginContainer = document.createElement('div'); document.getElementById('scratch').parentNode.appendChild(pluginContainer); - pluginContainer.innerHTML = ' '; + pluginContainer.innerHTML = + ' '; plugin = pluginContainer.firstChild; } // Talk to the actual plugin, but make it pretend to be asynchronous. @@ -235,7 +293,7 @@ window.ScratchExtensions = new (function () { setTimeout(checkPolling, 100); } - function hidDevice(info, ext_name) { + function HidDevice(info, ext_name) { var dev = null; var self = this; @@ -255,11 +313,11 @@ window.ScratchExtensions = new (function () { this.open = function (readyCallback) { plugin.hid_open(self.id, function (d) { dev = d; - dev.set_nonblocking(true); - //devices[ext_name][path] = self; - devices[ext_name] = self; - - if (readyCallback) readyCallback(self); + if (dev) { + devices[ext_name] = self; + dev.set_nonblocking(true); + } + if (readyCallback) readyCallback(d ? self : null); }); }; this.close = function () { @@ -287,7 +345,7 @@ window.ScratchExtensions = new (function () { }; } - function serialDevice(id, ext_name) { + function SerialDevice(id, ext_name) { var dev = null; var self = this; @@ -296,26 +354,16 @@ window.ScratchExtensions = new (function () { this.id = id; this.open = function (opts, readyCallback) { - try { - plugin.serial_open(self.id, opts, function (d) { -// dev.set_disconnect_handler(function () { -// self.close(); -// handlers[ext_name]._deviceRemoved(self); -// }); -// devices[ext_name][path] = self; - dev = d; + plugin.serial_open(self.id, opts, function (d) { + dev = d; + if (dev) { devices[ext_name] = self; - dev.set_error_handler(function (message) { alert('Serial device error\n\nDevice: ' + id + '\nError: ' + message); }); - - if (readyCallback) readyCallback(self); - }); - } - catch (e) { - console.log('Error opening serial device ' + id + ': ' + e); - } + } + if (readyCallback) readyCallback(d ? self : null); + }); }; this.close = function () { if (!dev) return; @@ -334,4 +382,74 @@ window.ScratchExtensions = new (function () { dev.set_receive_handler(handler); }; } + + // TODO: create a base class for these device classes so that we can share common code + function WeDo2Device(id, ext_name) { + var dev = null; + var self = this; + + this.id = id; + + function disconnect() { + setTimeout(function () { + self.close(); + handlers[ext_name]._deviceRemoved(self); + }, 0); + } + + this.open = function(readyCallback) { + ScratchDeviceManager.wedo2_open(self.id, function(d) { + dev = d; + if (dev) { + devices[ext_name] = self; + dev.setDeviceWasClosedHandler(disconnect); + } + if (readyCallback) readyCallback(d ? self : null); + }); + }; + this.close = function() { + if (!dev) return; + dev.close(); + delete devices[ext_name]; + dev = null; + + checkPolling(); + }; + this.is_open = function() { + return !!dev; + }; + + // The `handler` should be a function like: function handler(event) {...} + // The `event` will contain properties called `sensorName` and `sensorValue`. + // Sensor names include `tilt` and `distance`. + this.set_sensor_handler = function(handler) { + if (!dev) return; + dev.setSensorHandler(handler); + }; + + // Starts motor at given power, 0-100. Use negative power for reverse. + this.set_motor_on = function(motorIndex, power) { + dev.setMotorOn(motorIndex, power); + }; + // Applies active braking. + this.set_motor_brake = function(motorIndex) { + dev.setMotorBrake(motorIndex); + }; + // Turns motor off. Depending on power and load, the motor will drift to a stop. + this.set_motor_off = function(motorIndex) { + dev.setMotorOff(motorIndex); + }; + + // Sets the RGB LED color. The RGB color should be specified in 0xRRGGBB format. + this.set_led = function(rgb) { + dev.setLED(rgb); + }; + + this.play_tone = function(tone, durationMs) { + dev.playTone(tone, durationMs); + }; + this.stop_tone = function() { + dev.stopTone(); + }; + } })(); diff --git a/scratch_extensions/scratch_nmh.js b/scratch_extensions/scratch_nmh.js index 08067fb..551a56f 100644 --- a/scratch_extensions/scratch_nmh.js +++ b/scratch_extensions/scratch_nmh.js @@ -54,6 +54,7 @@ window.ScratchDeviceHost = new (function () { }; var pendingCallbacks = {}; + function sendMessage(message, callback) { var callbackToken = (callNumber++).toString(); pendingCallbacks[callbackToken] = callback; diff --git a/scratch_extensions/scratch_plugin.js b/scratch_extensions/scratch_plugin.js index 3912bbf..21320e5 100644 --- a/scratch_extensions/scratch_plugin.js +++ b/scratch_extensions/scratch_plugin.js @@ -71,7 +71,7 @@ window.ScratchPlugin = new (function () { self.close = function () { device.close(); }; - }; + } function SerialWrapper(device) { var self = this; @@ -92,5 +92,5 @@ window.ScratchPlugin = new (function () { self.set_error_handler = function (callback) { device.set_error_handler(callback); }; - }; + } })(); diff --git a/scratch_extensions/wedoExtension.js b/scratch_extensions/wedoExtension.js index fe9adfc..54a5975 100644 --- a/scratch_extensions/wedoExtension.js +++ b/scratch_extensions/wedoExtension.js @@ -4,7 +4,7 @@ // // This is an extension for development and testing of the Scratch Javascript Extension API. -(function(ext) { +(function (ext) { var device = null; var rawData = null; @@ -22,127 +22,140 @@ var weDoTilt = 0; // Commands - ext.motorOnFor = function(motor, time, callback) { + ext.motorOnFor = function (motor, time, callback) { //ext.allMotorsOn(); - ext.motorOn(motor); + ext.motorOn(motor); - setTimeout(function() { - ext.motorOff(motor); + setTimeout(function () { + ext.motorOff(motor); //callback(); - if (typeof callback=="function") callback(); + if (typeof callback == "function") callback(); }, 1000 * time); }; - - ext.motorOn = function(motor) { - switch(motor) { - case "motor": - ext.allMotorsOn('m'); - break; - case "motor A": - setMotorOn('m', 0, true); - break; - case "motor B": - setMotorOn('m', 1, true); - break; - case "lights": - ext.allMotorsOn('l'); - break; - default: - ext.allMotorsOn('a'); - } + + ext.motorOn = function (motor) { + switch (motor) { + case "motor": + ext.allMotorsOn('m'); + break; + case "motor A": + setMotorOn('m', 0, true); + break; + case "motor B": + setMotorOn('m', 1, true); + break; + case "lights": + ext.allMotorsOn('l'); + break; + default: + ext.allMotorsOn('a'); + } }; - ext.allMotorsOn = function(type) { + ext.allMotorsOn = function (type) { setMotorOn(type, 0, true); setMotorOn(type, 1, true); }; - ext.motorOff = function(motor) { - switch(motor) { - case "motor": - ext.allMotorsOff('m'); - break; - case "motor A": - setMotorOn('m', 0, false); - break; - case "motor B": - setMotorOn('m', 1, false); - break; - case "lights": - ext.allMotorsOff('l'); - break; - default: - ext.allMotorsOff('a'); - } + ext.motorOff = function (motor) { + switch (motor) { + case "motor": + ext.allMotorsOff('m'); + break; + case "motor A": + setMotorOn('m', 0, false); + break; + case "motor B": + setMotorOn('m', 1, false); + break; + case "lights": + ext.allMotorsOff('l'); + break; + default: + ext.allMotorsOff('a'); + } }; - ext.resetAll = function() { - ext.allMotorsOff('a'); + ext._stop = function () { + ext.allMotorsOff('a'); }; - ext.allMotorsOff = function(type) { + ext.allMotorsOff = function (type) { setMotorOn(type, 0, false); setMotorOn(type, 1, false); }; - ext.startMotorPower = function(motor, power) { - switch(motor) { - case "motor": - setMotorPower('m', 0, power); - setMotorPower('m', 1, power); - setMotorOn('m', 0, true); - setMotorOn('m', 1, true); - break; - case "motor A": - setMotorPower('m', 0, power); - setMotorOn('m', 0, true); - break; - case "motor B": - setMotorPower('m', 1, power); - setMotorOn('m', 1, true); - break; - case "lights": - setMotorPower('l', 0, power); - setMotorPower('l', 1, power); - setMotorOn('l', 0, true); - setMotorOn('l', 1, true); - break; - default: - setMotorPower('a', 0, power); - setMotorPower('a', 1, power); - setMotorOn('a', 0, true); - setMotorOn('a', 1, true); - } + ext.startMotorPower = function (motor, power) { + switch (motor) { + case "motor": + setMotorPower('m', 0, power); + setMotorPower('m', 1, power); + setMotorOn('m', 0, true); + setMotorOn('m', 1, true); + break; + case "motor A": + setMotorPower('m', 0, power); + setMotorOn('m', 0, true); + break; + case "motor B": + setMotorPower('m', 1, power); + setMotorOn('m', 1, true); + break; + case "lights": + setMotorPower('l', 0, power); + setMotorPower('l', 1, power); + setMotorOn('l', 0, true); + setMotorOn('l', 1, true); + break; + default: + setMotorPower('a', 0, power); + setMotorPower('a', 1, power); + setMotorOn('a', 0, true); + setMotorOn('a', 1, true); + } }; - ext.setMotorDirection = function(motor, s) { + ext.setMotorDirection = function (motor, s) { var dir; - if('this way' == s) dir = 1; - else if('that way' == s) dir = -1; - else if('reverse' == s) dir = 0; - else return; + if ('this way' == s) { + dir = 1; + } else if ('that way' == s) { + dir = -1; + } else if ('reverse' == s) { + dir = 0; + } else { + return; + } - switch(motor) { - case "motor A": - setMotorDirection(0, dir); - break; - case "motor B": - setMotorDirection(1, dir); - break; - default: - setMotorDirection(0, dir); - setMotorDirection(1, dir); - } + switch (motor) { + case "motor A": + setMotorDirection(0, dir); + break; + case "motor B": + setMotorDirection(1, dir); + break; + default: + setMotorDirection(0, dir); + setMotorDirection(1, dir); + } }; // Hat blocks - ext.whenDistance = function(s, dist) { return device!=null && ('<' == s ? (getDistance() < dist) : (getDistance() > dist)); }; - ext.whenTilt = function(s, tilt) { return device!=null && ('=' == s ? (getTilt() == tilt) : (getTilt() != tilt)); }; + ext.whenDistance = function (s, dist) { + return device != null && ('<' == s ? (getDistance() < dist) : (getDistance() > dist)); + }; + ext.whenTilt = function (s, tilt) { + return device != null && ('=' == s ? (getTilt() == tilt) : (getTilt() != tilt)); + }; //ext.whenDistanceLessThan = function(dist) { return device!=null && getDistance() < dist; }; //ext.whenTiltIs = function(tilt) { return device!=null && getTilt() == tilt; }; // Reporters - ext.getDistance = function() { return getDistance(); }; - ext.getTilt = function() { return getTilt(); }; + ext.getDistance = function () { + return getDistance(); + }; + ext.getTilt = function () { + return getTilt(); + }; // Internal logic function setMotorDirection(motorID, dir) { @@ -152,7 +165,7 @@ if ((dir == -1) || (dir == 1)) motor.dir = dir; if (dir == 0) motor.dir = -motor.dir; // reverse if (motor.isOn) sendMotorState(); - }; + } function setMotorOn(type, motorID, flag) { var motor = getMotor(type, motorID); @@ -161,7 +174,7 @@ motor.isOn = (flag == true); if (wasOn) checkForMotorsOff(); sendMotorState(); - }; + } function setMotorPower(type, motorID, pwr) { // Pwr: 0..100 @@ -172,9 +185,11 @@ if (motor.power > 0) motor.isOn = true; if (wasOn) checkForMotorsOff(); sendMotorState(); - }; + } + + var wedoCommand = new Uint8Array(9); + wedoCommand[1] = 0x40; - var wedoCommand = new Uint8Array(9); wedoCommand[1] = 0x40; function sendMotorState() { if (device) { // Each motor is controlled by a signed byte whose sign determines the direction and absolute value the power @@ -194,29 +209,29 @@ } function getMotor(type, motorID) { - if(rawData && okayToReadIDs()) { - var s = new Uint8Array(rawData); - id0 = s[3]; - id1 = s[5]; - } - //console.log(id0); - //console.log(id0.toString(2)); - //console.log(id1); - //console.log(id1.toString(2)); - //console.log(); + if (rawData && okayToReadIDs()) { + var s = new Uint8Array(rawData); + id0 = s[3]; + id1 = s[5]; + } + //console.log(id0); + //console.log(id0.toString(2)); + //console.log(id1); + //console.log(id1.toString(2)); + //console.log(); if ((motorID == 0) && isMotor(type, id0)) return motors[0]; if ((motorID == 1) && isMotor(type, id1)) return motors[1]; return null; } function isMotor(type, id) { - switch (type) { - case 'm': // motor - return (234 <= id) && (id <= 246); - case 'l': // light - return (200 <= id) && (id <= 205); - } - return ((234 <= id) && (id <= 246)) || ((200 <= id) && (id <= 205)); + switch (type) { + case 'm': // motor + return (234 <= id) && (id <= 246); + case 'l': // light + return (200 <= id) && (id <= 205); + } + return ((234 <= id) && (id <= 246)) || ((200 <= id) && (id <= 205)); } function checkForMotorsOff() { @@ -246,21 +261,27 @@ weDoDistance = Math.max(0, Math.min(weDoDistance, 100)); } if ((28 <= id) && (id <= 47)) { // tilt sensor - if (rawValue < 49) weDoTilt = 3; - else if (rawValue < 100) weDoTilt = 2; - else if (rawValue < 154) weDoTilt = 0; - else if (rawValue < 205) weDoTilt = 1; - else weDoTilt = 4; + if (rawValue < 49) { + weDoTilt = 3; + } else if (rawValue < 100) { + weDoTilt = 2; + } else if (rawValue < 154) { + weDoTilt = 0; + } else if (rawValue < 205) { + weDoTilt = 1; + } else { + weDoTilt = 4; + } } } function getDistance() { - if(rawData) processData(); + if (rawData) processData(); return weDoDistance; } function getTilt() { - if(rawData) processData(); + if (rawData) processData(); return weDoTilt; } @@ -279,58 +300,58 @@ } var poller = null; - ext._deviceConnected = function(dev) { - if(device) return; + ext._deviceConnected = function (dev) { + if (device) return; device = dev; device.open(); - poller = setInterval(function() { - device.read(function(data) { + poller = setInterval(function () { + device.read(function (data) { rawData = data; }); }, 20); }; - ext._deviceRemoved = function(dev) { - if(device != dev) return; - if(poller) poller = clearInterval(poller); + ext._deviceRemoved = function (dev) { + if (device != dev) return; + if (poller) poller = clearInterval(poller); device = null; }; - ext._shutdown = function() { + ext._shutdown = function () { setMotorOn('a', 0, false); setMotorOn('a', 1, false); - if(poller) poller = clearInterval(poller); - if(device) device.close(); + if (poller) poller = clearInterval(poller); + if (device) device.close(); device = null; }; - ext._getStatus = function() { - if(!device) return {status: 1, msg: 'LEGO WeDo disconnected'}; + ext._getStatus = function () { + if (!device) return {status: 1, msg: 'LEGO WeDo disconnected'}; return {status: 2, msg: ' LEGO WeDo connected'}; - } + }; var descriptor = { blocks: [ - ['w', 'turn %m.motor on for %n secs', 'motorOnFor', 'motor',1], - [' ', 'turn %m.motor on', 'motorOn', 'motor'], - [' ', 'turn %m.motor off', 'motorOff', 'motor'], - [' ', 'set %m.motor power to %n', 'startMotorPower', 'motor',100], - [' ', 'set %m.motor2 direction to %m.motorDirection','setMotorDirection','motor','this way'], - ['h', 'when distance %m.lessMore %n', 'whenDistance', '<', 20], - ['h', 'when tilt %m.eNe %n', 'whenTilt', '=', 1], - ['r', 'distance', 'getDistance'], - ['r', 'tilt', 'getTilt'] + ['w', 'turn %m.motor on for %n secs', 'motorOnFor', 'motor', 1], + [' ', 'turn %m.motor on', 'motorOn', 'motor'], + [' ', 'turn %m.motor off', 'motorOff', 'motor'], + [' ', 'set %m.motor power to %n', 'startMotorPower', 'motor', 100], + [' ', 'set %m.motor2 direction to %m.motorDirection', 'setMotorDirection', 'motor', 'this way'], + ['h', 'when distance %m.lessMore %n', 'whenDistance', '<', 20], + ['h', 'when tilt %m.eNe %n', 'whenTilt', '=', 1], + ['r', 'distance', 'getDistance'], + ['r', 'tilt', 'getTilt'] ], menus: { motor: ['motor', 'motor A', 'motor B', 'lights', 'everything'], - motor2: ['motor', 'motor A', 'motor B', 'all motors'], + motor2: ['motor', 'motor A', 'motor B', 'all motors'], motorDirection: ['this way', 'that way', 'reverse'], lessMore: ['<', '>'], - eNe: ['=','not ='] + eNe: ['=', 'not ='] }, - url: '/info/help/studio/tips/ext/LEGO WeDo/' + url: '/info/help/studio/tips/ext/LEGO WeDo/' }; - ScratchExtensions.register('LEGO WeDo', descriptor, ext, {type: 'hid', vendor:1684, product:3}); + ScratchExtensions.register('LEGO WeDo', descriptor, ext, {type: 'hid', vendor: 1684, product: 3}); })({});