@scratch/scratch-vm 11.0.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.jsdoc.json +20 -0
- package/.nvmrc +1 -0
- package/CHANGELOG.md +7004 -0
- package/LICENSE +12 -0
- package/README.md +120 -0
- package/TRADEMARK +1 -0
- package/commitlint.config.js +4 -0
- package/docs/extensions.md +527 -0
- package/package.json +103 -0
- package/release.config.js +13 -0
- package/src/blocks/scratch3_control.js +206 -0
- package/src/blocks/scratch3_core_example.js +69 -0
- package/src/blocks/scratch3_data.js +257 -0
- package/src/blocks/scratch3_event.js +137 -0
- package/src/blocks/scratch3_looks.js +612 -0
- package/src/blocks/scratch3_motion.js +288 -0
- package/src/blocks/scratch3_operators.js +154 -0
- package/src/blocks/scratch3_procedures.js +79 -0
- package/src/blocks/scratch3_sensing.js +336 -0
- package/src/blocks/scratch3_sound.js +350 -0
- package/src/dispatch/central-dispatch.js +143 -0
- package/src/dispatch/shared-dispatch.js +235 -0
- package/src/dispatch/worker-dispatch.js +110 -0
- package/src/engine/adapter.js +176 -0
- package/src/engine/block-utility.js +242 -0
- package/src/engine/blocks-execute-cache.js +19 -0
- package/src/engine/blocks-runtime-cache.js +78 -0
- package/src/engine/blocks.js +1299 -0
- package/src/engine/comment.js +56 -0
- package/src/engine/execute.js +580 -0
- package/src/engine/monitor-record.js +23 -0
- package/src/engine/mutation-adapter.js +48 -0
- package/src/engine/profiler.js +390 -0
- package/src/engine/runtime.js +2686 -0
- package/src/engine/scratch-blocks-constants.js +27 -0
- package/src/engine/sequencer.js +361 -0
- package/src/engine/stage-layering.js +29 -0
- package/src/engine/target.js +799 -0
- package/src/engine/thread.js +404 -0
- package/src/engine/variable.js +70 -0
- package/src/extension-support/argument-type.js +47 -0
- package/src/extension-support/block-type.js +50 -0
- package/src/extension-support/define-messages.js +18 -0
- package/src/extension-support/extension-manager.js +440 -0
- package/src/extension-support/extension-metadata.js +64 -0
- package/src/extension-support/extension-worker.js +59 -0
- package/src/extension-support/reporter-scope.js +18 -0
- package/src/extension-support/target-type.js +17 -0
- package/src/extensions/scratch3_boost/index.js +2113 -0
- package/src/extensions/scratch3_ev3/index.js +1355 -0
- package/src/extensions/scratch3_gdx_for/index.js +981 -0
- package/src/extensions/scratch3_gdx_for/scratch-link-device-adapter.js +44 -0
- package/src/extensions/scratch3_makeymakey/index.js +396 -0
- package/src/extensions/scratch3_microbit/index.js +984 -0
- package/src/extensions/scratch3_music/assets/drums/1-snare.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/10-wood-block.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/11-cowbell.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/12-triangle.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/13-bongo.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/14-conga.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/15-cabasa.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/16-guiro.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/17-vibraslap.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/18-cuica.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/2-bass-drum.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/3-side-stick.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/4-crash-cymbal.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/5-open-hi-hat.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/6-closed-hi-hat.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/7-tambourine.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/8-hand-clap.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/9-claves.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/1-piano/108.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/1-piano/24.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/1-piano/36.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/1-piano/48.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/1-piano/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/1-piano/72.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/1-piano/84.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/1-piano/96.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/10-clarinet/48.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/10-clarinet/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/11-saxophone/36.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/11-saxophone/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/11-saxophone/84.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/12-flute/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/12-flute/72.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/13-wooden-flute/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/13-wooden-flute/72.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/14-bassoon/36.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/14-bassoon/48.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/14-bassoon/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/15-choir/48.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/15-choir/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/15-choir/72.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/16-vibraphone/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/16-vibraphone/72.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/17-music-box/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/18-steel-drum/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/19-marimba/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/2-electric-piano/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/20-synth-lead/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/21-synth-pad/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/3-organ/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/4-guitar/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/5-electric-guitar/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/6-bass/36.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/6-bass/48.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/7-pizzicato/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/8-cello/36.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/8-cello/48.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/8-cello/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/9-trombone/36.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/9-trombone/48.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/9-trombone/60.mp3 +0 -0
- package/src/extensions/scratch3_music/index.js +1333 -0
- package/src/extensions/scratch3_music/manifest.js +63 -0
- package/src/extensions/scratch3_pen/index.js +770 -0
- package/src/extensions/scratch3_speech2text/index.js +700 -0
- package/src/extensions/scratch3_text2speech/index.js +766 -0
- package/src/extensions/scratch3_translate/index.js +286 -0
- package/src/extensions/scratch3_video_sensing/debug.js +13 -0
- package/src/extensions/scratch3_video_sensing/index.js +594 -0
- package/src/extensions/scratch3_video_sensing/library.js +384 -0
- package/src/extensions/scratch3_video_sensing/math.js +76 -0
- package/src/extensions/scratch3_video_sensing/view.js +509 -0
- package/src/extensions/scratch3_wedo2/index.js +1616 -0
- package/src/import/load-costume.js +416 -0
- package/src/import/load-sound.js +116 -0
- package/src/index.js +10 -0
- package/src/io/ble.js +256 -0
- package/src/io/bt.js +202 -0
- package/src/io/clock.js +39 -0
- package/src/io/cloud.js +169 -0
- package/src/io/keyboard.js +146 -0
- package/src/io/mouse.js +145 -0
- package/src/io/mouseWheel.js +28 -0
- package/src/io/userData.js +24 -0
- package/src/io/video.js +210 -0
- package/src/playground/benchmark.css +83 -0
- package/src/playground/benchmark.js +753 -0
- package/src/playground/index.html +111 -0
- package/src/playground/suite.css +78 -0
- package/src/playground/suite.html +25 -0
- package/src/playground/suite.js +544 -0
- package/src/playground/video-sensing.html +18 -0
- package/src/playground/video-sensing.js +133 -0
- package/src/serialization/deserialize-assets.js +178 -0
- package/src/serialization/sb2.js +1295 -0
- package/src/serialization/sb2_specmap.js +1818 -0
- package/src/serialization/sb3.js +1320 -0
- package/src/serialization/serialize-assets.js +60 -0
- package/src/sprites/rendered-target.js +1115 -0
- package/src/sprites/sprite.js +177 -0
- package/src/util/base64-util.js +48 -0
- package/src/util/cast.js +218 -0
- package/src/util/clone.js +17 -0
- package/src/util/color.js +204 -0
- package/src/util/fetch-with-timeout.js +53 -0
- package/src/util/get-monitor-id.js +33 -0
- package/src/util/jsonrpc.js +114 -0
- package/src/util/log.js +4 -0
- package/src/util/math-util.js +122 -0
- package/src/util/maybe-format-message.js +18 -0
- package/src/util/new-block-ids.js +33 -0
- package/src/util/rateLimiter.js +73 -0
- package/src/util/scratch-link-websocket.js +133 -0
- package/src/util/string-util.js +94 -0
- package/src/util/task-queue.js +203 -0
- package/src/util/timer.js +103 -0
- package/src/util/uid.js +29 -0
- package/src/util/variable-util.js +48 -0
- package/src/util/xml-escape.js +33 -0
- package/src/virtual-machine.js +1586 -0
- package/webpack.config.js +134 -0
package/src/io/ble.js
ADDED
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
const JSONRPC = require('../util/jsonrpc');
|
|
2
|
+
|
|
3
|
+
class BLE extends JSONRPC {
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* A BLE peripheral socket object. It handles connecting, over web sockets, to
|
|
7
|
+
* BLE peripherals, and reading and writing data to them.
|
|
8
|
+
* @param {Runtime} runtime - the Runtime for sending/receiving GUI update events.
|
|
9
|
+
* @param {string} extensionId - the id of the extension using this socket.
|
|
10
|
+
* @param {object} peripheralOptions - the list of options for peripheral discovery.
|
|
11
|
+
* @param {object} connectCallback - a callback for connection.
|
|
12
|
+
* @param {object} resetCallback - a callback for resetting extension state.
|
|
13
|
+
*/
|
|
14
|
+
constructor (runtime, extensionId, peripheralOptions, connectCallback, resetCallback = null) {
|
|
15
|
+
super();
|
|
16
|
+
|
|
17
|
+
this._socket = runtime.getScratchLinkSocket('BLE');
|
|
18
|
+
this._socket.setOnOpen(this.requestPeripheral.bind(this));
|
|
19
|
+
this._socket.setOnClose(this.handleDisconnectError.bind(this));
|
|
20
|
+
this._socket.setOnError(this._handleRequestError.bind(this));
|
|
21
|
+
this._socket.setHandleMessage(this._handleMessage.bind(this));
|
|
22
|
+
|
|
23
|
+
this._sendMessage = this._socket.sendMessage.bind(this._socket);
|
|
24
|
+
|
|
25
|
+
this._availablePeripherals = {};
|
|
26
|
+
this._connectCallback = connectCallback;
|
|
27
|
+
this._connected = false;
|
|
28
|
+
this._characteristicDidChangeCallback = null;
|
|
29
|
+
this._resetCallback = resetCallback;
|
|
30
|
+
this._discoverTimeoutID = null;
|
|
31
|
+
this._extensionId = extensionId;
|
|
32
|
+
this._peripheralOptions = peripheralOptions;
|
|
33
|
+
this._runtime = runtime;
|
|
34
|
+
|
|
35
|
+
this._socket.open();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Request connection to the peripheral.
|
|
40
|
+
* If the web socket is not yet open, request when the socket promise resolves.
|
|
41
|
+
*/
|
|
42
|
+
requestPeripheral () {
|
|
43
|
+
this._availablePeripherals = {};
|
|
44
|
+
if (this._discoverTimeoutID) {
|
|
45
|
+
window.clearTimeout(this._discoverTimeoutID);
|
|
46
|
+
}
|
|
47
|
+
this._discoverTimeoutID = window.setTimeout(this._handleDiscoverTimeout.bind(this), 15000);
|
|
48
|
+
this.sendRemoteRequest('discover', this._peripheralOptions)
|
|
49
|
+
.catch(e => {
|
|
50
|
+
this._handleRequestError(e);
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Try connecting to the input peripheral id, and then call the connect
|
|
56
|
+
* callback if connection is successful.
|
|
57
|
+
* @param {number} id - the id of the peripheral to connect to
|
|
58
|
+
*/
|
|
59
|
+
connectPeripheral (id) {
|
|
60
|
+
this.sendRemoteRequest('connect', {peripheralId: id})
|
|
61
|
+
.then(() => {
|
|
62
|
+
this._connected = true;
|
|
63
|
+
this._runtime.emit(this._runtime.constructor.PERIPHERAL_CONNECTED);
|
|
64
|
+
this._connectCallback();
|
|
65
|
+
})
|
|
66
|
+
.catch(e => {
|
|
67
|
+
this._handleRequestError(e);
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Close the websocket.
|
|
73
|
+
*/
|
|
74
|
+
disconnect () {
|
|
75
|
+
if (this._connected) {
|
|
76
|
+
this._connected = false;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (this._socket.isOpen()) {
|
|
80
|
+
this._socket.close();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (this._discoverTimeoutID) {
|
|
84
|
+
window.clearTimeout(this._discoverTimeoutID);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Sets connection status icon to orange
|
|
88
|
+
this._runtime.emit(this._runtime.constructor.PERIPHERAL_DISCONNECTED);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* @return {bool} whether the peripheral is connected.
|
|
93
|
+
*/
|
|
94
|
+
isConnected () {
|
|
95
|
+
return this._connected;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Start receiving notifications from the specified ble service.
|
|
100
|
+
* @param {number} serviceId - the ble service to read.
|
|
101
|
+
* @param {number} characteristicId - the ble characteristic to get notifications from.
|
|
102
|
+
* @param {object} onCharacteristicChanged - callback for characteristic change notifications.
|
|
103
|
+
* @return {Promise} - a promise from the remote startNotifications request.
|
|
104
|
+
*/
|
|
105
|
+
startNotifications (serviceId, characteristicId, onCharacteristicChanged = null) {
|
|
106
|
+
const params = {
|
|
107
|
+
serviceId,
|
|
108
|
+
characteristicId
|
|
109
|
+
};
|
|
110
|
+
this._characteristicDidChangeCallback = onCharacteristicChanged;
|
|
111
|
+
return this.sendRemoteRequest('startNotifications', params)
|
|
112
|
+
.catch(e => {
|
|
113
|
+
this.handleDisconnectError(e);
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Read from the specified ble service.
|
|
119
|
+
* @param {number} serviceId - the ble service to read.
|
|
120
|
+
* @param {number} characteristicId - the ble characteristic to read.
|
|
121
|
+
* @param {boolean} optStartNotifications - whether to start receiving characteristic change notifications.
|
|
122
|
+
* @param {object} onCharacteristicChanged - callback for characteristic change notifications.
|
|
123
|
+
* @return {Promise} - a promise from the remote read request.
|
|
124
|
+
*/
|
|
125
|
+
read (serviceId, characteristicId, optStartNotifications = false, onCharacteristicChanged = null) {
|
|
126
|
+
const params = {
|
|
127
|
+
serviceId,
|
|
128
|
+
characteristicId
|
|
129
|
+
};
|
|
130
|
+
if (optStartNotifications) {
|
|
131
|
+
params.startNotifications = true;
|
|
132
|
+
}
|
|
133
|
+
if (onCharacteristicChanged) {
|
|
134
|
+
this._characteristicDidChangeCallback = onCharacteristicChanged;
|
|
135
|
+
}
|
|
136
|
+
return this.sendRemoteRequest('read', params)
|
|
137
|
+
.catch(e => {
|
|
138
|
+
this.handleDisconnectError(e);
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Write data to the specified ble service.
|
|
144
|
+
* @param {number} serviceId - the ble service to write.
|
|
145
|
+
* @param {number} characteristicId - the ble characteristic to write.
|
|
146
|
+
* @param {string} message - the message to send.
|
|
147
|
+
* @param {string} encoding - the message encoding type.
|
|
148
|
+
* @param {boolean} withResponse - if true, resolve after peripheral's response.
|
|
149
|
+
* @return {Promise} - a promise from the remote send request.
|
|
150
|
+
*/
|
|
151
|
+
write (serviceId, characteristicId, message, encoding = null, withResponse = null) {
|
|
152
|
+
const params = {serviceId, characteristicId, message};
|
|
153
|
+
if (encoding) {
|
|
154
|
+
params.encoding = encoding;
|
|
155
|
+
}
|
|
156
|
+
if (withResponse !== null) {
|
|
157
|
+
params.withResponse = withResponse;
|
|
158
|
+
}
|
|
159
|
+
return this.sendRemoteRequest('write', params)
|
|
160
|
+
.catch(e => {
|
|
161
|
+
this.handleDisconnectError(e);
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Handle a received call from the socket.
|
|
167
|
+
* @param {string} method - a received method label.
|
|
168
|
+
* @param {object} params - a received list of parameters.
|
|
169
|
+
* @return {object} - optional return value.
|
|
170
|
+
*/
|
|
171
|
+
didReceiveCall (method, params) {
|
|
172
|
+
switch (method) {
|
|
173
|
+
case 'didDiscoverPeripheral':
|
|
174
|
+
this._availablePeripherals[params.peripheralId] = params;
|
|
175
|
+
this._runtime.emit(
|
|
176
|
+
this._runtime.constructor.PERIPHERAL_LIST_UPDATE,
|
|
177
|
+
this._availablePeripherals
|
|
178
|
+
);
|
|
179
|
+
if (this._discoverTimeoutID) {
|
|
180
|
+
window.clearTimeout(this._discoverTimeoutID);
|
|
181
|
+
}
|
|
182
|
+
break;
|
|
183
|
+
case 'userDidPickPeripheral':
|
|
184
|
+
this._availablePeripherals[params.peripheralId] = params;
|
|
185
|
+
this._runtime.emit(
|
|
186
|
+
this._runtime.constructor.USER_PICKED_PERIPHERAL,
|
|
187
|
+
this._availablePeripherals
|
|
188
|
+
);
|
|
189
|
+
if (this._discoverTimeoutID) {
|
|
190
|
+
window.clearTimeout(this._discoverTimeoutID);
|
|
191
|
+
}
|
|
192
|
+
break;
|
|
193
|
+
case 'userDidNotPickPeripheral':
|
|
194
|
+
this._runtime.emit(
|
|
195
|
+
this._runtime.constructor.PERIPHERAL_SCAN_TIMEOUT
|
|
196
|
+
);
|
|
197
|
+
if (this._discoverTimeoutID) {
|
|
198
|
+
window.clearTimeout(this._discoverTimeoutID);
|
|
199
|
+
}
|
|
200
|
+
break;
|
|
201
|
+
case 'characteristicDidChange':
|
|
202
|
+
if (this._characteristicDidChangeCallback) {
|
|
203
|
+
this._characteristicDidChangeCallback(params.message);
|
|
204
|
+
}
|
|
205
|
+
break;
|
|
206
|
+
case 'ping':
|
|
207
|
+
return 42;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Handle an error resulting from losing connection to a peripheral.
|
|
213
|
+
*
|
|
214
|
+
* This could be due to:
|
|
215
|
+
* - battery depletion
|
|
216
|
+
* - going out of bluetooth range
|
|
217
|
+
* - being powered down
|
|
218
|
+
*
|
|
219
|
+
* Disconnect the socket, and if the extension using this socket has a
|
|
220
|
+
* reset callback, call it. Finally, emit an error to the runtime.
|
|
221
|
+
*/
|
|
222
|
+
handleDisconnectError (/* e */) {
|
|
223
|
+
// log.error(`BLE error: ${JSON.stringify(e)}`);
|
|
224
|
+
|
|
225
|
+
if (!this._connected) return;
|
|
226
|
+
|
|
227
|
+
this.disconnect();
|
|
228
|
+
|
|
229
|
+
if (this._resetCallback) {
|
|
230
|
+
this._resetCallback();
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
this._runtime.emit(this._runtime.constructor.PERIPHERAL_CONNECTION_LOST_ERROR, {
|
|
234
|
+
message: `Scratch lost connection to`,
|
|
235
|
+
extensionId: this._extensionId
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
_handleRequestError (/* e */) {
|
|
240
|
+
// log.error(`BLE error: ${JSON.stringify(e)}`);
|
|
241
|
+
|
|
242
|
+
this._runtime.emit(this._runtime.constructor.PERIPHERAL_REQUEST_ERROR, {
|
|
243
|
+
message: `Scratch lost connection to`,
|
|
244
|
+
extensionId: this._extensionId
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
_handleDiscoverTimeout () {
|
|
249
|
+
if (this._discoverTimeoutID) {
|
|
250
|
+
window.clearTimeout(this._discoverTimeoutID);
|
|
251
|
+
}
|
|
252
|
+
this._runtime.emit(this._runtime.constructor.PERIPHERAL_SCAN_TIMEOUT);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
module.exports = BLE;
|
package/src/io/bt.js
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
const JSONRPC = require('../util/jsonrpc');
|
|
2
|
+
|
|
3
|
+
class BT extends JSONRPC {
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* A BT peripheral socket object. It handles connecting, over web sockets, to
|
|
7
|
+
* BT peripherals, and reading and writing data to them.
|
|
8
|
+
* @param {Runtime} runtime - the Runtime for sending/receiving GUI update events.
|
|
9
|
+
* @param {string} extensionId - the id of the extension using this socket.
|
|
10
|
+
* @param {object} peripheralOptions - the list of options for peripheral discovery.
|
|
11
|
+
* @param {object} connectCallback - a callback for connection.
|
|
12
|
+
* @param {object} resetCallback - a callback for resetting extension state.
|
|
13
|
+
* @param {object} messageCallback - a callback for message sending.
|
|
14
|
+
*/
|
|
15
|
+
constructor (runtime, extensionId, peripheralOptions, connectCallback, resetCallback = null, messageCallback) {
|
|
16
|
+
super();
|
|
17
|
+
|
|
18
|
+
this._socket = runtime.getScratchLinkSocket('BT');
|
|
19
|
+
this._socket.setOnOpen(this.requestPeripheral.bind(this));
|
|
20
|
+
this._socket.setOnError(this._handleRequestError.bind(this));
|
|
21
|
+
this._socket.setOnClose(this.handleDisconnectError.bind(this));
|
|
22
|
+
this._socket.setHandleMessage(this._handleMessage.bind(this));
|
|
23
|
+
|
|
24
|
+
this._sendMessage = this._socket.sendMessage.bind(this._socket);
|
|
25
|
+
|
|
26
|
+
this._availablePeripherals = {};
|
|
27
|
+
this._connectCallback = connectCallback;
|
|
28
|
+
this._connected = false;
|
|
29
|
+
this._characteristicDidChangeCallback = null;
|
|
30
|
+
this._resetCallback = resetCallback;
|
|
31
|
+
this._discoverTimeoutID = null;
|
|
32
|
+
this._extensionId = extensionId;
|
|
33
|
+
this._peripheralOptions = peripheralOptions;
|
|
34
|
+
this._messageCallback = messageCallback;
|
|
35
|
+
this._runtime = runtime;
|
|
36
|
+
|
|
37
|
+
this._socket.open();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Request connection to the peripheral.
|
|
42
|
+
* If the web socket is not yet open, request when the socket promise resolves.
|
|
43
|
+
*/
|
|
44
|
+
requestPeripheral () {
|
|
45
|
+
this._availablePeripherals = {};
|
|
46
|
+
if (this._discoverTimeoutID) {
|
|
47
|
+
window.clearTimeout(this._discoverTimeoutID);
|
|
48
|
+
}
|
|
49
|
+
this._discoverTimeoutID = window.setTimeout(this._handleDiscoverTimeout.bind(this), 15000);
|
|
50
|
+
this.sendRemoteRequest('discover', this._peripheralOptions)
|
|
51
|
+
.catch(
|
|
52
|
+
e => this._handleRequestError(e)
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Try connecting to the input peripheral id, and then call the connect
|
|
58
|
+
* callback if connection is successful.
|
|
59
|
+
* @param {number} id - the id of the peripheral to connect to
|
|
60
|
+
* @param {string} pin - an optional pin for pairing
|
|
61
|
+
*/
|
|
62
|
+
connectPeripheral (id, pin = null) {
|
|
63
|
+
const params = {peripheralId: id};
|
|
64
|
+
if (pin) {
|
|
65
|
+
params.pin = pin;
|
|
66
|
+
}
|
|
67
|
+
this.sendRemoteRequest('connect', params)
|
|
68
|
+
.then(() => {
|
|
69
|
+
this._connected = true;
|
|
70
|
+
this._runtime.emit(this._runtime.constructor.PERIPHERAL_CONNECTED);
|
|
71
|
+
this._connectCallback();
|
|
72
|
+
})
|
|
73
|
+
.catch(e => {
|
|
74
|
+
this._handleRequestError(e);
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Close the websocket.
|
|
80
|
+
*/
|
|
81
|
+
disconnect () {
|
|
82
|
+
if (this._connected) {
|
|
83
|
+
this._connected = false;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (this._socket.isOpen()) {
|
|
87
|
+
this._socket.close();
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (this._discoverTimeoutID) {
|
|
91
|
+
window.clearTimeout(this._discoverTimeoutID);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Sets connection status icon to orange
|
|
95
|
+
this._runtime.emit(this._runtime.constructor.PERIPHERAL_DISCONNECTED);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* @return {bool} whether the peripheral is connected.
|
|
100
|
+
*/
|
|
101
|
+
isConnected () {
|
|
102
|
+
return this._connected;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
sendMessage (options) {
|
|
106
|
+
return this.sendRemoteRequest('send', options)
|
|
107
|
+
.catch(e => {
|
|
108
|
+
this.handleDisconnectError(e);
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Handle a received call from the socket.
|
|
114
|
+
* @param {string} method - a received method label.
|
|
115
|
+
* @param {object} params - a received list of parameters.
|
|
116
|
+
* @return {object} - optional return value.
|
|
117
|
+
*/
|
|
118
|
+
didReceiveCall (method, params) {
|
|
119
|
+
// TODO: Add peripheral 'undiscover' handling
|
|
120
|
+
switch (method) {
|
|
121
|
+
case 'didDiscoverPeripheral':
|
|
122
|
+
this._availablePeripherals[params.peripheralId] = params;
|
|
123
|
+
this._runtime.emit(
|
|
124
|
+
this._runtime.constructor.PERIPHERAL_LIST_UPDATE,
|
|
125
|
+
this._availablePeripherals
|
|
126
|
+
);
|
|
127
|
+
if (this._discoverTimeoutID) {
|
|
128
|
+
window.clearTimeout(this._discoverTimeoutID);
|
|
129
|
+
}
|
|
130
|
+
break;
|
|
131
|
+
case 'userDidPickPeripheral':
|
|
132
|
+
this._availablePeripherals[params.peripheralId] = params;
|
|
133
|
+
this._runtime.emit(
|
|
134
|
+
this._runtime.constructor.USER_PICKED_PERIPHERAL,
|
|
135
|
+
this._availablePeripherals
|
|
136
|
+
);
|
|
137
|
+
if (this._discoverTimeoutID) {
|
|
138
|
+
window.clearTimeout(this._discoverTimeoutID);
|
|
139
|
+
}
|
|
140
|
+
break;
|
|
141
|
+
case 'userDidNotPickPeripheral':
|
|
142
|
+
this._runtime.emit(
|
|
143
|
+
this._runtime.constructor.PERIPHERAL_SCAN_TIMEOUT
|
|
144
|
+
);
|
|
145
|
+
if (this._discoverTimeoutID) {
|
|
146
|
+
window.clearTimeout(this._discoverTimeoutID);
|
|
147
|
+
}
|
|
148
|
+
break;
|
|
149
|
+
case 'didReceiveMessage':
|
|
150
|
+
this._messageCallback(params); // TODO: refine?
|
|
151
|
+
break;
|
|
152
|
+
default:
|
|
153
|
+
return 'nah';
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Handle an error resulting from losing connection to a peripheral.
|
|
159
|
+
*
|
|
160
|
+
* This could be due to:
|
|
161
|
+
* - battery depletion
|
|
162
|
+
* - going out of bluetooth range
|
|
163
|
+
* - being powered down
|
|
164
|
+
*
|
|
165
|
+
* Disconnect the socket, and if the extension using this socket has a
|
|
166
|
+
* reset callback, call it. Finally, emit an error to the runtime.
|
|
167
|
+
*/
|
|
168
|
+
handleDisconnectError (/* e */) {
|
|
169
|
+
// log.error(`BT error: ${JSON.stringify(e)}`);
|
|
170
|
+
|
|
171
|
+
if (!this._connected) return;
|
|
172
|
+
|
|
173
|
+
this.disconnect();
|
|
174
|
+
|
|
175
|
+
if (this._resetCallback) {
|
|
176
|
+
this._resetCallback();
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
this._runtime.emit(this._runtime.constructor.PERIPHERAL_CONNECTION_LOST_ERROR, {
|
|
180
|
+
message: `Scratch lost connection to`,
|
|
181
|
+
extensionId: this._extensionId
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
_handleRequestError (/* e */) {
|
|
186
|
+
// log.error(`BT error: ${JSON.stringify(e)}`);
|
|
187
|
+
|
|
188
|
+
this._runtime.emit(this._runtime.constructor.PERIPHERAL_REQUEST_ERROR, {
|
|
189
|
+
message: `Scratch lost connection to`,
|
|
190
|
+
extensionId: this._extensionId
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
_handleDiscoverTimeout () {
|
|
195
|
+
if (this._discoverTimeoutID) {
|
|
196
|
+
window.clearTimeout(this._discoverTimeoutID);
|
|
197
|
+
}
|
|
198
|
+
this._runtime.emit(this._runtime.constructor.PERIPHERAL_SCAN_TIMEOUT);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
module.exports = BT;
|
package/src/io/clock.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
const Timer = require('../util/timer');
|
|
2
|
+
|
|
3
|
+
class Clock {
|
|
4
|
+
constructor (runtime) {
|
|
5
|
+
this._projectTimer = new Timer({now: () => runtime.currentMSecs});
|
|
6
|
+
this._projectTimer.start();
|
|
7
|
+
this._pausedTime = null;
|
|
8
|
+
this._paused = false;
|
|
9
|
+
/**
|
|
10
|
+
* Reference to the owning Runtime.
|
|
11
|
+
* @type{!Runtime}
|
|
12
|
+
*/
|
|
13
|
+
this.runtime = runtime;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
projectTimer () {
|
|
17
|
+
if (this._paused) {
|
|
18
|
+
return this._pausedTime / 1000;
|
|
19
|
+
}
|
|
20
|
+
return this._projectTimer.timeElapsed() / 1000;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
pause () {
|
|
24
|
+
this._paused = true;
|
|
25
|
+
this._pausedTime = this._projectTimer.timeElapsed();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
resume () {
|
|
29
|
+
this._paused = false;
|
|
30
|
+
const dt = this._projectTimer.timeElapsed() - this._pausedTime;
|
|
31
|
+
this._projectTimer.startTime += dt;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
resetProjectTimer () {
|
|
35
|
+
this._projectTimer.start();
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
module.exports = Clock;
|
package/src/io/cloud.js
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
const Variable = require('../engine/variable');
|
|
2
|
+
const log = require('../util/log');
|
|
3
|
+
|
|
4
|
+
class Cloud {
|
|
5
|
+
/**
|
|
6
|
+
* @typedef updateVariable
|
|
7
|
+
* @param {string} name The name of the cloud variable to update on the server
|
|
8
|
+
* @param {(string | number)} value The value to update the cloud variable with.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* A cloud data provider, responsible for managing the connection to the
|
|
13
|
+
* cloud data server and for posting data about cloud data activity to
|
|
14
|
+
* this IO device.
|
|
15
|
+
* @typedef {object} CloudProvider
|
|
16
|
+
* @property {updateVariable} updateVariable A function which sends a cloud variable
|
|
17
|
+
* update to the cloud data server.
|
|
18
|
+
* @property {Function} requestCloseConnection A function which closes
|
|
19
|
+
* the connection to the cloud data server.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Part of a cloud io data post indicating a cloud variable update.
|
|
24
|
+
* @typedef {object} VarUpdateData
|
|
25
|
+
* @property {string} name The name of the variable to update
|
|
26
|
+
* @property {(number | string)} value The scalar value to update the variable with
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* A cloud io data post message.
|
|
31
|
+
* @typedef {object} CloudIOData
|
|
32
|
+
* @property {VarUpdateData} varUpdate A {@link VarUpdateData} message indicating
|
|
33
|
+
* a cloud variable update
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Cloud IO Device responsible for sending and receiving messages from
|
|
38
|
+
* cloud provider (mananging the cloud server connection) and interacting
|
|
39
|
+
* with cloud variables in the current project.
|
|
40
|
+
* @param {Runtime} runtime The runtime context for this cloud io device.
|
|
41
|
+
*/
|
|
42
|
+
constructor (runtime) {
|
|
43
|
+
/**
|
|
44
|
+
* Reference to the cloud data provider, responsible for mananging
|
|
45
|
+
* the web socket connection to the cloud data server.
|
|
46
|
+
* @type {?CloudProvider}
|
|
47
|
+
*/
|
|
48
|
+
this.provider = null;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Reference to the runtime that owns this cloud io device.
|
|
52
|
+
* @type {!Runtime}
|
|
53
|
+
*/
|
|
54
|
+
this.runtime = runtime;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Reference to the stage target which owns the cloud variables
|
|
58
|
+
* in the project.
|
|
59
|
+
* @type {?Target}
|
|
60
|
+
*/
|
|
61
|
+
this.stage = null;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Set a reference to the cloud data provider.
|
|
66
|
+
* @param {CloudProvider} provider The cloud data provider
|
|
67
|
+
*/
|
|
68
|
+
setProvider (provider) {
|
|
69
|
+
this.provider = provider;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Set a reference to the stage target which owns the
|
|
74
|
+
* cloud variables in the project.
|
|
75
|
+
* @param {Target} stage The stage target
|
|
76
|
+
*/
|
|
77
|
+
setStage (stage) {
|
|
78
|
+
this.stage = stage;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Handle incoming data to this io device.
|
|
83
|
+
* @param {CloudIOData} data The {@link CloudIOData} object to process
|
|
84
|
+
*/
|
|
85
|
+
postData (data) {
|
|
86
|
+
if (data.varUpdate) {
|
|
87
|
+
this.updateCloudVariable(data.varUpdate);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
requestCreateVariable (variable) {
|
|
92
|
+
if (this.runtime.canAddCloudVariable()) {
|
|
93
|
+
if (this.provider) {
|
|
94
|
+
this.provider.createVariable(variable.name, variable.value);
|
|
95
|
+
// We'll set the cloud flag and update the
|
|
96
|
+
// cloud variable limit when we actually
|
|
97
|
+
// get a confirmation from the cloud data server
|
|
98
|
+
}
|
|
99
|
+
} // TODO else track creation for later
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Request the cloud data provider to update the given variable with
|
|
104
|
+
* the given value. Does nothing if this io device does not have a provider set.
|
|
105
|
+
* @param {string} name The name of the variable to update
|
|
106
|
+
* @param {string | number} value The value to update the variable with
|
|
107
|
+
*/
|
|
108
|
+
requestUpdateVariable (name, value) {
|
|
109
|
+
if (this.provider) {
|
|
110
|
+
this.provider.updateVariable(name, value);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Request the cloud data provider to rename the variable with the given name
|
|
116
|
+
* to the given new name. Does nothing if this io device does not have a provider set.
|
|
117
|
+
* @param {string} oldName The name of the variable to rename
|
|
118
|
+
* @param {string | number} newName The new name for the variable
|
|
119
|
+
*/
|
|
120
|
+
requestRenameVariable (oldName, newName) {
|
|
121
|
+
if (this.provider) {
|
|
122
|
+
this.provider.renameVariable(oldName, newName);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Request the cloud data provider to delete the variable with the given name
|
|
128
|
+
* Does nothing if this io device does not have a provider set.
|
|
129
|
+
* @param {string} name The name of the variable to delete
|
|
130
|
+
*/
|
|
131
|
+
requestDeleteVariable (name) {
|
|
132
|
+
if (this.provider) {
|
|
133
|
+
this.provider.deleteVariable(name);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Update a cloud variable in the runtime based on the message received
|
|
139
|
+
* from the cloud provider.
|
|
140
|
+
* @param {VarData} varUpdate A {@link VarData} object describing
|
|
141
|
+
* a cloud variable update received from the cloud data provider.
|
|
142
|
+
*/
|
|
143
|
+
updateCloudVariable (varUpdate) {
|
|
144
|
+
const varName = varUpdate.name;
|
|
145
|
+
|
|
146
|
+
const variable = this.stage.lookupVariableByNameAndType(varName, Variable.SCALAR_TYPE);
|
|
147
|
+
if (!variable || !variable.isCloud) {
|
|
148
|
+
log.warn(`Received an update for a cloud variable that does not exist: ${varName}`);
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
variable.value = varUpdate.value;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Request the cloud data provider to close the web socket connection and
|
|
157
|
+
* clear this io device of references to the cloud data provider and the
|
|
158
|
+
* stage.
|
|
159
|
+
*/
|
|
160
|
+
clear () {
|
|
161
|
+
if (!this.provider) return;
|
|
162
|
+
|
|
163
|
+
this.provider.requestCloseConnection();
|
|
164
|
+
this.provider = null;
|
|
165
|
+
this.stage = null;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
module.exports = Cloud;
|