mirror of
https://github.com/scratchfoundation/scratchx.git
synced 2025-02-17 19:30:20 -05:00
Added built-in extensions and fixed loading them
This commit is contained in:
parent
a0670e4ea9
commit
6c2a999922
5 changed files with 828 additions and 0 deletions
BIN
Scratch.swf
BIN
Scratch.swf
Binary file not shown.
|
@ -7,7 +7,12 @@
|
|||
<link href="css/scratchx.css" rel="stylesheet" type="text/css">
|
||||
<script type="text/javascript" src="libs/swfobject.js"></script>
|
||||
<script type="text/javascript" src="libs/jquery.min.js"></script>
|
||||
<script type="text/javascript" src="scratch_extensions/scratch_ext.js"></script>
|
||||
<script type="text/javascript">
|
||||
// Simulate the bare minimum of the view that exists on the main site
|
||||
var Scratch = Scratch || {};
|
||||
Scratch.FlashApp = Scratch.FlashApp || {};
|
||||
|
||||
function handleEmbedStatus(e) {
|
||||
$('#scratch-loader').hide();
|
||||
var scratch = $('#scratch');
|
||||
|
@ -18,6 +23,7 @@
|
|||
scratch.find('DIV.scratch_loading').hide();
|
||||
} else {
|
||||
scratch.css('visibility', 'visible');
|
||||
Scratch.FlashApp.ASobj = scratch[0];
|
||||
}
|
||||
}
|
||||
|
||||
|
|
202
scratch_extensions/picoExtension.js
Normal file
202
scratch_extensions/picoExtension.js
Normal file
|
@ -0,0 +1,202 @@
|
|||
// picoExtension.js
|
||||
// Shane M. Clements, February 2014
|
||||
// PicoBoard Scratch Extension
|
||||
//
|
||||
// This is an extension for development and testing of the Scratch Javascript Extension API.
|
||||
|
||||
(function(ext) {
|
||||
var device = null;
|
||||
var rawData = null;
|
||||
|
||||
// Sensor states:
|
||||
var channels = {
|
||||
slider: 7,
|
||||
light: 5,
|
||||
sound: 6,
|
||||
button: 3,
|
||||
'resistance-A': 4,
|
||||
'resistance-B': 2,
|
||||
'resistance-C': 1,
|
||||
'resistance-D': 0
|
||||
};
|
||||
var inputs = {
|
||||
slider: 0,
|
||||
light: 0,
|
||||
sound: 0,
|
||||
button: 0,
|
||||
'resistance-A': 0,
|
||||
'resistance-B': 0,
|
||||
'resistance-C': 0,
|
||||
'resistance-D': 0
|
||||
};
|
||||
|
||||
ext.resetAll = function(){};
|
||||
|
||||
// Hats / triggers
|
||||
ext.whenSensorConnected = function(which) {
|
||||
return getSensorPressed(which);
|
||||
};
|
||||
|
||||
ext.whenSensorPass = function(which, sign, level) {
|
||||
if (sign == '<') return getSensor(which) < level;
|
||||
return getSensor(which) > level;
|
||||
};
|
||||
|
||||
// Reporters
|
||||
ext.sensorPressed = function(which) {
|
||||
return getSensorPressed(which);
|
||||
};
|
||||
|
||||
ext.sensor = function(which) { return getSensor(which); };
|
||||
|
||||
// Private logic
|
||||
function getSensorPressed(which) {
|
||||
if (device == null) return false;
|
||||
if (which == 'button pressed' && getSensor('button') < 1) return true;
|
||||
if (which == 'A connected' && getSensor('resistance-A') < 10) return true;
|
||||
if (which == 'B connected' && getSensor('resistance-B') < 10) return true;
|
||||
if (which == 'C connected' && getSensor('resistance-C') < 10) return true;
|
||||
if (which == 'D connected' && getSensor('resistance-D') < 10) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
function getSensor(which) {
|
||||
return inputs[which];
|
||||
}
|
||||
|
||||
var inputArray = [];
|
||||
function processData() {
|
||||
var bytes = new Uint8Array(rawData);
|
||||
|
||||
inputArray[15] = 0;
|
||||
|
||||
// TODO: make this robust against misaligned packets.
|
||||
// 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;
|
||||
var channel = hb >> 3;
|
||||
var lb = bytes[i*2+1] & 127;
|
||||
inputArray[channel] = ((hb & 7) << 7) + lb;
|
||||
}
|
||||
|
||||
if (watchdog && (inputArray[15] == 0x04)) {
|
||||
// Seems to be a valid PicoBoard.
|
||||
clearTimeout(watchdog);
|
||||
watchdog = null;
|
||||
}
|
||||
|
||||
for(var name in inputs) {
|
||||
var v = inputArray[channels[name]];
|
||||
if(name == 'light') {
|
||||
v = (v < 25) ? 100 - v : Math.round((1023 - v) * (75 / 998));
|
||||
}
|
||||
else if(name == 'sound') {
|
||||
//empirically tested noise sensor floor
|
||||
v = Math.max(0, v - 18)
|
||||
v = (v < 50) ? v / 2 :
|
||||
//noise ceiling
|
||||
25 + Math.min(75, Math.round((v - 50) * (75 / 580)));
|
||||
}
|
||||
else {
|
||||
v = (100 * v) / 1023;
|
||||
}
|
||||
|
||||
inputs[name] = v;
|
||||
}
|
||||
|
||||
//console.log(inputs);
|
||||
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 );
|
||||
return tmp.buffer;
|
||||
}
|
||||
|
||||
// Extension API interactions
|
||||
var potentialDevices = [];
|
||||
ext._deviceConnected = function(dev) {
|
||||
potentialDevices.push(dev);
|
||||
|
||||
if (!device) {
|
||||
tryNextDevice();
|
||||
}
|
||||
}
|
||||
|
||||
var poller = null;
|
||||
var watchdog = null;
|
||||
function tryNextDevice() {
|
||||
// If potentialDevices is empty, device will be undefined.
|
||||
// That will get us back here next time a device is connected.
|
||||
device = potentialDevices.shift();
|
||||
if (!device) return;
|
||||
|
||||
device.open({ stopBits: 0, bitRate: 38400, ctsFlowControl: 0 });
|
||||
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.byteLength >= 18) {
|
||||
//console.log(rawData);
|
||||
processData();
|
||||
//device.send(pingCmd.buffer);
|
||||
}
|
||||
});
|
||||
|
||||
// Tell the PicoBoard to send a input data every 50ms
|
||||
var pingCmd = new Uint8Array(1);
|
||||
pingCmd[0] = 1;
|
||||
poller = setInterval(function() {
|
||||
device.send(pingCmd.buffer);
|
||||
}, 50);
|
||||
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);
|
||||
poller = null;
|
||||
device.set_receive_handler(null);
|
||||
device.close();
|
||||
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'};
|
||||
}
|
||||
|
||||
var descriptor = {
|
||||
blocks: [
|
||||
['h', 'when %m.booleanSensor', 'whenSensorConnected', 'button pressed'],
|
||||
['h', 'when %m.sensor %m.lessMore %n', 'whenSensorPass', 'slider', '>', 50],
|
||||
['b', 'sensor %m.booleanSensor?', 'sensorPressed', 'button pressed'],
|
||||
['r', '%m.sensor sensor value', 'sensor', 'slider']
|
||||
],
|
||||
menus: {
|
||||
booleanSensor: ['button pressed', 'A connected', 'B connected', 'C connected', 'D connected'],
|
||||
sensor: ['slider', 'light', 'sound', 'resistance-A', 'resistance-B', 'resistance-C', 'resistance-D'],
|
||||
lessMore: ['>', '<']
|
||||
},
|
||||
url: '/info/help/studio/tips/ext/PicoBoard/'
|
||||
};
|
||||
ScratchExtensions.register('PicoBoard', descriptor, ext, {type: 'serial'});
|
||||
})({});
|
286
scratch_extensions/scratch_ext.js
Normal file
286
scratch_extensions/scratch_ext.js
Normal file
|
@ -0,0 +1,286 @@
|
|||
// scratch_ext.js
|
||||
// Shane M. Clements, November 2013
|
||||
// ScratchExtensions
|
||||
//
|
||||
// Scratch 2.0 extension manager which Scratch communicates with to initialize extensions and communicate with them.
|
||||
// The extension manager also handles creating the browser plugin to enable access to HID and serial devices.
|
||||
window.ScratchExtensions = new (function(){
|
||||
var pluginName = 'Scratch Device Plugin'; // will be 'Scratch Plugin for Devices'
|
||||
var pluginAvailable = (window.ActiveXObject || !!navigator.plugins[pluginName]) && !!window.ArrayBuffer;
|
||||
var plugin = null;
|
||||
var handlers = {};
|
||||
var blockDefs = {};
|
||||
var menuDefs = {};
|
||||
var deviceSpecs = {};
|
||||
var devices = {};
|
||||
var poller = null;
|
||||
var lib = this;
|
||||
|
||||
lib.register = function(name, descriptor, handler, deviceSpec) {
|
||||
if(name in handlers) {
|
||||
console.log('Scratch extension "'+name+'" already exists!');
|
||||
return false;
|
||||
}
|
||||
|
||||
if(deviceSpec) {
|
||||
if(!pluginAvailable && window.ActiveXObject) {
|
||||
JSsetProjectBanner('Sorry, your version of Internet Explorer is not supported. Please upgrade to version 10 or 11.');
|
||||
}
|
||||
if(pluginAvailable && !plugin) setTimeout(createDevicePlugin, 10);
|
||||
|
||||
// Wait a moment to access the plugin and claim any devices that plugins are
|
||||
// interested in.
|
||||
setTimeout(checkPolling, 100);
|
||||
}
|
||||
|
||||
handlers[name] = handler;
|
||||
blockDefs[name] = descriptor.blocks;
|
||||
if(descriptor.menus) menuDefs[name] = descriptor.menus;
|
||||
if(deviceSpec) deviceSpecs[name] = deviceSpec;
|
||||
|
||||
// Show the blocks in Scratch!
|
||||
var extObj = {
|
||||
extensionName: name,
|
||||
blockSpecs: descriptor.blocks,
|
||||
url: descriptor.url,
|
||||
menus: descriptor.menus,
|
||||
javascriptURL: loadingURL
|
||||
};
|
||||
Scratch.FlashApp.ASobj.ASloadExtension(extObj);
|
||||
return true;
|
||||
};
|
||||
|
||||
var loadingURL;
|
||||
lib.loadExternalJS = function(url) {
|
||||
var scr = document.createElement("script");
|
||||
scr.src = url;// + "?ts=" + new Date().getTime();
|
||||
loadingURL = url;
|
||||
document.getElementsByTagName("head")[0].appendChild(scr);
|
||||
};
|
||||
|
||||
lib.loadLocalJS = function(code) {
|
||||
// Run the extension code in the global scope
|
||||
try {
|
||||
(new Function(code))();
|
||||
} catch(e) {
|
||||
console.log(e.stack.toString());
|
||||
}
|
||||
};
|
||||
|
||||
lib.unregister = function(name) {
|
||||
try { handlers[name]._shutdown(); } catch(e){}
|
||||
delete handlers[name];
|
||||
delete blockDefs[name];
|
||||
delete menuDefs[name];
|
||||
delete deviceSpecs[name];
|
||||
};
|
||||
|
||||
lib.canAccessDevices = function() { return pluginAvailable; };
|
||||
lib.getReporter = function(ext_name, reporter, args) {
|
||||
return handlers[ext_name][reporter].apply(handlers[ext_name], args);
|
||||
};
|
||||
|
||||
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);
|
||||
};
|
||||
|
||||
lib.runCommand = function(ext_name, command, args) {
|
||||
handlers[ext_name][command].apply(handlers[ext_name], args);
|
||||
};
|
||||
|
||||
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 deviceSpecs && !pluginAvailable)
|
||||
return {status: 0, msg: 'Missing browser plugin'};
|
||||
|
||||
return handlers[ext_name]._getStatus();
|
||||
};
|
||||
|
||||
lib.notify = function(text) {
|
||||
if(window.JSsetProjectBanner) JSsetProjectBanner(text);
|
||||
else alert(text);
|
||||
};
|
||||
|
||||
lib.resetPlugin = function() {
|
||||
if (plugin && plugin.reset) plugin.reset();
|
||||
shutdown();
|
||||
};
|
||||
|
||||
$(window).unload(function(e) {
|
||||
shutdown();
|
||||
});
|
||||
|
||||
function shutdown() {
|
||||
for(var extName in handlers)
|
||||
handlers[extName]._shutdown();
|
||||
handlers = {};
|
||||
stopPolling();
|
||||
}
|
||||
|
||||
function checkDevices() {
|
||||
var awaitingSpecs = {};
|
||||
for(var 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')
|
||||
awaitingSpecs['serial'] = ext_name;
|
||||
}
|
||||
|
||||
if(awaitingSpecs['hid']) {
|
||||
var deviceList = plugin.hid_list();
|
||||
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']) {
|
||||
var ext_name = awaitingSpecs['serial'];
|
||||
var deviceList = plugin.serial_list();
|
||||
for (var i = 0; i < deviceList.length; i++) {
|
||||
handlers[ext_name]._deviceConnected(new serialDevice(deviceList[i], ext_name));
|
||||
}
|
||||
}
|
||||
|
||||
if(!shouldLookForDevices())
|
||||
stopPolling();
|
||||
}
|
||||
|
||||
function checkPolling() {
|
||||
if(poller || !shouldLookForDevices()) return;
|
||||
|
||||
poller = setInterval(checkDevices, 500);
|
||||
}
|
||||
|
||||
function stopPolling() {
|
||||
if(poller) clearInterval(poller);
|
||||
poller = null;
|
||||
}
|
||||
|
||||
function shouldLookForDevices() {
|
||||
for(var ext_name in deviceSpecs)
|
||||
if(!devices[ext_name])
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function createDevicePlugin() {
|
||||
if(plugin) return;
|
||||
|
||||
var pluginContainer = document.createElement('div');
|
||||
document.getElementById('scratch').parentNode.appendChild(pluginContainer);
|
||||
pluginContainer.innerHTML = '<object type="application/x-scratchdeviceplugin" width="1" height="1" codebase="/scratchr2/static/ext/ScratchDevicePlugin.cab"> </object>';
|
||||
plugin = pluginContainer.firstChild;
|
||||
}
|
||||
|
||||
function hidDevice(info, ext_name) {
|
||||
var dev = null;
|
||||
var self = this;
|
||||
|
||||
// TODO: add support for multiple devices per extension
|
||||
//if(!(ext_name in devices)) devices[ext_name] = {};
|
||||
|
||||
this.id = info["path"];
|
||||
this.info = info;
|
||||
|
||||
function disconnect() {
|
||||
setTimeout(function(){
|
||||
self.close();
|
||||
handlers[ext_name]._deviceRemoved(self);
|
||||
}, 0);
|
||||
}
|
||||
|
||||
this.open = function() {
|
||||
try {
|
||||
dev = plugin.hid_open(this.id);
|
||||
if(window.ActiveXObject) dev = dev(this.id);
|
||||
dev.set_nonblocking(true);
|
||||
//devices[ext_name][path] = this;
|
||||
devices[ext_name] = this;
|
||||
}
|
||||
catch(e) {}
|
||||
};
|
||||
this.close = function() {
|
||||
if(!dev) return;
|
||||
dev.close();
|
||||
delete devices[ext_name];
|
||||
dev = null;
|
||||
|
||||
checkPolling();
|
||||
};
|
||||
this.write = function(data) {
|
||||
if(!dev) return;
|
||||
var len = dev.write(data);
|
||||
if(window.ActiveXObject) len = len(data);
|
||||
if(len < 0) disconnect();
|
||||
return len;
|
||||
};
|
||||
this.read = function(len) {
|
||||
if(!dev) return null;
|
||||
if(!len) len = 65;
|
||||
var data = dev.read(len);
|
||||
if(window.ActiveXObject) data = data(len);
|
||||
if(data.byteLength == 0) disconnect();
|
||||
return data;
|
||||
};
|
||||
}
|
||||
|
||||
function serialDevice(id, ext_name) {
|
||||
var dev = null;
|
||||
var self = this;
|
||||
|
||||
// TODO: add support for multiple devices per extension
|
||||
//if(!(ext_name in devices)) devices[ext_name] = {};
|
||||
|
||||
this.id = id;
|
||||
this.open = function(opts) {
|
||||
try {
|
||||
dev = plugin.serial_open(this.id, opts);
|
||||
// dev.set_disconnect_handler(function() {
|
||||
// self.close();
|
||||
// handlers[ext_name]._deviceRemoved(self);
|
||||
// });
|
||||
//devices[ext_name][path] = this;
|
||||
devices[ext_name] = this;
|
||||
}
|
||||
catch(e) {}
|
||||
};
|
||||
this.close = function() {
|
||||
if(!dev) return;
|
||||
dev.close();
|
||||
delete devices[ext_name];
|
||||
dev = null;
|
||||
|
||||
checkPolling();
|
||||
};
|
||||
this.send = function(data) {
|
||||
if(!dev) return;
|
||||
dev.send(data);
|
||||
};
|
||||
this.set_receive_handler = function(handler) {
|
||||
if(!dev) return;
|
||||
dev.set_receive_handler(handler);
|
||||
};
|
||||
}
|
||||
})();
|
334
scratch_extensions/wedoExtension.js
Normal file
334
scratch_extensions/wedoExtension.js
Normal file
|
@ -0,0 +1,334 @@
|
|||
// wedoExtension.js
|
||||
// Shane M. Clements, January 2014
|
||||
// LEGO WEDO Scratch Extension
|
||||
//
|
||||
// This is an extension for development and testing of the Scratch Javascript Extension API.
|
||||
|
||||
(function(ext) {
|
||||
var device = null;
|
||||
var rawData = null;
|
||||
|
||||
// Motor states: power: 0 to 100, dir: -1 or 1
|
||||
var motors = [
|
||||
{power: 100, dir: 1, isOn: false},
|
||||
{power: 100, dir: 1, isOn: false}
|
||||
];
|
||||
var motorOffTime = 0;
|
||||
|
||||
// Sensor states:
|
||||
var id0 = 0;
|
||||
var id1 = 0;
|
||||
var weDoDistance = 0;
|
||||
var weDoTilt = 0;
|
||||
|
||||
// Commands
|
||||
ext.motorOnFor = function(motor, time, callback) {
|
||||
//ext.allMotorsOn();
|
||||
ext.motorOn(motor);
|
||||
|
||||
setTimeout(function() {
|
||||
ext.motorOff(motor);
|
||||
//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.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.resetAll = function() {
|
||||
ext.allMotorsOff('a');
|
||||
};
|
||||
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.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;
|
||||
|
||||
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.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(); };
|
||||
|
||||
// Internal logic
|
||||
function setMotorDirection(motorID, dir) {
|
||||
// Dir: -1 - counter-clockwise, 1 - clockwise, 0 - reverse
|
||||
var motor = getMotor('m', motorID);
|
||||
if (!motor) return; // motorID must be 0 or 1
|
||||
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);
|
||||
if (!motor) return; // motorID must be 0 or 1
|
||||
var wasOn = motor.isOn && (motor.power > 0);
|
||||
motor.isOn = (flag == true);
|
||||
if (wasOn) checkForMotorsOff();
|
||||
sendMotorState();
|
||||
};
|
||||
|
||||
function setMotorPower(type, motorID, pwr) {
|
||||
// Pwr: 0..100
|
||||
var motor = getMotor(type, motorID);
|
||||
if (!motor) return; // motorID must be 0 or 1
|
||||
var wasOn = motor.isOn && (motor.power > 0);
|
||||
motor.power = Math.max(0, Math.min(pwr, 100));
|
||||
if (motor.power > 0) motor.isOn = true;
|
||||
if (wasOn) checkForMotorsOff();
|
||||
sendMotorState();
|
||||
};
|
||||
|
||||
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
|
||||
wedoCommand[2] = motorValue(0);
|
||||
wedoCommand[3] = motorValue(1);
|
||||
device.write(wedoCommand.buffer);
|
||||
}
|
||||
}
|
||||
|
||||
function motorValue(motorID) {
|
||||
// Return a two character hex string to control the given motor.
|
||||
var motor = motors[motorID];
|
||||
var byte = 0;
|
||||
if (motor.isOn && (motor.power > 0)) byte = (17 + Math.floor(1.1 * motor.power));
|
||||
if (motor.dir < 0) byte = (256 - byte) & 0xFF;
|
||||
return byte;
|
||||
}
|
||||
|
||||
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 ((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));
|
||||
}
|
||||
|
||||
function checkForMotorsOff() {
|
||||
// Called on motor transition from on to off or motor power goes from non-zero to zero.
|
||||
// If both motors are just become off (or zero power), set motorOffTime to the current time.
|
||||
if (motors[0].isOn && (motors[0].power > 0)) return; // motor 0 is still on
|
||||
if (motors[1].isOn && (motors[1].power > 0)) return; // motor 1 is still on
|
||||
motorOffTime = new Date().getTime();
|
||||
}
|
||||
|
||||
function okayToReadIDs() {
|
||||
// The WeDo sensor ID data is garbled and meaningless while any motor is running.
|
||||
// In fact, the ID continues to be garbled for a short while after all motors have
|
||||
// been turned off because the motor "coasts" and generates a current which throws
|
||||
// off the analog-to-digital converter in the WeDo hub. Thus, we keep track when the last
|
||||
// motor was turned off and wait half a second before trying to read the sensor ID's
|
||||
// Cached values of the sensor ID's are used while motors are running. Thus, if a user
|
||||
// plugs a different sensor into the WeDo hub while the motors are running, the plugin
|
||||
// won't notice until all motors are stopped.
|
||||
if (motors[0].isOn || motors[1].isOn) return false;
|
||||
return (new Date().getTime() - motorOffTime) > 500;
|
||||
}
|
||||
|
||||
function updateSensor(id, rawValue) {
|
||||
if ((170 <= id) && (id <= 190)) { // distance sensor
|
||||
weDoDistance = Math.round((100 * (rawValue - 70)) / 140);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
function getDistance() {
|
||||
if(rawData) processData();
|
||||
return weDoDistance;
|
||||
}
|
||||
|
||||
function getTilt() {
|
||||
if(rawData) processData();
|
||||
return weDoTilt;
|
||||
}
|
||||
|
||||
function processData() {
|
||||
var s = new Uint8Array(rawData);
|
||||
|
||||
if (okayToReadIDs()) {
|
||||
id0 = s[3];
|
||||
id1 = s[5];
|
||||
}
|
||||
weDoDistance = weDoTilt = 0; // zero if no sensor plugged in
|
||||
updateSensor(id0, s[2]);
|
||||
updateSensor(id1, s[4]);
|
||||
|
||||
rawData = null;
|
||||
}
|
||||
|
||||
var poller = null;
|
||||
ext._deviceConnected = function(dev) {
|
||||
if(device) return;
|
||||
|
||||
device = dev;
|
||||
device.open();
|
||||
poller = setInterval(function() {
|
||||
rawData = device.read();
|
||||
}, 20);
|
||||
};
|
||||
|
||||
ext._deviceRemoved = function(dev) {
|
||||
if(device != dev) return;
|
||||
if(poller) poller = clearInterval(poller);
|
||||
device = null;
|
||||
};
|
||||
|
||||
ext._shutdown = function() {
|
||||
setMotorOn('a', 0, false);
|
||||
setMotorOn('a', 1, false);
|
||||
|
||||
if(poller) poller = clearInterval(poller);
|
||||
if(device) device.close();
|
||||
device = null;
|
||||
};
|
||||
|
||||
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']
|
||||
],
|
||||
menus: {
|
||||
motor: ['motor', 'motor A', 'motor B', 'lights', 'everything'],
|
||||
motor2: ['motor', 'motor A', 'motor B', 'all motors'],
|
||||
motorDirection: ['this way', 'that way', 'reverse'],
|
||||
lessMore: ['<', '>'],
|
||||
eNe: ['=','not =']
|
||||
},
|
||||
url: '/info/help/studio/tips/ext/LEGO WeDo/'
|
||||
};
|
||||
ScratchExtensions.register('LEGO WeDo', descriptor, ext, {type: 'hid', vendor:1684, product:3});
|
||||
})({});
|
Loading…
Reference in a new issue