@scratch/scratch-vm 11.0.0-UEPR-176

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.
Files changed (178) hide show
  1. package/LICENSE +12 -0
  2. package/README.md +120 -0
  3. package/dist/node/extension-worker.js +2 -0
  4. package/dist/node/extension-worker.js.map +1 -0
  5. package/dist/node/scratch-vm.js +3 -0
  6. package/dist/node/scratch-vm.js.LICENSE.txt +205 -0
  7. package/dist/node/scratch-vm.js.map +1 -0
  8. package/dist/web/extension-worker.js +2 -0
  9. package/dist/web/extension-worker.js.map +1 -0
  10. package/dist/web/scratch-vm.js +3 -0
  11. package/dist/web/scratch-vm.js.LICENSE.txt +42 -0
  12. package/dist/web/scratch-vm.js.map +1 -0
  13. package/package.json +108 -0
  14. package/src/.eslintrc.js +7 -0
  15. package/src/blocks/scratch3_control.js +206 -0
  16. package/src/blocks/scratch3_core_example.js +69 -0
  17. package/src/blocks/scratch3_data.js +257 -0
  18. package/src/blocks/scratch3_event.js +137 -0
  19. package/src/blocks/scratch3_looks.js +612 -0
  20. package/src/blocks/scratch3_motion.js +288 -0
  21. package/src/blocks/scratch3_operators.js +154 -0
  22. package/src/blocks/scratch3_procedures.js +79 -0
  23. package/src/blocks/scratch3_sensing.js +336 -0
  24. package/src/blocks/scratch3_sound.js +350 -0
  25. package/src/dispatch/central-dispatch.js +143 -0
  26. package/src/dispatch/shared-dispatch.js +235 -0
  27. package/src/dispatch/worker-dispatch.js +110 -0
  28. package/src/engine/adapter.js +176 -0
  29. package/src/engine/block-utility.js +242 -0
  30. package/src/engine/blocks-execute-cache.js +19 -0
  31. package/src/engine/blocks-runtime-cache.js +78 -0
  32. package/src/engine/blocks.js +1299 -0
  33. package/src/engine/comment.js +56 -0
  34. package/src/engine/execute.js +580 -0
  35. package/src/engine/monitor-record.js +23 -0
  36. package/src/engine/mutation-adapter.js +48 -0
  37. package/src/engine/profiler.js +390 -0
  38. package/src/engine/runtime.js +2686 -0
  39. package/src/engine/scratch-blocks-constants.js +27 -0
  40. package/src/engine/sequencer.js +361 -0
  41. package/src/engine/stage-layering.js +29 -0
  42. package/src/engine/target.js +799 -0
  43. package/src/engine/thread.js +404 -0
  44. package/src/engine/variable.js +70 -0
  45. package/src/extension-support/argument-type.js +47 -0
  46. package/src/extension-support/block-type.js +50 -0
  47. package/src/extension-support/define-messages.js +18 -0
  48. package/src/extension-support/extension-manager.js +440 -0
  49. package/src/extension-support/extension-metadata.js +64 -0
  50. package/src/extension-support/extension-worker.js +59 -0
  51. package/src/extension-support/reporter-scope.js +18 -0
  52. package/src/extension-support/target-type.js +17 -0
  53. package/src/extensions/scratch3_boost/index.js +2113 -0
  54. package/src/extensions/scratch3_ev3/index.js +1355 -0
  55. package/src/extensions/scratch3_gdx_for/index.js +981 -0
  56. package/src/extensions/scratch3_gdx_for/scratch-link-device-adapter.js +44 -0
  57. package/src/extensions/scratch3_makeymakey/index.js +396 -0
  58. package/src/extensions/scratch3_microbit/index.js +984 -0
  59. package/src/extensions/scratch3_music/assets/drums/1-snare.mp3 +0 -0
  60. package/src/extensions/scratch3_music/assets/drums/10-wood-block.mp3 +0 -0
  61. package/src/extensions/scratch3_music/assets/drums/11-cowbell.mp3 +0 -0
  62. package/src/extensions/scratch3_music/assets/drums/12-triangle.mp3 +0 -0
  63. package/src/extensions/scratch3_music/assets/drums/13-bongo.mp3 +0 -0
  64. package/src/extensions/scratch3_music/assets/drums/14-conga.mp3 +0 -0
  65. package/src/extensions/scratch3_music/assets/drums/15-cabasa.mp3 +0 -0
  66. package/src/extensions/scratch3_music/assets/drums/16-guiro.mp3 +0 -0
  67. package/src/extensions/scratch3_music/assets/drums/17-vibraslap.mp3 +0 -0
  68. package/src/extensions/scratch3_music/assets/drums/18-cuica.mp3 +0 -0
  69. package/src/extensions/scratch3_music/assets/drums/2-bass-drum.mp3 +0 -0
  70. package/src/extensions/scratch3_music/assets/drums/3-side-stick.mp3 +0 -0
  71. package/src/extensions/scratch3_music/assets/drums/4-crash-cymbal.mp3 +0 -0
  72. package/src/extensions/scratch3_music/assets/drums/5-open-hi-hat.mp3 +0 -0
  73. package/src/extensions/scratch3_music/assets/drums/6-closed-hi-hat.mp3 +0 -0
  74. package/src/extensions/scratch3_music/assets/drums/7-tambourine.mp3 +0 -0
  75. package/src/extensions/scratch3_music/assets/drums/8-hand-clap.mp3 +0 -0
  76. package/src/extensions/scratch3_music/assets/drums/9-claves.mp3 +0 -0
  77. package/src/extensions/scratch3_music/assets/instruments/1-piano/108.mp3 +0 -0
  78. package/src/extensions/scratch3_music/assets/instruments/1-piano/24.mp3 +0 -0
  79. package/src/extensions/scratch3_music/assets/instruments/1-piano/36.mp3 +0 -0
  80. package/src/extensions/scratch3_music/assets/instruments/1-piano/48.mp3 +0 -0
  81. package/src/extensions/scratch3_music/assets/instruments/1-piano/60.mp3 +0 -0
  82. package/src/extensions/scratch3_music/assets/instruments/1-piano/72.mp3 +0 -0
  83. package/src/extensions/scratch3_music/assets/instruments/1-piano/84.mp3 +0 -0
  84. package/src/extensions/scratch3_music/assets/instruments/1-piano/96.mp3 +0 -0
  85. package/src/extensions/scratch3_music/assets/instruments/10-clarinet/48.mp3 +0 -0
  86. package/src/extensions/scratch3_music/assets/instruments/10-clarinet/60.mp3 +0 -0
  87. package/src/extensions/scratch3_music/assets/instruments/11-saxophone/36.mp3 +0 -0
  88. package/src/extensions/scratch3_music/assets/instruments/11-saxophone/60.mp3 +0 -0
  89. package/src/extensions/scratch3_music/assets/instruments/11-saxophone/84.mp3 +0 -0
  90. package/src/extensions/scratch3_music/assets/instruments/12-flute/60.mp3 +0 -0
  91. package/src/extensions/scratch3_music/assets/instruments/12-flute/72.mp3 +0 -0
  92. package/src/extensions/scratch3_music/assets/instruments/13-wooden-flute/60.mp3 +0 -0
  93. package/src/extensions/scratch3_music/assets/instruments/13-wooden-flute/72.mp3 +0 -0
  94. package/src/extensions/scratch3_music/assets/instruments/14-bassoon/36.mp3 +0 -0
  95. package/src/extensions/scratch3_music/assets/instruments/14-bassoon/48.mp3 +0 -0
  96. package/src/extensions/scratch3_music/assets/instruments/14-bassoon/60.mp3 +0 -0
  97. package/src/extensions/scratch3_music/assets/instruments/15-choir/48.mp3 +0 -0
  98. package/src/extensions/scratch3_music/assets/instruments/15-choir/60.mp3 +0 -0
  99. package/src/extensions/scratch3_music/assets/instruments/15-choir/72.mp3 +0 -0
  100. package/src/extensions/scratch3_music/assets/instruments/16-vibraphone/60.mp3 +0 -0
  101. package/src/extensions/scratch3_music/assets/instruments/16-vibraphone/72.mp3 +0 -0
  102. package/src/extensions/scratch3_music/assets/instruments/17-music-box/60.mp3 +0 -0
  103. package/src/extensions/scratch3_music/assets/instruments/18-steel-drum/60.mp3 +0 -0
  104. package/src/extensions/scratch3_music/assets/instruments/19-marimba/60.mp3 +0 -0
  105. package/src/extensions/scratch3_music/assets/instruments/2-electric-piano/60.mp3 +0 -0
  106. package/src/extensions/scratch3_music/assets/instruments/20-synth-lead/60.mp3 +0 -0
  107. package/src/extensions/scratch3_music/assets/instruments/21-synth-pad/60.mp3 +0 -0
  108. package/src/extensions/scratch3_music/assets/instruments/3-organ/60.mp3 +0 -0
  109. package/src/extensions/scratch3_music/assets/instruments/4-guitar/60.mp3 +0 -0
  110. package/src/extensions/scratch3_music/assets/instruments/5-electric-guitar/60.mp3 +0 -0
  111. package/src/extensions/scratch3_music/assets/instruments/6-bass/36.mp3 +0 -0
  112. package/src/extensions/scratch3_music/assets/instruments/6-bass/48.mp3 +0 -0
  113. package/src/extensions/scratch3_music/assets/instruments/7-pizzicato/60.mp3 +0 -0
  114. package/src/extensions/scratch3_music/assets/instruments/8-cello/36.mp3 +0 -0
  115. package/src/extensions/scratch3_music/assets/instruments/8-cello/48.mp3 +0 -0
  116. package/src/extensions/scratch3_music/assets/instruments/8-cello/60.mp3 +0 -0
  117. package/src/extensions/scratch3_music/assets/instruments/9-trombone/36.mp3 +0 -0
  118. package/src/extensions/scratch3_music/assets/instruments/9-trombone/48.mp3 +0 -0
  119. package/src/extensions/scratch3_music/assets/instruments/9-trombone/60.mp3 +0 -0
  120. package/src/extensions/scratch3_music/index.js +1333 -0
  121. package/src/extensions/scratch3_music/manifest.js +63 -0
  122. package/src/extensions/scratch3_pen/index.js +770 -0
  123. package/src/extensions/scratch3_speech2text/index.js +700 -0
  124. package/src/extensions/scratch3_text2speech/index.js +766 -0
  125. package/src/extensions/scratch3_translate/index.js +286 -0
  126. package/src/extensions/scratch3_video_sensing/debug.js +13 -0
  127. package/src/extensions/scratch3_video_sensing/index.js +594 -0
  128. package/src/extensions/scratch3_video_sensing/library.js +384 -0
  129. package/src/extensions/scratch3_video_sensing/math.js +76 -0
  130. package/src/extensions/scratch3_video_sensing/view.js +509 -0
  131. package/src/extensions/scratch3_wedo2/index.js +1616 -0
  132. package/src/import/load-costume.js +416 -0
  133. package/src/import/load-sound.js +116 -0
  134. package/src/index.js +10 -0
  135. package/src/io/ble.js +256 -0
  136. package/src/io/bt.js +202 -0
  137. package/src/io/clock.js +39 -0
  138. package/src/io/cloud.js +169 -0
  139. package/src/io/keyboard.js +146 -0
  140. package/src/io/mouse.js +145 -0
  141. package/src/io/mouseWheel.js +28 -0
  142. package/src/io/userData.js +24 -0
  143. package/src/io/video.js +210 -0
  144. package/src/playground/benchmark.css +83 -0
  145. package/src/playground/benchmark.js +753 -0
  146. package/src/playground/index.html +111 -0
  147. package/src/playground/suite.css +78 -0
  148. package/src/playground/suite.html +25 -0
  149. package/src/playground/suite.js +544 -0
  150. package/src/playground/video-sensing.html +18 -0
  151. package/src/playground/video-sensing.js +133 -0
  152. package/src/serialization/deserialize-assets.js +178 -0
  153. package/src/serialization/sb2.js +1295 -0
  154. package/src/serialization/sb2_specmap.js +1818 -0
  155. package/src/serialization/sb3.js +1320 -0
  156. package/src/serialization/serialize-assets.js +60 -0
  157. package/src/sprites/rendered-target.js +1115 -0
  158. package/src/sprites/sprite.js +177 -0
  159. package/src/util/base64-util.js +48 -0
  160. package/src/util/cast.js +218 -0
  161. package/src/util/clone.js +17 -0
  162. package/src/util/color.js +204 -0
  163. package/src/util/fetch-with-timeout.js +53 -0
  164. package/src/util/get-monitor-id.js +33 -0
  165. package/src/util/jsonrpc.js +114 -0
  166. package/src/util/log.js +4 -0
  167. package/src/util/math-util.js +122 -0
  168. package/src/util/maybe-format-message.js +18 -0
  169. package/src/util/new-block-ids.js +33 -0
  170. package/src/util/rateLimiter.js +73 -0
  171. package/src/util/scratch-link-websocket.js +133 -0
  172. package/src/util/string-util.js +94 -0
  173. package/src/util/task-queue.js +203 -0
  174. package/src/util/timer.js +103 -0
  175. package/src/util/uid.js +29 -0
  176. package/src/util/variable-util.js +48 -0
  177. package/src/util/xml-escape.js +33 -0
  178. package/src/virtual-machine.js +1586 -0
