Skip to content

Commit 4e6ded0

Browse files
committed
Added example hardware extensions
1 parent d626efd commit 4e6ded0

File tree

2 files changed

+278
-0
lines changed

2 files changed

+278
-0
lines changed

joystickExtension.js

+76
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
// joystickExtension.js
2+
// Shane M. Clements, November 2013
3+
// Joystick Scratch Extension
4+
//
5+
// This is an extension for development and testing of the Scratch Javascript Extension API.
6+
7+
new (function() {
8+
var device = null;
9+
var input = null;
10+
var poller = null;
11+
var ext = this;
12+
13+
ext._deviceConnected = function(dev) {
14+
if(device) return;
15+
16+
device = dev;
17+
device.open();
18+
19+
poller = setInterval(function() {
20+
input = device.read(48);
21+
}, 10);
22+
23+
// setInterval(function() { console.log(input); }, 100);
24+
};
25+
26+
ext._deviceRemoved = function(dev) {
27+
if(device != dev) return;
28+
device = null;
29+
stopPolling();
30+
};
31+
32+
function stopPolling() {
33+
if(poller) clearInterval(poller);
34+
poller = null;
35+
}
36+
37+
ext._shutdown = function() {
38+
if(poller) clearInterval(poller);
39+
poller = null;
40+
41+
if(device) device.close();
42+
device = null;
43+
}
44+
45+
ext._getStatus = function() {
46+
if(!device) return {status: 1, msg: 'Controller disconnected'};
47+
return {status: 2, msg: 'Controller connected'};
48+
}
49+
50+
// Converts a byte into a value of the range -1 -> 1 with two decimal places of precision
51+
function convertByteStr(byte) { return (parseInt(byte, 16) - 128) / 128; }
52+
ext.readJoystick = function(name) {
53+
var retval = null;
54+
switch(name) {
55+
case 'leftX': retval = convertByteStr(input[12] + input[13]); break;
56+
case 'leftY': retval = -convertByteStr(input[14] + input[15]); break;
57+
case 'rightX': retval = convertByteStr(input[16] + input[17]); break;
58+
case 'rightY': retval = -convertByteStr(input[18] + input[19]); break;
59+
}
60+
61+
// If it's hardly off center then treat it as centered
62+
if(Math.abs(retval) < 0.1) retval = 0;
63+
64+
return retval.toFixed(2);
65+
}
66+
67+
var descriptor = {
68+
blocks: [
69+
['r', 'get joystick %m.joystickPart', 'readJoystick', 'leftX']
70+
],
71+
menus: {
72+
joystickPart: ['leftX', 'leftY', 'rightX', 'rightY']
73+
}
74+
};
75+
ScratchExtensions.register('Joystick', descriptor, ext, {type: 'hid', vendor:0x054c, product:0x0268});
76+
})();

picoExtension.js

