@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.
- package/LICENSE +12 -0
- package/README.md +120 -0
- package/dist/node/extension-worker.js +2 -0
- package/dist/node/extension-worker.js.map +1 -0
- package/dist/node/scratch-vm.js +3 -0
- package/dist/node/scratch-vm.js.LICENSE.txt +205 -0
- package/dist/node/scratch-vm.js.map +1 -0
- package/dist/web/extension-worker.js +2 -0
- package/dist/web/extension-worker.js.map +1 -0
- package/dist/web/scratch-vm.js +3 -0
- package/dist/web/scratch-vm.js.LICENSE.txt +42 -0
- package/dist/web/scratch-vm.js.map +1 -0
- package/package.json +108 -0
- package/src/.eslintrc.js +7 -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
|
@@ -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;
|