mirror of
https://github.com/scratchfoundation/scratchx.git
synced 2024-11-29 02:55:53 -05:00
335 lines
10 KiB
JavaScript
335 lines
10 KiB
JavaScript
|
// 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});
|
||
|
})({});
|