+202
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
// picoExtension.js
2+
// Shane M. Clements, February 2014
3+
// PicoBoard Scratch Extension
4+
//
5+
// This is an extension for development and testing of the Scratch Javascript Extension API.
6+
7+
(function(ext) {
8+
var device = null;
9+
var rawData = null;
10+
11+
// Sensor states:
12+
var channels = {
13+
slider: 7,
14+
light: 5,
15+
sound: 6,
16+
button: 3,
17+
'resistance-A': 4,
18+
'resistance-B': 2,
19+
'resistance-C': 1,
20+
'resistance-D': 0
21+
};
22+
var inputs = {
23+
slider: 0,
24+
light: 0,
25+
sound: 0,
26+
button: 0,
27+
'resistance-A': 0,
28+
'resistance-B': 0,
29+
'resistance-C': 0,
30+
'resistance-D': 0
31+
};
32+
33+
ext.resetAll = function(){};
34+
35+
// Hats / triggers
36+
ext.whenSensorConnected = function(which) {
37+
return getSensorPressed(which);
38+
};
39+
40+
ext.whenSensorPass = function(which, sign, level) {
41+
if (sign == '<') return getSensor(which) < level;
42+
return getSensor(which) > level;
43+
};
44+
45+
// Reporters
46+
ext.sensorPressed = function(which) {
47+
return getSensorPressed(which);
48+
};
49+
50+
ext.sensor = function(which) { return getSensor(which); };
51+
52+
// Private logic
53+
function getSensorPressed(which) {
54+
if (device == null) return false;
55+
if (which == 'button pressed' && getSensor('button') < 1) return true;
56+
if (which == 'A connected' && getSensor('resistance-A') < 10) return true;
57+
if (which == 'B connected' && getSensor('resistance-B') < 10) return true;
58+
if (which == 'C connected' && getSensor('resistance-C') < 10) return true;
59+
if (which == 'D connected' && getSensor('resistance-D') < 10) return true;
60+
return false;
61+
}
62+
63+
function getSensor(which) {
64+
return inputs[which];
65+
}
66+
67+
var inputArray = [];
68+
function processData() {
69+
var bytes = new Uint8Array(rawData);
70+
71+
inputArray[15] = 0;
72+
73+
// TODO: make this robust against misaligned packets.
74+
// Right now there's no guarantee that our 18 bytes start at the beginning of a message.
75+
// Maybe we should treat the data as a stream of 2-byte packets instead of 18-byte packets.
76+
// That way we could just check the high bit of each byte to verify that we're aligned.
77+
for(var i=0; i<9; ++i) {
78+
var hb = bytes[i*2] & 127;
79+
var channel = hb >> 3;
80+
var lb = bytes[i*2+1] & 127;
81+
inputArray[channel] = ((hb & 7) << 7) + lb;
82+
}
83+
84+
if (watchdog && (inputArray[15] == 0x04)) {
85+
// Seems to be a valid PicoBoard.
86+
clearTimeout(watchdog);
87+
watchdog = null;
88+
}
89+
90+
for(var name in inputs) {
91+
var v = inputArray[channels[name]];
92+
if(name == 'light') {
93+
v = (v < 25) ? 100 - v : Math.round((1023 - v) * (75 / 998));
94+
}
95+
else if(name == 'sound') {
96+
//empirically tested noise sensor floor
97+
v = Math.max(0, v - 18)
98+
v = (v < 50) ? v / 2 :
99+
//noise ceiling
100+
25 + Math.min(75, Math.round((v - 50) * (75 / 580)));
101+
}
102+
else {
103+
v = (100 * v) / 1023;
104+
}
105+
106+
inputs[name] = v;
107+
}
108+
109+
//console.log(inputs);
110+
rawData = null;
111+
}
112+
113+
function appendBuffer( buffer1, buffer2 ) {
114+
var tmp = new Uint8Array( buffer1.byteLength + buffer2.byteLength );
115+
tmp.set( new Uint8Array( buffer1 ), 0 );
116+
tmp.set( new Uint8Array( buffer2 ), buffer1.byteLength );
117+
return tmp.buffer;
118+
}
119+
120+
// Extension API interactions
121+
var potentialDevices = [];
122+
ext._deviceConnected = function(dev) {
123+
potentialDevices.push(dev);
124+
125+
if (!device) {
126+
tryNextDevice();
127+
}
128+
}
129+
130+
var poller = null;
131+
var watchdog = null;
132+
function tryNextDevice() {
133+
// If potentialDevices is empty, device will be undefined.
134+
// That will get us back here next time a device is connected.
135+
device = potentialDevices.shift();
136+
if (!device) return;
137+
138+
device.open({ stopBits: 0, bitRate: 38400, ctsFlowControl: 0 });
139+
device.set_receive_handler(function(data) {
140+
//console.log('Received: ' + data.byteLength);
141+
if(!rawData || rawData.byteLength == 18) rawData = new Uint8Array(data);
142+
else rawData = appendBuffer(rawData, data);
143+
144+
if(rawData.byteLength >= 18) {
145+
//console.log(rawData);
146+
processData();
147+
//device.send(pingCmd.buffer);
148+
}
149+
});
150+
151+
// Tell the PicoBoard to send a input data every 50ms
152+
var pingCmd = new Uint8Array(1);
153+
pingCmd[0] = 1;
154+
poller = setInterval(function() {
155+
device.send(pingCmd.buffer);
156+
}, 50);
157+
watchdog = setTimeout(function() {
158+
// This device didn't get good data in time, so give up on it. Clean up and then move on.
159+
// If we get good data then we'll terminate this watchdog.
160+
clearInterval(poller);
161+
poller = null;
162+
device.set_receive_handler(null);
163+
device.close();
164+
device = null;
165+
tryNextDevice();
166+
}, 250);
167+
};
168+
169+
ext._deviceRemoved = function(dev) {
170+
if(device != dev) return;
171+
if(poller) poller = clearInterval(poller);
172+
device = null;
173+
};
174+
175+
ext._shutdown = function() {
176+
if(device) device.close();
177+
if(poller) poller = clearInterval(poller);
178+
device = null;
179+
};
180+
181+
ext._getStatus = function() {
182+
if(!device) return {status: 1, msg: 'PicoBoard disconnected'};
183+
if(watchdog) return {status: 1, msg: 'Probing for PicoBoard'};
184+
return {status: 2, msg: 'PicoBoard connected'};
185+
}
186+
187+
var descriptor = {
188+
blocks: [
189+
['h', 'when %m.booleanSensor', 'whenSensorConnected', 'button pressed'],
190+
['h', 'when %m.sensor %m.lessMore %n', 'whenSensorPass', 'slider', '>', 50],
191+
['b', 'sensor %m.booleanSensor?', 'sensorPressed', 'button pressed'],
192+
['r', '%m.sensor sensor value', 'sensor', 'slider']
193+
],
194+
menus: {
195+
booleanSensor: ['button pressed', 'A connected', 'B connected', 'C connected', 'D connected'],
196+
sensor: ['slider', 'light', 'sound', 'resistance-A', 'resistance-B', 'resistance-C', 'resistance-D'],
197+
lessMore: ['>', '<']
198+
},
199+
url: '/info/help/studio/tips/ext/PicoBoard/'
200+
};
201+
ScratchExtensions.register('PicoBoard', descriptor, ext, {type: 'serial'});
202+
})({});

0 commit comments

Comments
 (0)