mirror of
https://github.com/scratchfoundation/scratchx.git
synced 2024-11-25 17:18:21 -05:00
203 lines
6.5 KiB
JavaScript
203 lines
6.5 KiB
JavaScript
|
// 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'});
|
||
|
})({});
|