@@ -0,0 +1,984 @@
1
+ const ArgumentType = require('../../extension-support/argument-type');
2
+ const BlockType = require('../../extension-support/block-type');
3
+ const log = require('../../util/log');
4
+ const cast = require('../../util/cast');
5
+ const formatMessage = require('format-message');
6
+ const BLE = require('../../io/ble');
7
+ const Base64Util = require('../../util/base64-util');
8
+
9
+ /**
10
+ * Icon png to be displayed at the left edge of each extension block, encoded as a data URI.
11
+ * @type {string}
12
+ */
13
+ // eslint-disable-next-line max-len
14
+ const blockIconURI = 'data:image/png;base64,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';
15
+
16
+ /**
17
+ * Enum for micro:bit BLE command protocol.
18
+ * https://github.com/scratchfoundation/scratch-microbit-firmware/blob/master/protocol.md
19
+ * @readonly
20
+ * @enum {number}
21
+ */
22
+ const BLECommand = {
23
+ CMD_PIN_CONFIG: 0x80,
24
+ CMD_DISPLAY_TEXT: 0x81,
25
+ CMD_DISPLAY_LED: 0x82
26
+ };
27
+
28
+
29
+ /**
30
+ * A time interval to wait (in milliseconds) before reporting to the BLE socket
31
+ * that data has stopped coming from the peripheral.
32
+ */
33
+ const BLETimeout = 4500;
34
+
35
+ /**
36
+ * A time interval to wait (in milliseconds) while a block that sends a BLE message is running.
37
+ * @type {number}
38
+ */
39
+ const BLESendInterval = 100;
40
+
41
+ /**
42
+ * A string to report to the BLE socket when the micro:bit has stopped receiving data.
43
+ * @type {string}
44
+ */
45
+ const BLEDataStoppedError = 'micro:bit extension stopped receiving data';
46
+
47
+ /**
48
+ * Enum for micro:bit protocol.
49
+ * https://github.com/scratchfoundation/scratch-microbit-firmware/blob/master/protocol.md
50
+ * @readonly
51
+ * @enum {string}
52
+ */
53
+ const BLEUUID = {
54
+ service: 0xf005,
55
+ rxChar: '5261da01-fa7e-42ab-850b-7c80220097cc',
56
+ txChar: '5261da02-fa7e-42ab-850b-7c80220097cc'
57
+ };
58
+
59
+ /**
60
+ * Manage communication with a MicroBit peripheral over a Scrath Link client socket.
61
+ */
62
+ class MicroBit {
63
+
64
+ /**
65
+ * Construct a MicroBit communication object.
66
+ * @param {Runtime} runtime - the Scratch 3.0 runtime
67
+ * @param {string} extensionId - the id of the extension
68
+ */
69
+ constructor (runtime, extensionId) {
70
+
71
+ /**
72
+ * The Scratch 3.0 runtime used to trigger the green flag button.
73
+ * @type {Runtime}
74
+ * @private
75
+ */
76
+ this._runtime = runtime;
77
+
78
+ /**
79
+ * The BluetoothLowEnergy connection socket for reading/writing peripheral data.
80
+ * @type {BLE}
81
+ * @private
82
+ */
83
+ this._ble = null;
84
+ this._runtime.registerPeripheralExtension(extensionId, this);
85
+
86
+ /**
87
+ * The id of the extension this peripheral belongs to.
88
+ */
89
+ this._extensionId = extensionId;
90
+
91
+ /**
92
+ * The most recently received value for each sensor.
93
+ * @type {Object.<string, number>}
94
+ * @private
95
+ */
96
+ this._sensors = {
97
+ tiltX: 0,
98
+ tiltY: 0,
99
+ buttonA: 0,
100
+ buttonB: 0,
101
+ touchPins: [0, 0, 0],
102
+ gestureState: 0,
103
+ ledMatrixState: new Uint8Array(5)
104
+ };
105
+
106
+ /**
107
+ * The most recently received value for each gesture.
108
+ * @type {Object.<string, Object>}
109
+ * @private
110
+ */
111
+ this._gestures = {
112
+ moving: false,
113
+ move: {
114
+ active: false,
115
+ timeout: false
116
+ },
117
+ shake: {
118
+ active: false,
119
+ timeout: false
120
+ },
121
+ jump: {
122
+ active: false,
123
+ timeout: false
124
+ }
125
+ };
126
+
127
+ /**
128
+ * Interval ID for data reading timeout.
129
+ * @type {number}
130
+ * @private
131
+ */
132
+ this._timeoutID = null;
133
+
134
+ /**
135
+ * A flag that is true while we are busy sending data to the BLE socket.
136
+ * @type {boolean}
137
+ * @private
138
+ */
139
+ this._busy = false;
140
+
141
+ /**
142
+ * ID for a timeout which is used to clear the busy flag if it has been
143
+ * true for a long time.
144
+ */
145
+ this._busyTimeoutID = null;
146
+
147
+ this.reset = this.reset.bind(this);
148
+ this._onConnect = this._onConnect.bind(this);
149
+ this._onMessage = this._onMessage.bind(this);
150
+ }
151
+
152
+ /**
153
+ * @param {string} text - the text to display.
154
+ * @return {Promise} - a Promise that resolves when writing to peripheral.
155
+ */
156
+ displayText (text) {
157
+ const output = new Uint8Array(text.length);
158
+ for (let i = 0; i < text.length; i++) {
159
+ output[i] = text.charCodeAt(i);
160
+ }
161
+ return this.send(BLECommand.CMD_DISPLAY_TEXT, output);
162
+ }
163
+
164
+ /**
165
+ * @param {Uint8Array} matrix - the matrix to display.
166
+ * @return {Promise} - a Promise that resolves when writing to peripheral.
167
+ */
168
+ displayMatrix (matrix) {
169
+ return this.send(BLECommand.CMD_DISPLAY_LED, matrix);
170
+ }
171
+
172
+ /**
173
+ * @return {number} - the latest value received for the tilt sensor's tilt about the X axis.
174
+ */
175
+ get tiltX () {
176
+ return this._sensors.tiltX;
177
+ }
178
+
179
+ /**
180
+ * @return {number} - the latest value received for the tilt sensor's tilt about the Y axis.
181
+ */
182
+ get tiltY () {
183
+ return this._sensors.tiltY;
184
+ }
185
+
186
+ /**
187
+ * @return {boolean} - the latest value received for the A button.
188
+ */
189
+ get buttonA () {
190
+ return this._sensors.buttonA;
191
+ }
192
+
193
+ /**
194
+ * @return {boolean} - the latest value received for the B button.
195
+ */
196
+ get buttonB () {
197
+ return this._sensors.buttonB;
198
+ }
199
+
200
+ /**
201
+ * @return {number} - the latest value received for the motion gesture states.
202
+ */
203
+ get gestureState () {
204
+ return this._sensors.gestureState;
205
+ }
206
+
207
+ /**
208
+ * @return {Uint8Array} - the current state of the 5x5 LED matrix.
209
+ */
210
+ get ledMatrixState () {
211
+ return this._sensors.ledMatrixState;
212
+ }
213
+
214
+ /**
215
+ * Called by the runtime when user wants to scan for a peripheral.
216
+ */
217
+ scan () {
218
+ if (this._ble) {
219
+ this._ble.disconnect();
220
+ }
221
+ this._ble = new BLE(this._runtime, this._extensionId, {
222
+ filters: [
223
+ {services: [BLEUUID.service]}
224
+ ]
225
+ }, this._onConnect, this.reset);
226
+ }
227
+
228
+ /**
229
+ * Called by the runtime when user wants to connect to a certain peripheral.
230
+ * @param {number} id - the id of the peripheral to connect to.
231
+ */
232
+ connect (id) {
233
+ if (this._ble) {
234
+ this._ble.connectPeripheral(id);
235
+ }
236
+ }
237
+
238
+ /**
239
+ * Disconnect from the micro:bit.
240
+ */
241
+ disconnect () {
242
+ if (this._ble) {
243
+ this._ble.disconnect();
244
+ }
245
+
246
+ this.reset();
247
+ }
248
+
249
+ /**
250
+ * Reset all the state and timeout/interval ids.
251
+ */
252
+ reset () {
253
+ if (this._timeoutID) {
254
+ window.clearTimeout(this._timeoutID);
255
+ this._timeoutID = null;
256
+ }
257
+ }
258
+
259
+ /**
260
+ * Return true if connected to the micro:bit.
261
+ * @return {boolean} - whether the micro:bit is connected.
262
+ */
263
+ isConnected () {
264
+ let connected = false;
265
+ if (this._ble) {
266
+ connected = this._ble.isConnected();
267
+ }
268
+ return connected;
269
+ }
270
+
271
+ /**
272
+ * Send a message to the peripheral BLE socket.
273
+ * @param {number} command - the BLE command hex.
274
+ * @param {Uint8Array} message - the message to write
275
+ */
276
+ send (command, message) {
277
+ if (!this.isConnected()) return;
278
+ if (this._busy) return;
279
+
280
+ // Set a busy flag so that while we are sending a message and waiting for
281
+ // the response, additional messages are ignored.
282
+ this._busy = true;
283
+
284
+ // Set a timeout after which to reset the busy flag. This is used in case
285
+ // a BLE message was sent for which we never received a response, because
286
+ // e.g. the peripheral was turned off after the message was sent. We reset
287
+ // the busy flag after a while so that it is possible to try again later.
288
+ this._busyTimeoutID = window.setTimeout(() => {
289
+ this._busy = false;
290
+ }, 5000);
291
+
292
+ const output = new Uint8Array(message.length + 1);
293
+ output[0] = command; // attach command to beginning of message
294
+ for (let i = 0; i < message.length; i++) {
295
+ output[i + 1] = message[i];
296
+ }
297
+ const data = Base64Util.uint8ArrayToBase64(output);
298
+
299
+ this._ble.write(BLEUUID.service, BLEUUID.txChar, data, 'base64', true).then(
300
+ () => {
301
+ this._busy = false;
302
+ window.clearTimeout(this._busyTimeoutID);
303
+ }
304
+ );
305
+ }
306
+
307
+ /**
308
+ * Starts reading data from peripheral after BLE has connected to it.
309
+ * @private
310
+ */
311
+ _onConnect () {
312
+ this._ble.read(BLEUUID.service, BLEUUID.rxChar, true, this._onMessage);
313
+ this._timeoutID = window.setTimeout(
314
+ () => this._ble.handleDisconnectError(BLEDataStoppedError),
315
+ BLETimeout
316
+ );
317
+ }
318
+
319
+ /**
320
+ * Process the sensor data from the incoming BLE characteristic.
321
+ * @param {object} base64 - the incoming BLE data.
322
+ * @private
323
+ */
324
+ _onMessage (base64) {
325
+ // parse data
326
+ const data = Base64Util.base64ToUint8Array(base64);
327
+
328
+ this._sensors.tiltX = data[1] | (data[0] << 8);
329
+ if (this._sensors.tiltX > (1 << 15)) this._sensors.tiltX -= (1 << 16);
330
+ this._sensors.tiltY = data[3] | (data[2] << 8);
331
+ if (this._sensors.tiltY > (1 << 15)) this._sensors.tiltY -= (1 << 16);
332
+
333
+ this._sensors.buttonA = data[4];
334
+ this._sensors.buttonB = data[5];
335
+
336
+ this._sensors.touchPins[0] = data[6];
337
+ this._sensors.touchPins[1] = data[7];
338
+ this._sensors.touchPins[2] = data[8];
339
+
340
+ this._sensors.gestureState = data[9];
341
+
342
+ // cancel disconnect timeout and start a new one
343
+ window.clearTimeout(this._timeoutID);
344
+ this._timeoutID = window.setTimeout(
345
+ () => this._ble.handleDisconnectError(BLEDataStoppedError),
346
+ BLETimeout
347
+ );
348
+ }
349
+
350
+ /**
351
+ * @param {number} pin - the pin to check touch state.
352
+ * @return {number} - the latest value received for the touch pin states.
353
+ * @private
354
+ */
355
+ _checkPinState (pin) {
356
+ return this._sensors.touchPins[pin];
357
+ }
358
+ }
359
+
360
+ /**
361
+ * Enum for tilt sensor direction.
362
+ * @readonly
363
+ * @enum {string}
364
+ */
365
+ const MicroBitTiltDirection = {
366
+ FRONT: 'front',
367
+ BACK: 'back',
368
+ LEFT: 'left',
369
+ RIGHT: 'right',
370
+ ANY: 'any'
371
+ };
372
+
373
+ /**
374
+ * Enum for micro:bit gestures.
375
+ * @readonly
376
+ * @enum {string}
377
+ */
378
+ const MicroBitGestures = {
379
+ MOVED: 'moved',
380
+ SHAKEN: 'shaken',
381
+ JUMPED: 'jumped'
382
+ };
383
+
384
+ /**
385
+ * Enum for micro:bit buttons.
386
+ * @readonly
387
+ * @enum {string}
388
+ */
389
+ const MicroBitButtons = {
390
+ A: 'A',
391
+ B: 'B',
392
+ ANY: 'any'
393
+ };
394
+
395
+ /**
396
+ * Enum for micro:bit pin states.
397
+ * @readonly
398
+ * @enum {string}
399
+ */
400
+ const MicroBitPinState = {
401
+ ON: 'on',
402
+ OFF: 'off'
403
+ };
404
+
405
+ /**
406
+ * Scratch 3.0 blocks to interact with a MicroBit peripheral.
407
+ */
408
+ class Scratch3MicroBitBlocks {
409
+
410
+ /**
411
+ * @return {string} - the name of this extension.
412
+ */
413
+ static get EXTENSION_NAME () {
414
+ return 'micro:bit';
415
+ }
416
+
417
+ /**
418
+ * @return {string} - the ID of this extension.
419
+ */
420
+ static get EXTENSION_ID () {
421
+ return 'microbit';
422
+ }
423
+
424
+ /**
425
+ * @return {number} - the tilt sensor counts as "tilted" if its tilt angle meets or exceeds this threshold.
426
+ */
427
+ static get TILT_THRESHOLD () {
428
+ return 15;
429
+ }
430
+
431
+ /**
432
+ * @return {array} - text and values for each buttons menu element
433
+ */
434
+ get BUTTONS_MENU () {
435
+ return [
436
+ {
437
+ text: 'A',
438
+ value: MicroBitButtons.A
439
+ },
440
+ {
441
+ text: 'B',
442
+ value: MicroBitButtons.B
443
+ },
444
+ {
445
+ text: formatMessage({
446
+ id: 'microbit.buttonsMenu.any',
447
+ default: 'any',
448
+ description: 'label for "any" element in button picker for micro:bit extension'
449
+ }),
450
+ value: MicroBitButtons.ANY
451
+ }
452
+ ];
453
+ }
454
+
455
+ /**
456
+ * @return {array} - text and values for each gestures menu element
457
+ */
458
+ get GESTURES_MENU () {
459
+ return [
460
+ {
461
+ text: formatMessage({
462
+ id: 'microbit.gesturesMenu.moved',
463
+ default: 'moved',
464
+ description: 'label for moved gesture in gesture picker for micro:bit extension'
465
+ }),
466
+ value: MicroBitGestures.MOVED
467
+ },
468
+ {
469
+ text: formatMessage({
470
+ id: 'microbit.gesturesMenu.shaken',
471
+ default: 'shaken',
472
+ description: 'label for shaken gesture in gesture picker for micro:bit extension'
473
+ }),
474
+ value: MicroBitGestures.SHAKEN
475
+ },
476
+ {
477
+ text: formatMessage({
478
+ id: 'microbit.gesturesMenu.jumped',
479
+ default: 'jumped',
480
+ description: 'label for jumped gesture in gesture picker for micro:bit extension'
481
+ }),
482
+ value: MicroBitGestures.JUMPED
483
+ }
484
+ ];
485
+ }
486
+
487
+ /**
488
+ * @return {array} - text and values for each pin state menu element
489
+ */
490
+ get PIN_STATE_MENU () {
491
+ return [
492
+ {
493
+ text: formatMessage({
494
+ id: 'microbit.pinStateMenu.on',
495
+ default: 'on',
496
+ description: 'label for on element in pin state picker for micro:bit extension'
497
+ }),
498
+ value: MicroBitPinState.ON
499
+ },
500
+ {
501
+ text: formatMessage({
502
+ id: 'microbit.pinStateMenu.off',
503
+ default: 'off',
504
+ description: 'label for off element in pin state picker for micro:bit extension'
505
+ }),
506
+ value: MicroBitPinState.OFF
507
+ }
508
+ ];
509
+ }
510
+
511
+ /**
512
+ * @return {array} - text and values for each tilt direction menu element
513
+ */
514
+ get TILT_DIRECTION_MENU () {
515
+ return [
516
+ {
517
+ text: formatMessage({
518
+ id: 'microbit.tiltDirectionMenu.front',
519
+ default: 'front',
520
+ description: 'label for front element in tilt direction picker for micro:bit extension'
521
+ }),
522
+ value: MicroBitTiltDirection.FRONT
523
+ },
524
+ {
525
+ text: formatMessage({
526
+ id: 'microbit.tiltDirectionMenu.back',
527
+ default: 'back',
528
+ description: 'label for back element in tilt direction picker for micro:bit extension'
529
+ }),
530
+ value: MicroBitTiltDirection.BACK
531
+ },
532
+ {
533
+ text: formatMessage({
534
+ id: 'microbit.tiltDirectionMenu.left',
535
+ default: 'left',
536
+ description: 'label for left element in tilt direction picker for micro:bit extension'
537
+ }),
538
+ value: MicroBitTiltDirection.LEFT
539
+ },
540
+ {
541
+ text: formatMessage({
542
+ id: 'microbit.tiltDirectionMenu.right',
543
+ default: 'right',
544
+ description: 'label for right element in tilt direction picker for micro:bit extension'
545
+ }),
546
+ value: MicroBitTiltDirection.RIGHT
547
+ }
548
+ ];
549
+ }
550
+
551
+ /**
552
+ * @return {array} - text and values for each tilt direction (plus "any") menu element
553
+ */
554
+ get TILT_DIRECTION_ANY_MENU () {
555
+ return [
556
+ ...this.TILT_DIRECTION_MENU,
557
+ {
558
+ text: formatMessage({
559
+ id: 'microbit.tiltDirectionMenu.any',
560
+ default: 'any',
561
+ description: 'label for any direction element in tilt direction picker for micro:bit extension'
562
+ }),
563
+ value: MicroBitTiltDirection.ANY
564
+ }
565
+ ];
566
+ }
567
+
568
+ /**
569
+ * Construct a set of MicroBit blocks.
570
+ * @param {Runtime} runtime - the Scratch 3.0 runtime.
571
+ */
572
+ constructor (runtime) {
573
+ /**
574
+ * The Scratch 3.0 runtime.
575
+ * @type {Runtime}
576
+ */
577
+ this.runtime = runtime;
578
+
579
+ // Create a new MicroBit peripheral instance
580
+ this._peripheral = new MicroBit(this.runtime, Scratch3MicroBitBlocks.EXTENSION_ID);
581
+ }
582
+
583
+ /**
584
+ * @returns {object} metadata for this extension and its blocks.
585
+ */
586
+ getInfo () {
587
+ return {
588
+ id: Scratch3MicroBitBlocks.EXTENSION_ID,
589
+ name: Scratch3MicroBitBlocks.EXTENSION_NAME,
590
+ blockIconURI: blockIconURI,
591
+ showStatusButton: true,
592
+ blocks: [
593
+ {
594
+ opcode: 'whenButtonPressed',
595
+ text: formatMessage({
596
+ id: 'microbit.whenButtonPressed',
597
+ default: 'when [BTN] button pressed',
598
+ description: 'when the selected button on the micro:bit is pressed'
599
+ }),
600
+ blockType: BlockType.HAT,
601
+ arguments: {
602
+ BTN: {
603
+ type: ArgumentType.STRING,
604
+ menu: 'buttons',
605
+ defaultValue: MicroBitButtons.A
606
+ }
607
+ }
608
+ },
609
+ {
610
+ opcode: 'isButtonPressed',
611
+ text: formatMessage({
612
+ id: 'microbit.isButtonPressed',
613
+ default: '[BTN] button pressed?',
614
+ description: 'is the selected button on the micro:bit pressed?'
615
+ }),
616
+ blockType: BlockType.BOOLEAN,
617
+ arguments: {
618
+ BTN: {
619
+ type: ArgumentType.STRING,
620
+ menu: 'buttons',
621
+ defaultValue: MicroBitButtons.A
622
+ }
623
+ }
624
+ },
625
+ '---',
626
+ {
627
+ opcode: 'whenGesture',
628
+ text: formatMessage({
629
+ id: 'microbit.whenGesture',
630
+ default: 'when [GESTURE]',
631
+ description: 'when the selected gesture is detected by the micro:bit'
632
+ }),
633
+ blockType: BlockType.HAT,
634
+ arguments: {
635
+ GESTURE: {
636
+ type: ArgumentType.STRING,
637
+ menu: 'gestures',
638
+ defaultValue: MicroBitGestures.MOVED
639
+ }
640
+ }
641
+ },
642
+ '---',
643
+ {
644
+ opcode: 'displaySymbol',
645
+ text: formatMessage({
646
+ id: 'microbit.displaySymbol',
647
+ default: 'display [MATRIX]',
648
+ description: 'display a pattern on the micro:bit display'
649
+ }),
650
+ blockType: BlockType.COMMAND,
651
+ arguments: {
652
+ MATRIX: {
653
+ type: ArgumentType.MATRIX,
654
+ defaultValue: '0101010101100010101000100'
655
+ }
656
+ }
657
+ },
658
+ {
659
+ opcode: 'displayText',
660
+ text: formatMessage({
661
+ id: 'microbit.displayText',
662
+ default: 'display text [TEXT]',
663
+ description: 'display text on the micro:bit display'
664
+ }),
665
+ blockType: BlockType.COMMAND,
666
+ arguments: {
667
+ TEXT: {
668
+ type: ArgumentType.STRING,
669
+ defaultValue: formatMessage({
670
+ id: 'microbit.defaultTextToDisplay',
671
+ default: 'Hello!',
672
+ description: `default text to display.
673
+ IMPORTANT - the micro:bit only supports letters a-z, A-Z.
674
+ Please substitute a default word in your language
675
+ that can be written with those characters,
676
+ substitute non-accented characters or leave it as "Hello!".
677
+ Check the micro:bit site documentation for details`
678
+ })
679
+ }
680
+ }
681
+ },
682
+ {
683
+ opcode: 'displayClear',
684
+ text: formatMessage({
685
+ id: 'microbit.clearDisplay',
686
+ default: 'clear display',
687
+ description: 'display nothing on the micro:bit display'
688
+ }),
689
+ blockType: BlockType.COMMAND
690
+ },
691
+ '---',
692
+ {
693
+ opcode: 'whenTilted',
694
+ text: formatMessage({
695
+ id: 'microbit.whenTilted',
696
+ default: 'when tilted [DIRECTION]',
697
+ description: 'when the micro:bit is tilted in a direction'
698
+ }),
699
+ blockType: BlockType.HAT,
700
+ arguments: {
701
+ DIRECTION: {
702
+ type: ArgumentType.STRING,
703
+ menu: 'tiltDirectionAny',
704
+ defaultValue: MicroBitTiltDirection.ANY
705
+ }
706
+ }
707
+ },
708
+ {
709
+ opcode: 'isTilted',
710
+ text: formatMessage({
711
+ id: 'microbit.isTilted',
712
+ default: 'tilted [DIRECTION]?',
713
+ description: 'is the micro:bit is tilted in a direction?'
714
+ }),
715
+ blockType: BlockType.BOOLEAN,
716
+ arguments: {
717
+ DIRECTION: {
718
+ type: ArgumentType.STRING,
719
+ menu: 'tiltDirectionAny',
720
+ defaultValue: MicroBitTiltDirection.ANY
721
+ }
722
+ }
723
+ },
724
+ {
725
+ opcode: 'getTiltAngle',
726
+ text: formatMessage({
727
+ id: 'microbit.tiltAngle',
728
+ default: 'tilt angle [DIRECTION]',
729
+ description: 'how much the micro:bit is tilted in a direction'
730
+ }),
731
+ blockType: BlockType.REPORTER,
732
+ arguments: {
733
+ DIRECTION: {
734
+ type: ArgumentType.STRING,
735
+ menu: 'tiltDirection',
736
+ defaultValue: MicroBitTiltDirection.FRONT
737
+ }
738
+ }
739
+ },
740
+ '---',
741
+ {
742
+ opcode: 'whenPinConnected',
743
+ text: formatMessage({
744
+ id: 'microbit.whenPinConnected',
745
+ default: 'when pin [PIN] connected',
746
+ description: 'when the pin detects a connection to Earth/Ground'
747
+
748
+ }),
749
+ blockType: BlockType.HAT,
750
+ arguments: {
751
+ PIN: {
752
+ type: ArgumentType.STRING,
753
+ menu: 'touchPins',
754
+ defaultValue: '0'
755
+ }
756
+ }
757
+ }
758
+ ],
759
+ menus: {
760
+ buttons: {
761
+ acceptReporters: true,
762
+ items: this.BUTTONS_MENU
763
+ },
764
+ gestures: {
765
+ acceptReporters: true,
766
+ items: this.GESTURES_MENU
767
+ },
768
+ pinState: {
769
+ acceptReporters: true,
770
+ items: this.PIN_STATE_MENU
771
+ },
772
+ tiltDirection: {
773
+ acceptReporters: true,
774
+ items: this.TILT_DIRECTION_MENU
775
+ },
776
+ tiltDirectionAny: {
777
+ acceptReporters: true,
778
+ items: this.TILT_DIRECTION_ANY_MENU
779
+ },
780
+ touchPins: {
781
+ acceptReporters: true,
782
+ items: ['0', '1', '2']
783
+ }
784
+ }
785
+ };
786
+ }
787
+
788
+ /**
789
+ * Test whether the A or B button is pressed
790
+ * @param {object} args - the block's arguments.
791
+ * @return {boolean} - true if the button is pressed.
792
+ */
793
+ whenButtonPressed (args) {
794
+ if (args.BTN === 'any') {
795
+ return this._peripheral.buttonA | this._peripheral.buttonB;
796
+ } else if (args.BTN === 'A') {
797
+ return this._peripheral.buttonA;
798
+ } else if (args.BTN === 'B') {
799
+ return this._peripheral.buttonB;
800
+ }
801
+ return false;
802
+ }
803
+
804
+ /**
805
+ * Test whether the A or B button is pressed
806
+ * @param {object} args - the block's arguments.
807
+ * @return {boolean} - true if the button is pressed.
808
+ */
809
+ isButtonPressed (args) {
810
+ if (args.BTN === 'any') {
811
+ return (this._peripheral.buttonA | this._peripheral.buttonB) !== 0;
812
+ } else if (args.BTN === 'A') {
813
+ return this._peripheral.buttonA !== 0;
814
+ } else if (args.BTN === 'B') {
815
+ return this._peripheral.buttonB !== 0;
816
+ }
817
+ return false;
818
+ }
819
+
820
+ /**
821
+ * Test whether the micro:bit is moving
822
+ * @param {object} args - the block's arguments.
823
+ * @return {boolean} - true if the micro:bit is moving.
824
+ */
825
+ whenGesture (args) {
826
+ const gesture = cast.toString(args.GESTURE);
827
+ if (gesture === 'moved') {
828
+ return (this._peripheral.gestureState >> 2) & 1;
829
+ } else if (gesture === 'shaken') {
830
+ return this._peripheral.gestureState & 1;
831
+ } else if (gesture === 'jumped') {
832
+ return (this._peripheral.gestureState >> 1) & 1;
833
+ }
834
+ return false;
835
+ }
836
+
837
+ /**
838
+ * Display a predefined symbol on the 5x5 LED matrix.
839
+ * @param {object} args - the block's arguments.
840
+ * @return {Promise} - a Promise that resolves after a tick.
841
+ */
842
+ displaySymbol (args) {
843
+ const symbol = cast.toString(args.MATRIX).replace(/\s/g, '');
844
+ const reducer = (accumulator, c, index) => {
845
+ const value = (c === '0') ? accumulator : accumulator + Math.pow(2, index);
846
+ return value;
847
+ };
848
+ const hex = symbol.split('').reduce(reducer, 0);
849
+ if (hex !== null) {
850
+ this._peripheral.ledMatrixState[0] = hex & 0x1F;
851
+ this._peripheral.ledMatrixState[1] = (hex >> 5) & 0x1F;
852
+ this._peripheral.ledMatrixState[2] = (hex >> 10) & 0x1F;
853
+ this._peripheral.ledMatrixState[3] = (hex >> 15) & 0x1F;
854
+ this._peripheral.ledMatrixState[4] = (hex >> 20) & 0x1F;
855
+ this._peripheral.displayMatrix(this._peripheral.ledMatrixState);
856
+ }
857
+
858
+ return new Promise(resolve => {
859
+ setTimeout(() => {
860
+ resolve();
861
+ }, BLESendInterval);
862
+ });
863
+ }
864
+
865
+ /**
866
+ * Display text on the 5x5 LED matrix.
867
+ * @param {object} args - the block's arguments.
868
+ * @return {Promise} - a Promise that resolves after the text is done printing.
869
+ * Note the limit is 19 characters
870
+ * The print time is calculated by multiplying the number of horizontal pixels
871
+ * by the default scroll delay of 120ms.
872
+ * The number of horizontal pixels = 6px for each character in the string,
873
+ * 1px before the string, and 5px after the string.
874
+ */
875
+ displayText (args) {
876
+ const text = String(args.TEXT).substring(0, 19);
877
+ if (text.length > 0) this._peripheral.displayText(text);
878
+ const yieldDelay = 120 * ((6 * text.length) + 6);
879
+
880
+ return new Promise(resolve => {
881
+ setTimeout(() => {
882
+ resolve();
883
+ }, yieldDelay);
884
+ });
885
+ }
886
+
887
+ /**
888
+ * Turn all 5x5 matrix LEDs off.
889
+ * @return {Promise} - a Promise that resolves after a tick.
890
+ */
891
+ displayClear () {
892
+ for (let i = 0; i < 5; i++) {
893
+ this._peripheral.ledMatrixState[i] = 0;
894
+ }
895
+ this._peripheral.displayMatrix(this._peripheral.ledMatrixState);
896
+
897
+ return new Promise(resolve => {
898
+ setTimeout(() => {
899
+ resolve();
900
+ }, BLESendInterval);
901
+ });
902
+ }
903
+
904
+ /**
905
+ * Test whether the tilt sensor is currently tilted.
906
+ * @param {object} args - the block's arguments.
907
+ * @property {TiltDirection} DIRECTION - the tilt direction to test (front, back, left, right, or any).
908
+ * @return {boolean} - true if the tilt sensor is tilted past a threshold in the specified direction.
909
+ */
910
+ whenTilted (args) {
911
+ return this._isTilted(args.DIRECTION);
912
+ }
913
+
914
+ /**
915
+ * Test whether the tilt sensor is currently tilted.
916
+ * @param {object} args - the block's arguments.
917
+ * @property {TiltDirection} DIRECTION - the tilt direction to test (front, back, left, right, or any).
918
+ * @return {boolean} - true if the tilt sensor is tilted past a threshold in the specified direction.
919
+ */
920
+ isTilted (args) {
921
+ return this._isTilted(args.DIRECTION);
922
+ }
923
+
924
+ /**
925
+ * @param {object} args - the block's arguments.
926
+ * @property {TiltDirection} DIRECTION - the direction (front, back, left, right) to check.
927
+ * @return {number} - the tilt sensor's angle in the specified direction.
928
+ * Note that getTiltAngle(front) = -getTiltAngle(back) and getTiltAngle(left) = -getTiltAngle(right).
929
+ */
930
+ getTiltAngle (args) {
931
+ return this._getTiltAngle(args.DIRECTION);
932
+ }
933
+
934
+ /**
935
+ * Test whether the tilt sensor is currently tilted.
936
+ * @param {TiltDirection} direction - the tilt direction to test (front, back, left, right, or any).
937
+ * @return {boolean} - true if the tilt sensor is tilted past a threshold in the specified direction.
938
+ * @private
939
+ */
940
+ _isTilted (direction) {
941
+ switch (direction) {
942
+ case MicroBitTiltDirection.ANY:
943
+ return (Math.abs(this._peripheral.tiltX / 10) >= Scratch3MicroBitBlocks.TILT_THRESHOLD) ||
944
+ (Math.abs(this._peripheral.tiltY / 10) >= Scratch3MicroBitBlocks.TILT_THRESHOLD);
945
+ default:
946
+ return this._getTiltAngle(direction) >= Scratch3MicroBitBlocks.TILT_THRESHOLD;
947
+ }
948
+ }
949
+
950
+ /**
951
+ * @param {TiltDirection} direction - the direction (front, back, left, right) to check.
952
+ * @return {number} - the tilt sensor's angle in the specified direction.
953
+ * Note that getTiltAngle(front) = -getTiltAngle(back) and getTiltAngle(left) = -getTiltAngle(right).
954
+ * @private
955
+ */
956
+ _getTiltAngle (direction) {
957
+ switch (direction) {
958
+ case MicroBitTiltDirection.FRONT:
959
+ return Math.round(this._peripheral.tiltY / -10);
960
+ case MicroBitTiltDirection.BACK:
961
+ return Math.round(this._peripheral.tiltY / 10);
962
+ case MicroBitTiltDirection.LEFT:
963
+ return Math.round(this._peripheral.tiltX / -10);
964
+ case MicroBitTiltDirection.RIGHT:
965
+ return Math.round(this._peripheral.tiltX / 10);
966
+ default:
967
+ log.warn(`Unknown tilt direction in _getTiltAngle: ${direction}`);
968
+ }
969
+ }
970
+
971
+ /**
972
+ * @param {object} args - the block's arguments.
973
+ * @return {boolean} - the touch pin state.
974
+ * @private
975
+ */
976
+ whenPinConnected (args) {
977
+ const pin = parseInt(args.PIN, 10);
978
+ if (isNaN(pin)) return;
979
+ if (pin < 0 || pin > 2) return false;
980
+ return this._peripheral._checkPinState(pin);
981
+ }
982
+ }
983
+
984
+ module.exports = Scratch3MicroBitBlocks;