@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,2113 @@
|
|
|
1
|
+
const ArgumentType = require('../../extension-support/argument-type');
|
|
2
|
+
const BlockType = require('../../extension-support/block-type');
|
|
3
|
+
const Cast = require('../../util/cast');
|
|
4
|
+
const formatMessage = require('format-message');
|
|
5
|
+
const color = require('../../util/color');
|
|
6
|
+
const BLE = require('../../io/ble');
|
|
7
|
+
const Base64Util = require('../../util/base64-util');
|
|
8
|
+
const MathUtil = require('../../util/math-util');
|
|
9
|
+
const RateLimiter = require('../../util/rateLimiter.js');
|
|
10
|
+
const log = require('../../util/log');
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* The LEGO Wireless Protocol documentation used to create this extension can be found at:
|
|
14
|
+
* https://lego.github.io/lego-ble-wireless-protocol-docs/index.html
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Icon svg to be displayed at the left edge of each extension block, encoded as a data URI.
|
|
19
|
+
* @type {string}
|
|
20
|
+
*/
|
|
21
|
+
// eslint-disable-next-line max-len
|
|
22
|
+
const iconURI = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAABQCAMAAAC5zwKfAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAACpQTFRF////fIel5ufolZ62/2YavsPS+YZOkJmy9/j53+Hk6+zs6N/b6dfO////tDhMHAAAAA50Uk5T/////////////////wBFwNzIAAAA6ElEQVR42uzX2w6DIBAEUGDVtlr//3dLaLwgiwUd2z7MJPJg5EQWiGhGcAxBggQJEiT436CIfqXJPTn3MKNYYMSDFpoAmp24OaYgvwKnFgL2zvVTCwHrMoMi+nUQLFthaNCCa0iwclLkDgYVsQp0mzxuqXgK1MRzoCLWgkPXNN2wI/q6Kvt7u/cX0HtejN8x2sXpnpb8J8D3b0Keuhh3X975M+i0xNVbg3s1TIasgK21bQyGO+s2PykaGMYbge8KrNrssvkOWDXkErB8UuBHETjoYLkKBA8ZfuDkbwVBggQJEiR4MC8BBgDTtMZLx2nFCQAAAABJRU5ErkJggg==';
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Boost BLE UUIDs.
|
|
26
|
+
* @enum {string}
|
|
27
|
+
*/
|
|
28
|
+
const BoostBLE = {
|
|
29
|
+
service: '00001623-1212-efde-1623-785feabcd123',
|
|
30
|
+
characteristic: '00001624-1212-efde-1623-785feabcd123',
|
|
31
|
+
sendInterval: 100,
|
|
32
|
+
sendRateMax: 20
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Boost Motor Max Power Add. Defines how much more power than the target speed
|
|
37
|
+
* the motors may supply to reach the target speed faster.
|
|
38
|
+
* Lower number == softer, slower reached target speed.
|
|
39
|
+
* Higher number == harder, faster reached target speed.
|
|
40
|
+
* @constant {number}
|
|
41
|
+
*/
|
|
42
|
+
const BoostMotorMaxPowerAdd = 10;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* A time interval to wait (in milliseconds) in between battery check calls.
|
|
46
|
+
* @type {number}
|
|
47
|
+
*/
|
|
48
|
+
const BoostPingInterval = 5000;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* The number of continuous samples the color-sensor will evaluate color from.
|
|
52
|
+
* @type {number}
|
|
53
|
+
*/
|
|
54
|
+
const BoostColorSampleSize = 5;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Enum for Boost sensor and actuator types.
|
|
58
|
+
* @readonly
|
|
59
|
+
* @enum {number}
|
|
60
|
+
*/
|
|
61
|
+
const BoostIO = {
|
|
62
|
+
MOTOR_WEDO: 0x01,
|
|
63
|
+
MOTOR_SYSTEM: 0x02,
|
|
64
|
+
BUTTON: 0x05,
|
|
65
|
+
LIGHT: 0x08,
|
|
66
|
+
VOLTAGE: 0x14,
|
|
67
|
+
CURRENT: 0x15,
|
|
68
|
+
PIEZO: 0x16,
|
|
69
|
+
LED: 0x17,
|
|
70
|
+
TILT_EXTERNAL: 0x22,
|
|
71
|
+
MOTION_SENSOR: 0x23,
|
|
72
|
+
COLOR: 0x25,
|
|
73
|
+
MOTOREXT: 0x26,
|
|
74
|
+
MOTORINT: 0x27,
|
|
75
|
+
TILT: 0x28
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Enum for ids for various output command feedback types on the Boost.
|
|
80
|
+
* @readonly
|
|
81
|
+
* @enum {number}
|
|
82
|
+
*/
|
|
83
|
+
const BoostPortFeedback = {
|
|
84
|
+
IN_PROGRESS: 0x01,
|
|
85
|
+
COMPLETED: 0x02,
|
|
86
|
+
DISCARDED: 0x04,
|
|
87
|
+
IDLE: 0x08,
|
|
88
|
+
BUSY_OR_FULL: 0x10
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Enum for physical Boost Ports
|
|
93
|
+
* @readonly
|
|
94
|
+
* @enum {number}
|
|
95
|
+
*/
|
|
96
|
+
|
|
97
|
+
const BoostPort10000223OrOlder = {
|
|
98
|
+
A: 55,
|
|
99
|
+
B: 56,
|
|
100
|
+
C: 1,
|
|
101
|
+
D: 2
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const BoostPort10000224OrNewer = {
|
|
105
|
+
A: 0,
|
|
106
|
+
B: 1,
|
|
107
|
+
C: 2,
|
|
108
|
+
D: 3
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
// Set default port mapping to support the newer firmware
|
|
112
|
+
let BoostPort = BoostPort10000224OrNewer;
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Ids for each color sensor value used by the extension.
|
|
116
|
+
* @readonly
|
|
117
|
+
* @enum {string}
|
|
118
|
+
*/
|
|
119
|
+
const BoostColor = {
|
|
120
|
+
ANY: 'any',
|
|
121
|
+
NONE: 'none',
|
|
122
|
+
RED: 'red',
|
|
123
|
+
BLUE: 'blue',
|
|
124
|
+
GREEN: 'green',
|
|
125
|
+
YELLOW: 'yellow',
|
|
126
|
+
WHITE: 'white',
|
|
127
|
+
BLACK: 'black'
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Enum for indices for each color sensed by the Boost vision sensor.
|
|
132
|
+
* @readonly
|
|
133
|
+
* @enum {number}
|
|
134
|
+
*/
|
|
135
|
+
const BoostColorIndex = {
|
|
136
|
+
[BoostColor.NONE]: 255,
|
|
137
|
+
[BoostColor.RED]: 9,
|
|
138
|
+
[BoostColor.BLUE]: 3,
|
|
139
|
+
[BoostColor.GREEN]: 5,
|
|
140
|
+
[BoostColor.YELLOW]: 7,
|
|
141
|
+
[BoostColor.WHITE]: 10,
|
|
142
|
+
[BoostColor.BLACK]: 0
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Enum for Message Types
|
|
147
|
+
* @readonly
|
|
148
|
+
* @enum {number}
|
|
149
|
+
*/
|
|
150
|
+
const BoostMessage = {
|
|
151
|
+
HUB_PROPERTIES: 0x01,
|
|
152
|
+
HUB_ACTIONS: 0x02,
|
|
153
|
+
HUB_ALERTS: 0x03,
|
|
154
|
+
HUB_ATTACHED_IO: 0x04,
|
|
155
|
+
ERROR: 0x05,
|
|
156
|
+
PORT_INPUT_FORMAT_SETUP_SINGLE: 0x41,
|
|
157
|
+
PORT_INPUT_FORMAT_SETUP_COMBINED: 0x42,
|
|
158
|
+
PORT_INFORMATION: 0x43,
|
|
159
|
+
PORT_MODEINFORMATION: 0x44,
|
|
160
|
+
PORT_VALUE: 0x45,
|
|
161
|
+
PORT_VALUE_COMBINED: 0x46,
|
|
162
|
+
PORT_INPUT_FORMAT: 0x47,
|
|
163
|
+
PORT_INPUT_FORMAT_COMBINED: 0x48,
|
|
164
|
+
OUTPUT: 0x81,
|
|
165
|
+
PORT_FEEDBACK: 0x82
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Enum for Hub Property Types
|
|
170
|
+
* @readonly
|
|
171
|
+
* @enum {number}
|
|
172
|
+
*/
|
|
173
|
+
|
|
174
|
+
const BoostHubProperty = {
|
|
175
|
+
ADVERTISEMENT_NAME: 0x01,
|
|
176
|
+
BUTTON: 0x02,
|
|
177
|
+
FW_VERSION: 0x03,
|
|
178
|
+
HW_VERSION: 0x04,
|
|
179
|
+
RSSI: 0x05,
|
|
180
|
+
BATTERY_VOLTAGE: 0x06,
|
|
181
|
+
BATTERY_TYPE: 0x07,
|
|
182
|
+
MANUFACTURER_NAME: 0x08,
|
|
183
|
+
RADIO_FW_VERSION: 0x09,
|
|
184
|
+
LEGO_WP_VERSION: 0x0A,
|
|
185
|
+
SYSTEM_TYPE_ID: 0x0B,
|
|
186
|
+
HW_NETWORK_ID: 0x0C,
|
|
187
|
+
PRIMARY_MAC: 0x0D,
|
|
188
|
+
SECONDARY_MAC: 0x0E,
|
|
189
|
+
HW_NETWORK_FAMILY: 0x0F
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Enum for Hub Property Operations
|
|
194
|
+
* @readonly
|
|
195
|
+
* @enum {number}
|
|
196
|
+
*/
|
|
197
|
+
|
|
198
|
+
const BoostHubPropertyOperation = {
|
|
199
|
+
SET: 0x01,
|
|
200
|
+
ENABLE_UPDATES: 0x02,
|
|
201
|
+
DISABLE_UPDATES: 0x03,
|
|
202
|
+
RESET: 0x04,
|
|
203
|
+
REQUEST_UPDATE: 0x05,
|
|
204
|
+
UPDATE: 0x06
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Enum for Motor Subcommands (for 0x81)
|
|
209
|
+
* @readonly
|
|
210
|
+
* @enum {number}
|
|
211
|
+
*/
|
|
212
|
+
const BoostOutputSubCommand = {
|
|
213
|
+
START_POWER: 0x01,
|
|
214
|
+
START_POWER_PAIR: 0x02,
|
|
215
|
+
SET_ACC_TIME: 0x05,
|
|
216
|
+
SET_DEC_TIME: 0x06,
|
|
217
|
+
START_SPEED: 0x07,
|
|
218
|
+
START_SPEED_PAIR: 0x08,
|
|
219
|
+
START_SPEED_FOR_TIME: 0x09,
|
|
220
|
+
START_SPEED_FOR_TIME_PAIR: 0x0A,
|
|
221
|
+
START_SPEED_FOR_DEGREES: 0x0B,
|
|
222
|
+
START_SPEED_FOR_DEGREES_PAIR: 0x0C,
|
|
223
|
+
GO_TO_ABS_POSITION: 0x0D,
|
|
224
|
+
GO_TO_ABS_POSITION_PAIR: 0x0E,
|
|
225
|
+
PRESET_ENCODER: 0x14,
|
|
226
|
+
WRITE_DIRECT_MODE_DATA: 0x51
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Enum for Startup/Completion information for an output command.
|
|
231
|
+
* Startup and completion bytes must be OR'ed to be combined to a single byte.
|
|
232
|
+
* @readonly
|
|
233
|
+
* @enum {number}
|
|
234
|
+
*/
|
|
235
|
+
const BoostOutputExecution = {
|
|
236
|
+
// Startup information
|
|
237
|
+
BUFFER_IF_NECESSARY: 0x00,
|
|
238
|
+
EXECUTE_IMMEDIATELY: 0x10,
|
|
239
|
+
// Completion information
|
|
240
|
+
NO_ACTION: 0x00,
|
|
241
|
+
COMMAND_FEEDBACK: 0x01
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Enum for Boost Motor end states
|
|
246
|
+
* @readonly
|
|
247
|
+
* @enum {number}
|
|
248
|
+
*/
|
|
249
|
+
const BoostMotorEndState = {
|
|
250
|
+
FLOAT: 0,
|
|
251
|
+
HOLD: 126,
|
|
252
|
+
BRAKE: 127
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Enum for Boost Motor acceleration/deceleration profiles
|
|
257
|
+
* @readyonly
|
|
258
|
+
* @enum {number}
|
|
259
|
+
*/
|
|
260
|
+
const BoostMotorProfile = {
|
|
261
|
+
DO_NOT_USE: 0x00,
|
|
262
|
+
ACCELERATION: 0x01,
|
|
263
|
+
DECELERATION: 0x02
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Enum for when Boost IO's are attached/detached
|
|
268
|
+
* @readonly
|
|
269
|
+
* @enum {number}
|
|
270
|
+
*/
|
|
271
|
+
const BoostIOEvent = {
|
|
272
|
+
ATTACHED: 0x01,
|
|
273
|
+
DETACHED: 0x00,
|
|
274
|
+
ATTACHED_VIRTUAL: 0x02
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Enum for selected sensor modes.
|
|
279
|
+
* @enum {number}
|
|
280
|
+
*/
|
|
281
|
+
const BoostMode = {
|
|
282
|
+
TILT: 0, // angle (pitch/yaw)
|
|
283
|
+
LED: 1, // Set LED to accept RGB values
|
|
284
|
+
COLOR: 0, // Read indexed colors from Vision Sensor
|
|
285
|
+
MOTOR_SENSOR: 2, // Set motors to report their position
|
|
286
|
+
UNKNOWN: 0 // Anything else will use the default mode (mode 0)
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Enum for Boost motor states.
|
|
291
|
+
* @param {number}
|
|
292
|
+
*/
|
|
293
|
+
const BoostMotorState = {
|
|
294
|
+
OFF: 0,
|
|
295
|
+
ON_FOREVER: 1,
|
|
296
|
+
ON_FOR_TIME: 2,
|
|
297
|
+
ON_FOR_ROTATION: 3
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Helper function for converting a JavaScript number to an INT32-number
|
|
302
|
+
* @param {number} number - a number
|
|
303
|
+
* @return {array} - a 4-byte array of Int8-values representing an INT32-number
|
|
304
|
+
*/
|
|
305
|
+
const numberToInt32Array = function (number) {
|
|
306
|
+
const buffer = new ArrayBuffer(4);
|
|
307
|
+
const dataview = new DataView(buffer);
|
|
308
|
+
dataview.setInt32(0, number);
|
|
309
|
+
return [
|
|
310
|
+
dataview.getInt8(3),
|
|
311
|
+
dataview.getInt8(2),
|
|
312
|
+
dataview.getInt8(1),
|
|
313
|
+
dataview.getInt8(0)
|
|
314
|
+
];
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Helper function for converting a regular array to a Little Endian INT32-value
|
|
319
|
+
* @param {Array} array - an array containing UInt8-values
|
|
320
|
+
* @return {number} - a number
|
|
321
|
+
*/
|
|
322
|
+
const int32ArrayToNumber = function (array) {
|
|
323
|
+
const i = Uint8Array.from(array);
|
|
324
|
+
const d = new DataView(i.buffer);
|
|
325
|
+
return d.getInt32(0, true);
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Manage power, direction, position, and timers for one Boost motor.
|
|
330
|
+
*/
|
|
331
|
+
class BoostMotor {
|
|
332
|
+
/**
|
|
333
|
+
* Construct a Boost Motor instance.
|
|
334
|
+
* @param {Boost} parent - the Boost peripheral which owns this motor.
|
|
335
|
+
* @param {int} index - the zero-based index of this motor on its parent peripheral.
|
|
336
|
+
*/
|
|
337
|
+
constructor (parent, index) {
|
|
338
|
+
/**
|
|
339
|
+
* The Boost peripheral which owns this motor.
|
|
340
|
+
* @type {Boost}
|
|
341
|
+
* @private
|
|
342
|
+
*/
|
|
343
|
+
this._parent = parent;
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* The zero-based index of this motor on its parent peripheral.
|
|
347
|
+
* @type {int}
|
|
348
|
+
* @private
|
|
349
|
+
*/
|
|
350
|
+
this._index = index;
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* This motor's current direction: 1 for "this way" or -1 for "that way"
|
|
354
|
+
* @type {number}
|
|
355
|
+
* @private
|
|
356
|
+
*/
|
|
357
|
+
this._direction = 1;
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* This motor's current power level, in the range [0,100].
|
|
361
|
+
* @type {number}
|
|
362
|
+
* @private
|
|
363
|
+
*/
|
|
364
|
+
this._power = 50;
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* This motor's current relative position
|
|
368
|
+
* @type {number}
|
|
369
|
+
* @private
|
|
370
|
+
*/
|
|
371
|
+
this._position = 0;
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Is this motor currently moving?
|
|
375
|
+
* @type {boolean}
|
|
376
|
+
* @private
|
|
377
|
+
*/
|
|
378
|
+
this._status = BoostMotorState.OFF;
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* If the motor has been turned on or is actively braking for a specific duration, this is the timeout ID for
|
|
382
|
+
* the end-of-action handler. Cancel this when changing plans.
|
|
383
|
+
* @type {Object}
|
|
384
|
+
* @private
|
|
385
|
+
*/
|
|
386
|
+
this._pendingDurationTimeoutId = null;
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* The starting time for the pending duration timeout.
|
|
390
|
+
* @type {number}
|
|
391
|
+
* @private
|
|
392
|
+
*/
|
|
393
|
+
this._pendingDurationTimeoutStartTime = null;
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* The delay/duration of the pending duration timeout.
|
|
397
|
+
* @type {number}
|
|
398
|
+
* @private
|
|
399
|
+
*/
|
|
400
|
+
this._pendingDurationTimeoutDelay = null;
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* The target position of a turn-based command.
|
|
404
|
+
* @type {number}
|
|
405
|
+
* @private
|
|
406
|
+
*/
|
|
407
|
+
this._pendingRotationDestination = null;
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* If the motor has been turned on run for a specific rotation, this is the function
|
|
411
|
+
* that will be called once Scratch VM gets a notification from the Move Hub.
|
|
412
|
+
* @type {Object}
|
|
413
|
+
* @private
|
|
414
|
+
*/
|
|
415
|
+
this._pendingRotationPromise = null;
|
|
416
|
+
|
|
417
|
+
this.turnOff = this.turnOff.bind(this);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* @return {int} - this motor's current direction: 1 for "this way" or -1 for "that way"
|
|
422
|
+
*/
|
|
423
|
+
get direction () {
|
|
424
|
+
return this._direction;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* @param {int} value - this motor's new direction: 1 for "this way" or -1 for "that way"
|
|
429
|
+
*/
|
|
430
|
+
set direction (value) {
|
|
431
|
+
if (value < 0) {
|
|
432
|
+
this._direction = -1;
|
|
433
|
+
} else {
|
|
434
|
+
this._direction = 1;
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
/**
|
|
439
|
+
* @return {int} - this motor's current power level, in the range [0,100].
|
|
440
|
+
*/
|
|
441
|
+
get power () {
|
|
442
|
+
return this._power;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* @param {int} value - this motor's new power level, in the range [10,100].
|
|
447
|
+
*/
|
|
448
|
+
set power (value) {
|
|
449
|
+
/**
|
|
450
|
+
* Scale the motor power to a range between 10 and 100,
|
|
451
|
+
* to make sure the motors will run with something built onto them.
|
|
452
|
+
*/
|
|
453
|
+
if (value === 0) {
|
|
454
|
+
this._power = 0;
|
|
455
|
+
} else {
|
|
456
|
+
this._power = MathUtil.scale(value, 1, 100, 10, 100);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* @return {int} - this motor's current position, in the range of [-MIN_INT32,MAX_INT32]
|
|
462
|
+
*/
|
|
463
|
+
get position () {
|
|
464
|
+
return this._position;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
/**
|
|
468
|
+
* @param {int} value - set this motor's current position.
|
|
469
|
+
*/
|
|
470
|
+
set position (value) {
|
|
471
|
+
this._position = value;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/**
|
|
475
|
+
* @return {BoostMotorState} - the motor's current state.
|
|
476
|
+
*/
|
|
477
|
+
get status () {
|
|
478
|
+
return this._status;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
/**
|
|
482
|
+
* @param {BoostMotorState} value - set this motor's state.
|
|
483
|
+
*/
|
|
484
|
+
set status (value) {
|
|
485
|
+
this._clearRotationState();
|
|
486
|
+
this._clearDurationTimeout();
|
|
487
|
+
this._status = value;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
/**
|
|
491
|
+
* @return {number} - time, in milliseconds, of when the pending duration timeout began.
|
|
492
|
+
*/
|
|
493
|
+
get pendingDurationTimeoutStartTime () {
|
|
494
|
+
return this._pendingDurationTimeoutStartTime;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
/**
|
|
498
|
+
* @return {number} - delay, in milliseconds, of the pending duration timeout.
|
|
499
|
+
*/
|
|
500
|
+
get pendingDurationTimeoutDelay () {
|
|
501
|
+
return this._pendingDurationTimeoutDelay;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
* @return {number} - target position, in degrees, of the pending rotation.
|
|
506
|
+
*/
|
|
507
|
+
get pendingRotationDestination () {
|
|
508
|
+
return this._pendingRotationDestination;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
/**
|
|
512
|
+
* @return {Promise} - the Promise function for the pending rotation.
|
|
513
|
+
*/
|
|
514
|
+
get pendingRotationPromise () {
|
|
515
|
+
return this._pendingRotationPromise;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
/**
|
|
519
|
+
* @param {function} func - function to resolve pending rotation Promise
|
|
520
|
+
*/
|
|
521
|
+
set pendingRotationPromise (func) {
|
|
522
|
+
this._pendingRotationPromise = func;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
/**
|
|
526
|
+
* Turn this motor on indefinitely
|
|
527
|
+
* @private
|
|
528
|
+
*/
|
|
529
|
+
_turnOn () {
|
|
530
|
+
const cmd = this._parent.generateOutputCommand(
|
|
531
|
+
this._index,
|
|
532
|
+
BoostOutputExecution.EXECUTE_IMMEDIATELY,
|
|
533
|
+
BoostOutputSubCommand.START_SPEED,
|
|
534
|
+
[
|
|
535
|
+
this.power * this.direction,
|
|
536
|
+
MathUtil.clamp(this.power + BoostMotorMaxPowerAdd, 0, 100),
|
|
537
|
+
BoostMotorProfile.DO_NOT_USE
|
|
538
|
+
]);
|
|
539
|
+
|
|
540
|
+
this._parent.send(BoostBLE.characteristic, cmd);
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
/**
|
|
544
|
+
* Turn this motor on indefinitely
|
|
545
|
+
*/
|
|
546
|
+
turnOnForever () {
|
|
547
|
+
this.status = BoostMotorState.ON_FOREVER;
|
|
548
|
+
this._turnOn();
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
/**
|
|
552
|
+
* Turn this motor on for a specific duration.
|
|
553
|
+
* @param {number} milliseconds - run the motor for this long.
|
|
554
|
+
*/
|
|
555
|
+
turnOnFor (milliseconds) {
|
|
556
|
+
milliseconds = Math.max(0, milliseconds);
|
|
557
|
+
this.status = BoostMotorState.ON_FOR_TIME;
|
|
558
|
+
this._turnOn();
|
|
559
|
+
this._setNewDurationTimeout(this.turnOff, milliseconds);
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
/**
|
|
563
|
+
* Turn this motor on for a specific rotation in degrees.
|
|
564
|
+
* @param {number} degrees - run the motor for this amount of degrees.
|
|
565
|
+
* @param {number} direction - rotate in this direction
|
|
566
|
+
*/
|
|
567
|
+
turnOnForDegrees (degrees, direction) {
|
|
568
|
+
degrees = Math.max(0, degrees);
|
|
569
|
+
|
|
570
|
+
const cmd = this._parent.generateOutputCommand(
|
|
571
|
+
this._index,
|
|
572
|
+
(BoostOutputExecution.EXECUTE_IMMEDIATELY ^ BoostOutputExecution.COMMAND_FEEDBACK),
|
|
573
|
+
BoostOutputSubCommand.START_SPEED_FOR_DEGREES,
|
|
574
|
+
[
|
|
575
|
+
...numberToInt32Array(degrees),
|
|
576
|
+
this.power * this.direction * direction,
|
|
577
|
+
MathUtil.clamp(this.power + BoostMotorMaxPowerAdd, 0, 100),
|
|
578
|
+
BoostMotorEndState.BRAKE,
|
|
579
|
+
BoostMotorProfile.DO_NOT_USE
|
|
580
|
+
]
|
|
581
|
+
);
|
|
582
|
+
|
|
583
|
+
this.status = BoostMotorState.ON_FOR_ROTATION;
|
|
584
|
+
this._pendingRotationDestination = this.position + (degrees * this.direction * direction);
|
|
585
|
+
this._parent.send(BoostBLE.characteristic, cmd);
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
/**
|
|
589
|
+
* Turn this motor off.
|
|
590
|
+
* @param {boolean} [useLimiter=true] - if true, use the rate limiter
|
|
591
|
+
*/
|
|
592
|
+
turnOff (useLimiter = true) {
|
|
593
|
+
const cmd = this._parent.generateOutputCommand(
|
|
594
|
+
this._index,
|
|
595
|
+
BoostOutputExecution.EXECUTE_IMMEDIATELY,
|
|
596
|
+
BoostOutputSubCommand.START_POWER,
|
|
597
|
+
[
|
|
598
|
+
BoostMotorEndState.FLOAT
|
|
599
|
+
]
|
|
600
|
+
);
|
|
601
|
+
|
|
602
|
+
this.status = BoostMotorState.OFF;
|
|
603
|
+
this._parent.send(BoostBLE.characteristic, cmd, useLimiter);
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
/**
|
|
607
|
+
* Clear the motor action timeout, if any. Safe to call even when there is no pending timeout.
|
|
608
|
+
* @private
|
|
609
|
+
*/
|
|
610
|
+
_clearDurationTimeout () {
|
|
611
|
+
if (this._pendingDurationTimeoutId !== null) {
|
|
612
|
+
clearTimeout(this._pendingDurationTimeoutId);
|
|
613
|
+
this._pendingDurationTimeoutId = null;
|
|
614
|
+
this._pendingDurationTimeoutStartTime = null;
|
|
615
|
+
this._pendingDurationTimeoutDelay = null;
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
/**
|
|
620
|
+
* Set a new motor action timeout, after clearing an existing one if necessary.
|
|
621
|
+
* @param {Function} callback - to be called at the end of the timeout.
|
|
622
|
+
* @param {int} delay - wait this many milliseconds before calling the callback.
|
|
623
|
+
* @private
|
|
624
|
+
*/
|
|
625
|
+
_setNewDurationTimeout (callback, delay) {
|
|
626
|
+
this._clearDurationTimeout();
|
|
627
|
+
const timeoutID = setTimeout(() => {
|
|
628
|
+
if (this._pendingDurationTimeoutId === timeoutID) {
|
|
629
|
+
this._pendingDurationTimeoutId = null;
|
|
630
|
+
this._pendingDurationTimeoutStartTime = null;
|
|
631
|
+
this._pendingDurationTimeoutDelay = null;
|
|
632
|
+
}
|
|
633
|
+
callback();
|
|
634
|
+
}, delay);
|
|
635
|
+
this._pendingDurationTimeoutId = timeoutID;
|
|
636
|
+
this._pendingDurationTimeoutStartTime = Date.now();
|
|
637
|
+
this._pendingDurationTimeoutDelay = delay;
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
/**
|
|
641
|
+
* Clear the motor states related to rotation-based commands, if any.
|
|
642
|
+
* Safe to call even when there is no pending promise function.
|
|
643
|
+
* @private
|
|
644
|
+
*/
|
|
645
|
+
_clearRotationState () {
|
|
646
|
+
if (this._pendingRotationPromise !== null) {
|
|
647
|
+
this._pendingRotationPromise();
|
|
648
|
+
this._pendingRotationPromise = null;
|
|
649
|
+
}
|
|
650
|
+
this._pendingRotationDestination = null;
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
/**
|
|
655
|
+
* Manage communication with a Boost peripheral over a Bluetooth Low Energy client socket.
|
|
656
|
+
*/
|
|
657
|
+
class Boost {
|
|
658
|
+
|
|
659
|
+
constructor (runtime, extensionId) {
|
|
660
|
+
|
|
661
|
+
/**
|
|
662
|
+
* The Scratch 3.0 runtime used to trigger the green flag button.
|
|
663
|
+
* @type {Runtime}
|
|
664
|
+
* @private
|
|
665
|
+
*/
|
|
666
|
+
this._runtime = runtime;
|
|
667
|
+
this._runtime.on('PROJECT_STOP_ALL', this.stopAll.bind(this));
|
|
668
|
+
|
|
669
|
+
/**
|
|
670
|
+
* The id of the extension this peripheral belongs to.
|
|
671
|
+
*/
|
|
672
|
+
this._extensionId = extensionId;
|
|
673
|
+
|
|
674
|
+
/**
|
|
675
|
+
* A list of the ids of the physical or virtual sensors.
|
|
676
|
+
* @type {string[]}
|
|
677
|
+
* @private
|
|
678
|
+
*/
|
|
679
|
+
this._ports = [];
|
|
680
|
+
|
|
681
|
+
/**
|
|
682
|
+
* A list of motors registered by the Boost hardware.
|
|
683
|
+
* @type {BoostMotor[]}
|
|
684
|
+
* @private
|
|
685
|
+
*/
|
|
686
|
+
this._motors = [];
|
|
687
|
+
|
|
688
|
+
/**
|
|
689
|
+
* The most recently received value for each sensor.
|
|
690
|
+
* @type {Object.<string, number>}
|
|
691
|
+
* @private
|
|
692
|
+
*/
|
|
693
|
+
this._sensors = {
|
|
694
|
+
tiltX: 0,
|
|
695
|
+
tiltY: 0,
|
|
696
|
+
color: BoostColor.NONE,
|
|
697
|
+
previousColor: BoostColor.NONE
|
|
698
|
+
};
|
|
699
|
+
|
|
700
|
+
/**
|
|
701
|
+
* An array of values from the Boost Vision Sensor.
|
|
702
|
+
* @type {Array}
|
|
703
|
+
* @private
|
|
704
|
+
*/
|
|
705
|
+
this._colorSamples = [];
|
|
706
|
+
|
|
707
|
+
/**
|
|
708
|
+
* The Bluetooth connection socket for reading/writing peripheral data.
|
|
709
|
+
* @type {BLE}
|
|
710
|
+
* @private
|
|
711
|
+
*/
|
|
712
|
+
this._ble = null;
|
|
713
|
+
this._runtime.registerPeripheralExtension(extensionId, this);
|
|
714
|
+
|
|
715
|
+
/**
|
|
716
|
+
* A rate limiter utility, to help limit the rate at which we send BLE messages
|
|
717
|
+
* over the socket to Scratch Link to a maximum number of sends per second.
|
|
718
|
+
* @type {RateLimiter}
|
|
719
|
+
* @private
|
|
720
|
+
*/
|
|
721
|
+
this._rateLimiter = new RateLimiter(BoostBLE.sendRateMax);
|
|
722
|
+
|
|
723
|
+
/**
|
|
724
|
+
* An interval id for the battery check interval.
|
|
725
|
+
* @type {number}
|
|
726
|
+
* @private
|
|
727
|
+
*/
|
|
728
|
+
this._pingDeviceId = null;
|
|
729
|
+
|
|
730
|
+
this.reset = this.reset.bind(this);
|
|
731
|
+
this._onConnect = this._onConnect.bind(this);
|
|
732
|
+
this._onMessage = this._onMessage.bind(this);
|
|
733
|
+
this._pingDevice = this._pingDevice.bind(this);
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
/**
|
|
737
|
+
* @return {number} - the latest value received for the tilt sensor's tilt about the X axis.
|
|
738
|
+
*/
|
|
739
|
+
get tiltX () {
|
|
740
|
+
return this._sensors.tiltX;
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
/**
|
|
744
|
+
* @return {number} - the latest value received for the tilt sensor's tilt about the Y axis.
|
|
745
|
+
*/
|
|
746
|
+
get tiltY () {
|
|
747
|
+
return this._sensors.tiltY;
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
/**
|
|
751
|
+
* @return {number} - the latest color value received from the vision sensor.
|
|
752
|
+
*/
|
|
753
|
+
get color () {
|
|
754
|
+
return this._sensors.color;
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
/**
|
|
758
|
+
* @return {number} - the previous color value received from the vision sensor.
|
|
759
|
+
*/
|
|
760
|
+
get previousColor () {
|
|
761
|
+
return this._sensors.previousColor;
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
/**
|
|
765
|
+
* Look up the color id for an index received from the vision sensor.
|
|
766
|
+
* @param {number} index - the color index to look up.
|
|
767
|
+
* @return {BoostColor} the color id for this index.
|
|
768
|
+
*/
|
|
769
|
+
boostColorForIndex (index) {
|
|
770
|
+
const colorForIndex = Object.keys(BoostColorIndex).find(key => BoostColorIndex[key] === index);
|
|
771
|
+
return colorForIndex || BoostColor.NONE;
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
/**
|
|
775
|
+
* Access a particular motor on this peripheral.
|
|
776
|
+
* @param {int} index - the index of the desired motor.
|
|
777
|
+
* @return {BoostMotor} - the BoostMotor instance, if any, at that index.
|
|
778
|
+
*/
|
|
779
|
+
motor (index) {
|
|
780
|
+
return this._motors[index];
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
/**
|
|
784
|
+
* Stop all the motors that are currently running.
|
|
785
|
+
*/
|
|
786
|
+
stopAllMotors () {
|
|
787
|
+
this._motors.forEach(motor => {
|
|
788
|
+
if (motor) {
|
|
789
|
+
// Send the motor off command without using the rate limiter.
|
|
790
|
+
// This allows the stop button to stop motors even if we are
|
|
791
|
+
// otherwise flooded with commands.
|
|
792
|
+
motor.turnOff(false);
|
|
793
|
+
}
|
|
794
|
+
});
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
/**
|
|
798
|
+
* Set the Boost peripheral's LED to a specific color.
|
|
799
|
+
* @param {int} inputRGB - a 24-bit RGB color in 0xRRGGBB format.
|
|
800
|
+
* @return {Promise} - a promise of the completion of the set led send operation.
|
|
801
|
+
*/
|
|
802
|
+
setLED (inputRGB) {
|
|
803
|
+
const rgb = [
|
|
804
|
+
(inputRGB >> 16) & 0x000000FF,
|
|
805
|
+
(inputRGB >> 8) & 0x000000FF,
|
|
806
|
+
(inputRGB) & 0x000000FF
|
|
807
|
+
];
|
|
808
|
+
|
|
809
|
+
const cmd = this.generateOutputCommand(
|
|
810
|
+
this._ports.indexOf(BoostIO.LED),
|
|
811
|
+
BoostOutputExecution.EXECUTE_IMMEDIATELY ^ BoostOutputExecution.COMMAND_FEEDBACK,
|
|
812
|
+
BoostOutputSubCommand.WRITE_DIRECT_MODE_DATA,
|
|
813
|
+
[BoostMode.LED,
|
|
814
|
+
...rgb]
|
|
815
|
+
);
|
|
816
|
+
|
|
817
|
+
return this.send(BoostBLE.characteristic, cmd);
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
/**
|
|
821
|
+
* Sets the input mode of the LED to RGB.
|
|
822
|
+
* @return {Promise} - a promise returned by the send operation.
|
|
823
|
+
*/
|
|
824
|
+
setLEDMode () {
|
|
825
|
+
const cmd = this.generateInputCommand(
|
|
826
|
+
this._ports.indexOf(BoostIO.LED),
|
|
827
|
+
BoostMode.LED,
|
|
828
|
+
0,
|
|
829
|
+
false
|
|
830
|
+
);
|
|
831
|
+
|
|
832
|
+
return this.send(BoostBLE.characteristic, cmd);
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
/**
|
|
836
|
+
* Stop the motors on the Boost peripheral.
|
|
837
|
+
*/
|
|
838
|
+
stopAll () {
|
|
839
|
+
if (!this.isConnected()) return;
|
|
840
|
+
this.stopAllMotors();
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
/**
|
|
844
|
+
* Called by the runtime when user wants to scan for a Boost peripheral.
|
|
845
|
+
*/
|
|
846
|
+
scan () {
|
|
847
|
+
if (this._ble) {
|
|
848
|
+
this._ble.disconnect();
|
|
849
|
+
}
|
|
850
|
+
this._ble = new BLE(this._runtime, this._extensionId, {
|
|
851
|
+
filters: [{
|
|
852
|
+
services: [BoostBLE.service],
|
|
853
|
+
manufacturerData: {
|
|
854
|
+
0x0397: {
|
|
855
|
+
dataPrefix: [0x00, 0x40],
|
|
856
|
+
mask: [0x00, 0xFF]
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
}],
|
|
860
|
+
optionalServices: []
|
|
861
|
+
}, this._onConnect, this.reset);
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
/**
|
|
865
|
+
* Called by the runtime when user wants to connect to a certain Boost peripheral.
|
|
866
|
+
* @param {number} id - the id of the peripheral to connect to.
|
|
867
|
+
*/
|
|
868
|
+
connect (id) {
|
|
869
|
+
if (this._ble) {
|
|
870
|
+
this._ble.connectPeripheral(id);
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
/**
|
|
875
|
+
* Disconnects from the current BLE socket and resets state.
|
|
876
|
+
*/
|
|
877
|
+
disconnect () {
|
|
878
|
+
if (this._ble) {
|
|
879
|
+
this._ble.disconnect();
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
this.reset();
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
/**
|
|
886
|
+
* Reset all the state and timeout/interval ids.
|
|
887
|
+
*/
|
|
888
|
+
reset () {
|
|
889
|
+
this._ports = [];
|
|
890
|
+
this._motors = [];
|
|
891
|
+
this._sensors = {
|
|
892
|
+
tiltX: 0,
|
|
893
|
+
tiltY: 0,
|
|
894
|
+
color: BoostColor.NONE,
|
|
895
|
+
previousColor: BoostColor.NONE
|
|
896
|
+
};
|
|
897
|
+
|
|
898
|
+
if (this._pingDeviceId) {
|
|
899
|
+
window.clearInterval(this._pingDeviceId);
|
|
900
|
+
this._pingDeviceId = null;
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
/**
|
|
905
|
+
* Called by the runtime to detect whether the Boost peripheral is connected.
|
|
906
|
+
* @return {boolean} - the connected state.
|
|
907
|
+
*/
|
|
908
|
+
isConnected () {
|
|
909
|
+
let connected = false;
|
|
910
|
+
if (this._ble) {
|
|
911
|
+
connected = this._ble.isConnected();
|
|
912
|
+
}
|
|
913
|
+
return connected;
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
/**
|
|
917
|
+
* Write a message to the Boost peripheral BLE socket.
|
|
918
|
+
* @param {number} uuid - the UUID of the characteristic to write to
|
|
919
|
+
* @param {Array} message - the message to write.
|
|
920
|
+
* @param {boolean} [useLimiter=true] - if true, use the rate limiter
|
|
921
|
+
* @return {Promise} - a promise result of the write operation
|
|
922
|
+
*/
|
|
923
|
+
send (uuid, message, useLimiter = true) {
|
|
924
|
+
if (!this.isConnected()) return Promise.resolve();
|
|
925
|
+
|
|
926
|
+
if (useLimiter) {
|
|
927
|
+
if (!this._rateLimiter.okayToSend()) return Promise.resolve();
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
return this._ble.write(
|
|
931
|
+
BoostBLE.service,
|
|
932
|
+
uuid,
|
|
933
|
+
Base64Util.uint8ArrayToBase64(message),
|
|
934
|
+
'base64'
|
|
935
|
+
);
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
/**
|
|
939
|
+
* Generate a Boost 'Output Command' in the byte array format
|
|
940
|
+
* (COMMON HEADER, PORT ID, EXECUTION BYTE, SUBCOMMAND ID, PAYLOAD).
|
|
941
|
+
*
|
|
942
|
+
* Payload is accepted as an array since these vary across different subcommands.
|
|
943
|
+
*
|
|
944
|
+
* @param {number} portID - the port (Connect ID) to send a command to.
|
|
945
|
+
* @param {number} execution - Byte containing startup/completion information
|
|
946
|
+
* @param {number} subCommand - the id of the subcommand byte.
|
|
947
|
+
* @param {array} payload - the list of bytes to send as subcommand payload
|
|
948
|
+
* @return {array} - a generated output command.
|
|
949
|
+
*/
|
|
950
|
+
generateOutputCommand (portID, execution, subCommand, payload) {
|
|
951
|
+
const hubID = 0x00;
|
|
952
|
+
const command = [hubID, BoostMessage.OUTPUT, portID, execution, subCommand, ...payload];
|
|
953
|
+
command.unshift(command.length + 1); // Prepend payload with length byte;
|
|
954
|
+
|
|
955
|
+
return command;
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
/**
|
|
959
|
+
* Generate a Boost 'Input Command' in the byte array format
|
|
960
|
+
* (COMMAND ID, COMMAND TYPE, CONNECT ID, TYPE ID, MODE, DELTA INTERVAL (4 BYTES),
|
|
961
|
+
* UNIT, NOTIFICATIONS ENABLED).
|
|
962
|
+
*
|
|
963
|
+
* This sends a command to the Boost that sets that input format
|
|
964
|
+
* of the specified inputs and sets value change notifications.
|
|
965
|
+
*
|
|
966
|
+
* @param {number} portID - the port (Connect ID) to send a command to.
|
|
967
|
+
* @param {number} mode - the mode of the input sensor.
|
|
968
|
+
* @param {number} delta - the delta change needed to trigger notification.
|
|
969
|
+
* @param {boolean} enableNotifications - whether to enable notifications.
|
|
970
|
+
* @return {array} - a generated input command.
|
|
971
|
+
*/
|
|
972
|
+
generateInputCommand (portID, mode, delta, enableNotifications) {
|
|
973
|
+
const command = [
|
|
974
|
+
0x00, // Hub ID
|
|
975
|
+
BoostMessage.PORT_INPUT_FORMAT_SETUP_SINGLE,
|
|
976
|
+
portID,
|
|
977
|
+
mode
|
|
978
|
+
].concat(numberToInt32Array(delta)).concat([
|
|
979
|
+
enableNotifications
|
|
980
|
+
]);
|
|
981
|
+
command.unshift(command.length + 1); // Prepend payload with length byte;
|
|
982
|
+
|
|
983
|
+
return command;
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
/**
|
|
987
|
+
* Starts reading data from peripheral after BLE has connected.
|
|
988
|
+
* @private
|
|
989
|
+
*/
|
|
990
|
+
_onConnect () {
|
|
991
|
+
this._ble.startNotifications(
|
|
992
|
+
BoostBLE.service,
|
|
993
|
+
BoostBLE.characteristic,
|
|
994
|
+
this._onMessage
|
|
995
|
+
);
|
|
996
|
+
this._pingDeviceId = window.setInterval(this._pingDevice, BoostPingInterval);
|
|
997
|
+
|
|
998
|
+
// Send a request for firmware version.
|
|
999
|
+
setTimeout(() => {
|
|
1000
|
+
const command = [
|
|
1001
|
+
0x00, // Hub ID
|
|
1002
|
+
BoostMessage.HUB_PROPERTIES,
|
|
1003
|
+
BoostHubProperty.FW_VERSION,
|
|
1004
|
+
BoostHubPropertyOperation.REQUEST_UPDATE
|
|
1005
|
+
];
|
|
1006
|
+
command.unshift(command.length + 1);
|
|
1007
|
+
this.send(BoostBLE.characteristic, command, false);
|
|
1008
|
+
}, 500);
|
|
1009
|
+
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
/**
|
|
1013
|
+
* Process the sensor data from the incoming BLE characteristic.
|
|
1014
|
+
* @param {object} base64 - the incoming BLE data.
|
|
1015
|
+
* @private
|
|
1016
|
+
*/
|
|
1017
|
+
_onMessage (base64) {
|
|
1018
|
+
const data = Base64Util.base64ToUint8Array(base64);
|
|
1019
|
+
|
|
1020
|
+
/**
|
|
1021
|
+
* First three bytes are the common header:
|
|
1022
|
+
* 0: Length of message
|
|
1023
|
+
* 1: Hub ID (always 0x00 at the moment, unused)
|
|
1024
|
+
* 2: Message Type
|
|
1025
|
+
* 3: Port ID
|
|
1026
|
+
* We base our switch-case on Message Type
|
|
1027
|
+
*/
|
|
1028
|
+
|
|
1029
|
+
const messageType = data[2];
|
|
1030
|
+
const portID = data[3];
|
|
1031
|
+
|
|
1032
|
+
switch (messageType) {
|
|
1033
|
+
|
|
1034
|
+
case BoostMessage.HUB_PROPERTIES: {
|
|
1035
|
+
const property = data[3];
|
|
1036
|
+
switch (property) {
|
|
1037
|
+
case BoostHubProperty.FW_VERSION: {
|
|
1038
|
+
// Establish firmware version 1.0.00.0224 as a 32-bit signed integer (little endian)
|
|
1039
|
+
const fwVersion10000224 = int32ArrayToNumber([0x24, 0x02, 0x00, 0x10]);
|
|
1040
|
+
const fwHub = int32ArrayToNumber(data.slice(5, data.length));
|
|
1041
|
+
if (fwHub < fwVersion10000224) {
|
|
1042
|
+
BoostPort = BoostPort10000223OrOlder;
|
|
1043
|
+
log.info('Move Hub firmware older than version 1.0.00.0224 detected. Using old port mapping.');
|
|
1044
|
+
} else {
|
|
1045
|
+
BoostPort = BoostPort10000224OrNewer;
|
|
1046
|
+
}
|
|
1047
|
+
break;
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
break;
|
|
1051
|
+
}
|
|
1052
|
+
case BoostMessage.HUB_ATTACHED_IO: { // IO Attach/Detach events
|
|
1053
|
+
const event = data[4];
|
|
1054
|
+
const typeId = data[5];
|
|
1055
|
+
|
|
1056
|
+
switch (event) {
|
|
1057
|
+
case BoostIOEvent.ATTACHED:
|
|
1058
|
+
this._registerSensorOrMotor(portID, typeId);
|
|
1059
|
+
break;
|
|
1060
|
+
case BoostIOEvent.DETACHED:
|
|
1061
|
+
this._clearPort(portID);
|
|
1062
|
+
break;
|
|
1063
|
+
case BoostIOEvent.ATTACHED_VIRTUAL:
|
|
1064
|
+
default:
|
|
1065
|
+
}
|
|
1066
|
+
break;
|
|
1067
|
+
}
|
|
1068
|
+
case BoostMessage.PORT_VALUE: {
|
|
1069
|
+
const type = this._ports[portID];
|
|
1070
|
+
|
|
1071
|
+
switch (type) {
|
|
1072
|
+
case BoostIO.TILT:
|
|
1073
|
+
this._sensors.tiltX = data[4];
|
|
1074
|
+
this._sensors.tiltY = data[5];
|
|
1075
|
+
break;
|
|
1076
|
+
case BoostIO.COLOR:
|
|
1077
|
+
this._colorSamples.unshift(data[4]);
|
|
1078
|
+
if (this._colorSamples.length > BoostColorSampleSize) {
|
|
1079
|
+
this._colorSamples.pop();
|
|
1080
|
+
if (this._colorSamples.every((v, i, arr) => v === arr[0])) {
|
|
1081
|
+
this._sensors.previousColor = this._sensors.color;
|
|
1082
|
+
this._sensors.color = this.boostColorForIndex(this._colorSamples[0]);
|
|
1083
|
+
} else {
|
|
1084
|
+
this._sensors.color = BoostColor.NONE;
|
|
1085
|
+
}
|
|
1086
|
+
} else {
|
|
1087
|
+
this._sensors.color = BoostColor.NONE;
|
|
1088
|
+
}
|
|
1089
|
+
break;
|
|
1090
|
+
case BoostIO.MOTOREXT:
|
|
1091
|
+
case BoostIO.MOTORINT:
|
|
1092
|
+
this.motor(portID).position = int32ArrayToNumber(data.slice(4, 8));
|
|
1093
|
+
break;
|
|
1094
|
+
case BoostIO.CURRENT:
|
|
1095
|
+
case BoostIO.VOLTAGE:
|
|
1096
|
+
case BoostIO.LED:
|
|
1097
|
+
break;
|
|
1098
|
+
default:
|
|
1099
|
+
log.warn(`Unknown sensor value! Type: ${type}`);
|
|
1100
|
+
}
|
|
1101
|
+
break;
|
|
1102
|
+
}
|
|
1103
|
+
case BoostMessage.PORT_FEEDBACK: {
|
|
1104
|
+
const feedback = data[4];
|
|
1105
|
+
const motor = this.motor(portID);
|
|
1106
|
+
if (motor) {
|
|
1107
|
+
// Makes sure that commands resolve both when they actually complete and when they fail
|
|
1108
|
+
const isBusy = feedback & BoostPortFeedback.IN_PROGRESS;
|
|
1109
|
+
const commandCompleted = feedback & (BoostPortFeedback.COMPLETED ^ BoostPortFeedback.DISCARDED);
|
|
1110
|
+
if (!isBusy && commandCompleted) {
|
|
1111
|
+
if (motor.status === BoostMotorState.ON_FOR_ROTATION) {
|
|
1112
|
+
motor.status = BoostMotorState.OFF;
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
break;
|
|
1117
|
+
}
|
|
1118
|
+
case BoostMessage.ERROR:
|
|
1119
|
+
log.warn(`Error reported by hub: ${data}`);
|
|
1120
|
+
break;
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
/**
|
|
1125
|
+
* Ping the Boost hub. If the Boost hub has disconnected
|
|
1126
|
+
* for some reason, the BLE socket will get an error back and automatically
|
|
1127
|
+
* close the socket.
|
|
1128
|
+
* @private
|
|
1129
|
+
*/
|
|
1130
|
+
_pingDevice () {
|
|
1131
|
+
this._ble.read(
|
|
1132
|
+
BoostBLE.service,
|
|
1133
|
+
BoostBLE.characteristic,
|
|
1134
|
+
false
|
|
1135
|
+
);
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
/**
|
|
1139
|
+
* Register a new sensor or motor connected at a port. Store the type of
|
|
1140
|
+
* sensor or motor internally, and then register for notifications on input
|
|
1141
|
+
* values if it is a sensor.
|
|
1142
|
+
* @param {number} portID - the port to register a sensor or motor on.
|
|
1143
|
+
* @param {number} type - the type ID of the sensor or motor
|
|
1144
|
+
* @private
|
|
1145
|
+
*/
|
|
1146
|
+
_registerSensorOrMotor (portID, type) {
|
|
1147
|
+
// Record which port is connected to what type of device
|
|
1148
|
+
this._ports[portID] = type;
|
|
1149
|
+
|
|
1150
|
+
// Record motor port
|
|
1151
|
+
if (type === BoostIO.MOTORINT || type === BoostIO.MOTOREXT) {
|
|
1152
|
+
this._motors[portID] = new BoostMotor(this, portID);
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
// Set input format for tilt or distance sensor
|
|
1156
|
+
let mode = null;
|
|
1157
|
+
let delta = 1;
|
|
1158
|
+
|
|
1159
|
+
switch (type) {
|
|
1160
|
+
case BoostIO.MOTORINT:
|
|
1161
|
+
case BoostIO.MOTOREXT:
|
|
1162
|
+
mode = BoostMode.MOTOR_SENSOR;
|
|
1163
|
+
break;
|
|
1164
|
+
case BoostIO.COLOR:
|
|
1165
|
+
mode = BoostMode.COLOR;
|
|
1166
|
+
delta = 0;
|
|
1167
|
+
break;
|
|
1168
|
+
case BoostIO.LED:
|
|
1169
|
+
mode = BoostMode.LED;
|
|
1170
|
+
/**
|
|
1171
|
+
* Sets the LED to blue to give an indication on the hub
|
|
1172
|
+
* that it has connected successfully.
|
|
1173
|
+
*/
|
|
1174
|
+
this.setLEDMode();
|
|
1175
|
+
this.setLED(0x0000FF);
|
|
1176
|
+
break;
|
|
1177
|
+
case BoostIO.TILT:
|
|
1178
|
+
mode = BoostMode.TILT;
|
|
1179
|
+
break;
|
|
1180
|
+
default:
|
|
1181
|
+
mode = BoostMode.UNKNOWN;
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
const cmd = this.generateInputCommand(
|
|
1185
|
+
portID,
|
|
1186
|
+
mode,
|
|
1187
|
+
delta,
|
|
1188
|
+
true // Receive feedback
|
|
1189
|
+
);
|
|
1190
|
+
|
|
1191
|
+
this.send(BoostBLE.characteristic, cmd);
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
/**
|
|
1195
|
+
* Clear the sensors or motors present on the ports.
|
|
1196
|
+
* @param {number} portID - the port to clear.
|
|
1197
|
+
* @private
|
|
1198
|
+
*/
|
|
1199
|
+
_clearPort (portID) {
|
|
1200
|
+
const type = this._ports[portID];
|
|
1201
|
+
if (type === BoostIO.TILT) {
|
|
1202
|
+
this._sensors.tiltX = this._sensors.tiltY = 0;
|
|
1203
|
+
}
|
|
1204
|
+
if (type === BoostIO.COLOR) {
|
|
1205
|
+
this._sensors.color = BoostColor.NONE;
|
|
1206
|
+
}
|
|
1207
|
+
this._ports[portID] = 'none';
|
|
1208
|
+
this._motors[portID] = null;
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
/**
|
|
1213
|
+
* Enum for motor specification.
|
|
1214
|
+
* @readonly
|
|
1215
|
+
* @enum {string}
|
|
1216
|
+
*/
|
|
1217
|
+
const BoostMotorLabel = {
|
|
1218
|
+
A: 'A',
|
|
1219
|
+
B: 'B',
|
|
1220
|
+
C: 'C',
|
|
1221
|
+
D: 'D',
|
|
1222
|
+
AB: 'AB',
|
|
1223
|
+
ALL: 'ABCD'
|
|
1224
|
+
};
|
|
1225
|
+
|
|
1226
|
+
/**
|
|
1227
|
+
* Enum for motor direction specification.
|
|
1228
|
+
* @readonly
|
|
1229
|
+
* @enum {string}
|
|
1230
|
+
*/
|
|
1231
|
+
const BoostMotorDirection = {
|
|
1232
|
+
FORWARD: 'this way',
|
|
1233
|
+
BACKWARD: 'that way',
|
|
1234
|
+
REVERSE: 'reverse'
|
|
1235
|
+
};
|
|
1236
|
+
|
|
1237
|
+
/**
|
|
1238
|
+
* Enum for tilt sensor direction.
|
|
1239
|
+
* @readonly
|
|
1240
|
+
* @enum {string}
|
|
1241
|
+
*/
|
|
1242
|
+
const BoostTiltDirection = {
|
|
1243
|
+
UP: 'up',
|
|
1244
|
+
DOWN: 'down',
|
|
1245
|
+
LEFT: 'left',
|
|
1246
|
+
RIGHT: 'right',
|
|
1247
|
+
ANY: 'any'
|
|
1248
|
+
};
|
|
1249
|
+
|
|
1250
|
+
/**
|
|
1251
|
+
* Scratch 3.0 blocks to interact with a LEGO Boost peripheral.
|
|
1252
|
+
*/
|
|
1253
|
+
class Scratch3BoostBlocks {
|
|
1254
|
+
|
|
1255
|
+
/**
|
|
1256
|
+
* @return {string} - the ID of this extension.
|
|
1257
|
+
*/
|
|
1258
|
+
static get EXTENSION_ID () {
|
|
1259
|
+
return 'boost';
|
|
1260
|
+
}
|
|
1261
|
+
|
|
1262
|
+
/**
|
|
1263
|
+
* @return {number} - the tilt sensor counts as "tilted" if its tilt angle meets or exceeds this threshold.
|
|
1264
|
+
*/
|
|
1265
|
+
static get TILT_THRESHOLD () {
|
|
1266
|
+
return 15;
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
/**
|
|
1270
|
+
* Construct a set of Boost blocks.
|
|
1271
|
+
* @param {Runtime} runtime - the Scratch 3.0 runtime.
|
|
1272
|
+
*/
|
|
1273
|
+
constructor (runtime) {
|
|
1274
|
+
/**
|
|
1275
|
+
* The Scratch 3.0 runtime.
|
|
1276
|
+
* @type {Runtime}
|
|
1277
|
+
*/
|
|
1278
|
+
this.runtime = runtime;
|
|
1279
|
+
|
|
1280
|
+
// Create a new Boost peripheral instance
|
|
1281
|
+
this._peripheral = new Boost(this.runtime, Scratch3BoostBlocks.EXTENSION_ID);
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1284
|
+
/**
|
|
1285
|
+
* @returns {object} metadata for this extension and its blocks.
|
|
1286
|
+
*/
|
|
1287
|
+
getInfo () {
|
|
1288
|
+
return {
|
|
1289
|
+
id: Scratch3BoostBlocks.EXTENSION_ID,
|
|
1290
|
+
name: 'BOOST',
|
|
1291
|
+
blockIconURI: iconURI,
|
|
1292
|
+
showStatusButton: true,
|
|
1293
|
+
blocks: [
|
|
1294
|
+
{
|
|
1295
|
+
opcode: 'motorOnFor',
|
|
1296
|
+
text: formatMessage({
|
|
1297
|
+
id: 'boost.motorOnFor',
|
|
1298
|
+
default: 'turn motor [MOTOR_ID] for [DURATION] seconds',
|
|
1299
|
+
description: 'turn a motor on for some time'
|
|
1300
|
+
}),
|
|
1301
|
+
blockType: BlockType.COMMAND,
|
|
1302
|
+
arguments: {
|
|
1303
|
+
MOTOR_ID: {
|
|
1304
|
+
type: ArgumentType.STRING,
|
|
1305
|
+
menu: 'MOTOR_ID',
|
|
1306
|
+
defaultValue: BoostMotorLabel.A
|
|
1307
|
+
},
|
|
1308
|
+
DURATION: {
|
|
1309
|
+
type: ArgumentType.NUMBER,
|
|
1310
|
+
defaultValue: 1
|
|
1311
|
+
}
|
|
1312
|
+
}
|
|
1313
|
+
},
|
|
1314
|
+
{
|
|
1315
|
+
opcode: 'motorOnForRotation',
|
|
1316
|
+
text: formatMessage({
|
|
1317
|
+
id: 'boost.motorOnForRotation',
|
|
1318
|
+
default: 'turn motor [MOTOR_ID] for [ROTATION] rotations',
|
|
1319
|
+
description: 'turn a motor on for rotation'
|
|
1320
|
+
}),
|
|
1321
|
+
blockType: BlockType.COMMAND,
|
|
1322
|
+
arguments: {
|
|
1323
|
+
MOTOR_ID: {
|
|
1324
|
+
type: ArgumentType.STRING,
|
|
1325
|
+
menu: 'MOTOR_ID',
|
|
1326
|
+
defaultValue: BoostMotorLabel.A
|
|
1327
|
+
},
|
|
1328
|
+
ROTATION: {
|
|
1329
|
+
type: ArgumentType.NUMBER,
|
|
1330
|
+
defaultValue: 1
|
|
1331
|
+
}
|
|
1332
|
+
}
|
|
1333
|
+
},
|
|
1334
|
+
{
|
|
1335
|
+
opcode: 'motorOn',
|
|
1336
|
+
text: formatMessage({
|
|
1337
|
+
id: 'boost.motorOn',
|
|
1338
|
+
default: 'turn motor [MOTOR_ID] on',
|
|
1339
|
+
description: 'turn a motor on indefinitely'
|
|
1340
|
+
}),
|
|
1341
|
+
blockType: BlockType.COMMAND,
|
|
1342
|
+
arguments: {
|
|
1343
|
+
MOTOR_ID: {
|
|
1344
|
+
type: ArgumentType.STRING,
|
|
1345
|
+
menu: 'MOTOR_ID',
|
|
1346
|
+
defaultValue: BoostMotorLabel.A
|
|
1347
|
+
}
|
|
1348
|
+
}
|
|
1349
|
+
},
|
|
1350
|
+
{
|
|
1351
|
+
opcode: 'motorOff',
|
|
1352
|
+
text: formatMessage({
|
|
1353
|
+
id: 'boost.motorOff',
|
|
1354
|
+
default: 'turn motor [MOTOR_ID] off',
|
|
1355
|
+
description: 'turn a motor off'
|
|
1356
|
+
}),
|
|
1357
|
+
blockType: BlockType.COMMAND,
|
|
1358
|
+
arguments: {
|
|
1359
|
+
MOTOR_ID: {
|
|
1360
|
+
type: ArgumentType.STRING,
|
|
1361
|
+
menu: 'MOTOR_ID',
|
|
1362
|
+
defaultValue: BoostMotorLabel.A
|
|
1363
|
+
}
|
|
1364
|
+
}
|
|
1365
|
+
},
|
|
1366
|
+
{
|
|
1367
|
+
opcode: 'setMotorPower',
|
|
1368
|
+
text: formatMessage({
|
|
1369
|
+
id: 'boost.setMotorPower',
|
|
1370
|
+
default: 'set motor [MOTOR_ID] speed to [POWER] %',
|
|
1371
|
+
description: 'set the motor\'s speed without turning it on'
|
|
1372
|
+
}),
|
|
1373
|
+
blockType: BlockType.COMMAND,
|
|
1374
|
+
arguments: {
|
|
1375
|
+
MOTOR_ID: {
|
|
1376
|
+
type: ArgumentType.STRING,
|
|
1377
|
+
menu: 'MOTOR_ID',
|
|
1378
|
+
defaultValue: BoostMotorLabel.ALL
|
|
1379
|
+
},
|
|
1380
|
+
POWER: {
|
|
1381
|
+
type: ArgumentType.NUMBER,
|
|
1382
|
+
defaultValue: 100
|
|
1383
|
+
}
|
|
1384
|
+
}
|
|
1385
|
+
},
|
|
1386
|
+
{
|
|
1387
|
+
opcode: 'setMotorDirection',
|
|
1388
|
+
text: formatMessage({
|
|
1389
|
+
id: 'boost.setMotorDirection',
|
|
1390
|
+
default: 'set motor [MOTOR_ID] direction [MOTOR_DIRECTION]',
|
|
1391
|
+
description: 'set the motor\'s turn direction without turning it on'
|
|
1392
|
+
}),
|
|
1393
|
+
blockType: BlockType.COMMAND,
|
|
1394
|
+
arguments: {
|
|
1395
|
+
MOTOR_ID: {
|
|
1396
|
+
type: ArgumentType.STRING,
|
|
1397
|
+
menu: 'MOTOR_ID',
|
|
1398
|
+
defaultValue: BoostMotorLabel.A
|
|
1399
|
+
},
|
|
1400
|
+
MOTOR_DIRECTION: {
|
|
1401
|
+
type: ArgumentType.STRING,
|
|
1402
|
+
menu: 'MOTOR_DIRECTION',
|
|
1403
|
+
defaultValue: BoostMotorDirection.FORWARD
|
|
1404
|
+
}
|
|
1405
|
+
}
|
|
1406
|
+
},
|
|
1407
|
+
{
|
|
1408
|
+
opcode: 'getMotorPosition',
|
|
1409
|
+
text: formatMessage({
|
|
1410
|
+
id: 'boost.getMotorPosition',
|
|
1411
|
+
default: 'motor [MOTOR_REPORTER_ID] position',
|
|
1412
|
+
description: 'the position returned by the motor'
|
|
1413
|
+
}),
|
|
1414
|
+
blockType: BlockType.REPORTER,
|
|
1415
|
+
arguments: {
|
|
1416
|
+
MOTOR_REPORTER_ID: {
|
|
1417
|
+
type: ArgumentType.STRING,
|
|
1418
|
+
menu: 'MOTOR_REPORTER_ID',
|
|
1419
|
+
defaultValue: BoostMotorLabel.A
|
|
1420
|
+
}
|
|
1421
|
+
}
|
|
1422
|
+
},
|
|
1423
|
+
{
|
|
1424
|
+
opcode: 'whenColor',
|
|
1425
|
+
text: formatMessage({
|
|
1426
|
+
id: 'boost.whenColor',
|
|
1427
|
+
default: 'when [COLOR] brick seen',
|
|
1428
|
+
description: 'check for when color'
|
|
1429
|
+
}),
|
|
1430
|
+
blockType: BlockType.HAT,
|
|
1431
|
+
arguments: {
|
|
1432
|
+
COLOR: {
|
|
1433
|
+
type: ArgumentType.STRING,
|
|
1434
|
+
menu: 'COLOR',
|
|
1435
|
+
defaultValue: BoostColor.ANY
|
|
1436
|
+
}
|
|
1437
|
+
}
|
|
1438
|
+
},
|
|
1439
|
+
{
|
|
1440
|
+
opcode: 'seeingColor',
|
|
1441
|
+
text: formatMessage({
|
|
1442
|
+
id: 'boost.seeingColor',
|
|
1443
|
+
default: 'seeing [COLOR] brick?',
|
|
1444
|
+
description: 'is the color sensor seeing a certain color?'
|
|
1445
|
+
}),
|
|
1446
|
+
blockType: BlockType.BOOLEAN,
|
|
1447
|
+
arguments: {
|
|
1448
|
+
COLOR: {
|
|
1449
|
+
type: ArgumentType.STRING,
|
|
1450
|
+
menu: 'COLOR',
|
|
1451
|
+
defaultValue: BoostColor.ANY
|
|
1452
|
+
}
|
|
1453
|
+
}
|
|
1454
|
+
},
|
|
1455
|
+
{
|
|
1456
|
+
opcode: 'whenTilted',
|
|
1457
|
+
text: formatMessage({
|
|
1458
|
+
id: 'boost.whenTilted',
|
|
1459
|
+
default: 'when tilted [TILT_DIRECTION_ANY]',
|
|
1460
|
+
description: 'check when tilted in a certain direction'
|
|
1461
|
+
}),
|
|
1462
|
+
func: 'isTilted',
|
|
1463
|
+
blockType: BlockType.HAT,
|
|
1464
|
+
arguments: {
|
|
1465
|
+
TILT_DIRECTION_ANY: {
|
|
1466
|
+
type: ArgumentType.STRING,
|
|
1467
|
+
menu: 'TILT_DIRECTION_ANY',
|
|
1468
|
+
defaultValue: BoostTiltDirection.ANY
|
|
1469
|
+
}
|
|
1470
|
+
}
|
|
1471
|
+
},
|
|
1472
|
+
{
|
|
1473
|
+
opcode: 'getTiltAngle',
|
|
1474
|
+
text: formatMessage({
|
|
1475
|
+
id: 'boost.getTiltAngle',
|
|
1476
|
+
default: 'tilt angle [TILT_DIRECTION]',
|
|
1477
|
+
description: 'the angle returned by the tilt sensor'
|
|
1478
|
+
}),
|
|
1479
|
+
blockType: BlockType.REPORTER,
|
|
1480
|
+
arguments: {
|
|
1481
|
+
TILT_DIRECTION: {
|
|
1482
|
+
type: ArgumentType.STRING,
|
|
1483
|
+
menu: 'TILT_DIRECTION',
|
|
1484
|
+
defaultValue: BoostTiltDirection.UP
|
|
1485
|
+
}
|
|
1486
|
+
}
|
|
1487
|
+
},
|
|
1488
|
+
{
|
|
1489
|
+
opcode: 'setLightHue',
|
|
1490
|
+
text: formatMessage({
|
|
1491
|
+
id: 'boost.setLightHue',
|
|
1492
|
+
default: 'set light color to [HUE]',
|
|
1493
|
+
description: 'set the LED color'
|
|
1494
|
+
}),
|
|
1495
|
+
blockType: BlockType.COMMAND,
|
|
1496
|
+
arguments: {
|
|
1497
|
+
HUE: {
|
|
1498
|
+
type: ArgumentType.NUMBER,
|
|
1499
|
+
defaultValue: 50
|
|
1500
|
+
}
|
|
1501
|
+
}
|
|
1502
|
+
}
|
|
1503
|
+
],
|
|
1504
|
+
menus: {
|
|
1505
|
+
MOTOR_ID: {
|
|
1506
|
+
acceptReporters: true,
|
|
1507
|
+
items: [
|
|
1508
|
+
{
|
|
1509
|
+
text: 'A',
|
|
1510
|
+
value: BoostMotorLabel.A
|
|
1511
|
+
},
|
|
1512
|
+
{
|
|
1513
|
+
text: 'B',
|
|
1514
|
+
value: BoostMotorLabel.B
|
|
1515
|
+
},
|
|
1516
|
+
{
|
|
1517
|
+
text: 'C',
|
|
1518
|
+
value: BoostMotorLabel.C
|
|
1519
|
+
},
|
|
1520
|
+
{
|
|
1521
|
+
text: 'D',
|
|
1522
|
+
value: BoostMotorLabel.D
|
|
1523
|
+
},
|
|
1524
|
+
{
|
|
1525
|
+
text: 'AB',
|
|
1526
|
+
value: BoostMotorLabel.AB
|
|
1527
|
+
},
|
|
1528
|
+
{
|
|
1529
|
+
text: 'ABCD',
|
|
1530
|
+
value: BoostMotorLabel.ALL
|
|
1531
|
+
}
|
|
1532
|
+
]
|
|
1533
|
+
},
|
|
1534
|
+
MOTOR_REPORTER_ID: {
|
|
1535
|
+
acceptReporters: true,
|
|
1536
|
+
items: [
|
|
1537
|
+
{
|
|
1538
|
+
text: 'A',
|
|
1539
|
+
value: BoostMotorLabel.A
|
|
1540
|
+
},
|
|
1541
|
+
{
|
|
1542
|
+
text: 'B',
|
|
1543
|
+
value: BoostMotorLabel.B
|
|
1544
|
+
},
|
|
1545
|
+
{
|
|
1546
|
+
text: 'C',
|
|
1547
|
+
value: BoostMotorLabel.C
|
|
1548
|
+
},
|
|
1549
|
+
{
|
|
1550
|
+
text: 'D',
|
|
1551
|
+
value: BoostMotorLabel.D
|
|
1552
|
+
}
|
|
1553
|
+
]
|
|
1554
|
+
},
|
|
1555
|
+
MOTOR_DIRECTION: {
|
|
1556
|
+
acceptReporters: true,
|
|
1557
|
+
items: [
|
|
1558
|
+
{
|
|
1559
|
+
text: formatMessage({
|
|
1560
|
+
id: 'boost.motorDirection.forward',
|
|
1561
|
+
default: 'this way',
|
|
1562
|
+
description:
|
|
1563
|
+
'label for forward element in motor direction menu for LEGO Boost extension'
|
|
1564
|
+
}),
|
|
1565
|
+
value: BoostMotorDirection.FORWARD
|
|
1566
|
+
},
|
|
1567
|
+
{
|
|
1568
|
+
text: formatMessage({
|
|
1569
|
+
id: 'boost.motorDirection.backward',
|
|
1570
|
+
default: 'that way',
|
|
1571
|
+
description:
|
|
1572
|
+
'label for backward element in motor direction menu for LEGO Boost extension'
|
|
1573
|
+
}),
|
|
1574
|
+
value: BoostMotorDirection.BACKWARD
|
|
1575
|
+
},
|
|
1576
|
+
{
|
|
1577
|
+
text: formatMessage({
|
|
1578
|
+
id: 'boost.motorDirection.reverse',
|
|
1579
|
+
default: 'reverse',
|
|
1580
|
+
description:
|
|
1581
|
+
'label for reverse element in motor direction menu for LEGO Boost extension'
|
|
1582
|
+
}),
|
|
1583
|
+
value: BoostMotorDirection.REVERSE
|
|
1584
|
+
}
|
|
1585
|
+
]
|
|
1586
|
+
},
|
|
1587
|
+
TILT_DIRECTION: {
|
|
1588
|
+
acceptReporters: true,
|
|
1589
|
+
items: [
|
|
1590
|
+
{
|
|
1591
|
+
text: formatMessage({
|
|
1592
|
+
id: 'boost.tiltDirection.up',
|
|
1593
|
+
default: 'up',
|
|
1594
|
+
description: 'label for up element in tilt direction menu for LEGO Boost extension'
|
|
1595
|
+
}),
|
|
1596
|
+
value: BoostTiltDirection.UP
|
|
1597
|
+
},
|
|
1598
|
+
{
|
|
1599
|
+
text: formatMessage({
|
|
1600
|
+
id: 'boost.tiltDirection.down',
|
|
1601
|
+
default: 'down',
|
|
1602
|
+
description: 'label for down element in tilt direction menu for LEGO Boost extension'
|
|
1603
|
+
}),
|
|
1604
|
+
value: BoostTiltDirection.DOWN
|
|
1605
|
+
},
|
|
1606
|
+
{
|
|
1607
|
+
text: formatMessage({
|
|
1608
|
+
id: 'boost.tiltDirection.left',
|
|
1609
|
+
default: 'left',
|
|
1610
|
+
description: 'label for left element in tilt direction menu for LEGO Boost extension'
|
|
1611
|
+
}),
|
|
1612
|
+
value: BoostTiltDirection.LEFT
|
|
1613
|
+
},
|
|
1614
|
+
{
|
|
1615
|
+
text: formatMessage({
|
|
1616
|
+
id: 'boost.tiltDirection.right',
|
|
1617
|
+
default: 'right',
|
|
1618
|
+
description: 'label for right element in tilt direction menu for LEGO Boost extension'
|
|
1619
|
+
}),
|
|
1620
|
+
value: BoostTiltDirection.RIGHT
|
|
1621
|
+
}
|
|
1622
|
+
]
|
|
1623
|
+
},
|
|
1624
|
+
TILT_DIRECTION_ANY: {
|
|
1625
|
+
acceptReporters: true,
|
|
1626
|
+
items: [
|
|
1627
|
+
{
|
|
1628
|
+
text: formatMessage({
|
|
1629
|
+
id: 'boost.tiltDirection.up',
|
|
1630
|
+
default: 'up'
|
|
1631
|
+
}),
|
|
1632
|
+
value: BoostTiltDirection.UP
|
|
1633
|
+
},
|
|
1634
|
+
{
|
|
1635
|
+
text: formatMessage({
|
|
1636
|
+
id: 'boost.tiltDirection.down',
|
|
1637
|
+
default: 'down'
|
|
1638
|
+
}),
|
|
1639
|
+
value: BoostTiltDirection.DOWN
|
|
1640
|
+
},
|
|
1641
|
+
{
|
|
1642
|
+
text: formatMessage({
|
|
1643
|
+
id: 'boost.tiltDirection.left',
|
|
1644
|
+
default: 'left'
|
|
1645
|
+
}),
|
|
1646
|
+
value: BoostTiltDirection.LEFT
|
|
1647
|
+
},
|
|
1648
|
+
{
|
|
1649
|
+
text: formatMessage({
|
|
1650
|
+
id: 'boost.tiltDirection.right',
|
|
1651
|
+
default: 'right'
|
|
1652
|
+
}),
|
|
1653
|
+
value: BoostTiltDirection.RIGHT
|
|
1654
|
+
},
|
|
1655
|
+
{
|
|
1656
|
+
text: formatMessage({
|
|
1657
|
+
id: 'boost.tiltDirection.any',
|
|
1658
|
+
default: 'any',
|
|
1659
|
+
description: 'label for any element in tilt direction menu for LEGO Boost extension'
|
|
1660
|
+
}),
|
|
1661
|
+
value: BoostTiltDirection.ANY
|
|
1662
|
+
}
|
|
1663
|
+
]
|
|
1664
|
+
},
|
|
1665
|
+
COLOR: {
|
|
1666
|
+
acceptReporters: true,
|
|
1667
|
+
items: [
|
|
1668
|
+
{
|
|
1669
|
+
text: formatMessage({
|
|
1670
|
+
id: 'boost.color.red',
|
|
1671
|
+
default: 'red',
|
|
1672
|
+
description: 'the color red'
|
|
1673
|
+
}),
|
|
1674
|
+
value: BoostColor.RED
|
|
1675
|
+
},
|
|
1676
|
+
{
|
|
1677
|
+
text: formatMessage({
|
|
1678
|
+
id: 'boost.color.blue',
|
|
1679
|
+
default: 'blue',
|
|
1680
|
+
description: 'the color blue'
|
|
1681
|
+
}),
|
|
1682
|
+
value: BoostColor.BLUE
|
|
1683
|
+
},
|
|
1684
|
+
{
|
|
1685
|
+
text: formatMessage({
|
|
1686
|
+
id: 'boost.color.green',
|
|
1687
|
+
default: 'green',
|
|
1688
|
+
description: 'the color green'
|
|
1689
|
+
}),
|
|
1690
|
+
value: BoostColor.GREEN
|
|
1691
|
+
},
|
|
1692
|
+
{
|
|
1693
|
+
text: formatMessage({
|
|
1694
|
+
id: 'boost.color.yellow',
|
|
1695
|
+
default: 'yellow',
|
|
1696
|
+
description: 'the color yellow'
|
|
1697
|
+
}),
|
|
1698
|
+
value: BoostColor.YELLOW
|
|
1699
|
+
},
|
|
1700
|
+
{
|
|
1701
|
+
text: formatMessage({
|
|
1702
|
+
id: 'boost.color.white',
|
|
1703
|
+
default: 'white',
|
|
1704
|
+
desription: 'the color white'
|
|
1705
|
+
}),
|
|
1706
|
+
value: BoostColor.WHITE
|
|
1707
|
+
},
|
|
1708
|
+
{
|
|
1709
|
+
text: formatMessage({
|
|
1710
|
+
id: 'boost.color.black',
|
|
1711
|
+
default: 'black',
|
|
1712
|
+
description: 'the color black'
|
|
1713
|
+
}),
|
|
1714
|
+
value: BoostColor.BLACK
|
|
1715
|
+
},
|
|
1716
|
+
{
|
|
1717
|
+
text: formatMessage({
|
|
1718
|
+
id: 'boost.color.any',
|
|
1719
|
+
default: 'any color',
|
|
1720
|
+
description: 'any color'
|
|
1721
|
+
}),
|
|
1722
|
+
value: BoostColor.ANY
|
|
1723
|
+
}
|
|
1724
|
+
]
|
|
1725
|
+
}
|
|
1726
|
+
}
|
|
1727
|
+
};
|
|
1728
|
+
}
|
|
1729
|
+
|
|
1730
|
+
/**
|
|
1731
|
+
* Turn specified motor(s) on for a specified duration.
|
|
1732
|
+
* @param {object} args - the block's arguments.
|
|
1733
|
+
* @property {MotorID} MOTOR_ID - the motor(s) to activate.
|
|
1734
|
+
* @property {int} DURATION - the amount of time to run the motors.
|
|
1735
|
+
* @return {Promise} - a promise which will resolve at the end of the duration.
|
|
1736
|
+
*/
|
|
1737
|
+
motorOnFor (args) {
|
|
1738
|
+
// TODO: cast args.MOTOR_ID?
|
|
1739
|
+
let durationMS = Cast.toNumber(args.DURATION) * 1000;
|
|
1740
|
+
durationMS = MathUtil.clamp(durationMS, 0, 15000);
|
|
1741
|
+
return new Promise(resolve => {
|
|
1742
|
+
this._forEachMotor(args.MOTOR_ID, motorIndex => {
|
|
1743
|
+
const motor = this._peripheral.motor(motorIndex);
|
|
1744
|
+
if (motor) motor.turnOnFor(durationMS);
|
|
1745
|
+
});
|
|
1746
|
+
|
|
1747
|
+
// Run for some time even when no motor is connected
|
|
1748
|
+
setTimeout(resolve, durationMS);
|
|
1749
|
+
});
|
|
1750
|
+
}
|
|
1751
|
+
|
|
1752
|
+
/**
|
|
1753
|
+
* Turn specified motor(s) on for a specified rotation in full rotations.
|
|
1754
|
+
* @param {object} args - the block's arguments.
|
|
1755
|
+
* @property {MotorID} MOTOR_ID - the motor(s) to activate.
|
|
1756
|
+
* @property {int} ROTATION - the amount of full rotations to turn the motors.
|
|
1757
|
+
* @return {Promise} - a promise which will resolve at the end of the duration.
|
|
1758
|
+
*/
|
|
1759
|
+
motorOnForRotation (args) {
|
|
1760
|
+
// TODO: cast args.MOTOR_ID?
|
|
1761
|
+
let degrees = Cast.toNumber(args.ROTATION) * 360;
|
|
1762
|
+
// TODO: Clamps to 100 rotations. Consider changing.
|
|
1763
|
+
const sign = Math.sign(degrees);
|
|
1764
|
+
degrees = Math.abs(MathUtil.clamp(degrees, -360000, 360000));
|
|
1765
|
+
|
|
1766
|
+
const motors = [];
|
|
1767
|
+
this._forEachMotor(args.MOTOR_ID, motorIndex => {
|
|
1768
|
+
motors.push(motorIndex);
|
|
1769
|
+
});
|
|
1770
|
+
|
|
1771
|
+
/**
|
|
1772
|
+
* Checks that the motors given in args.MOTOR_ID exist,
|
|
1773
|
+
* and maps a promise for each of the motor-commands to an array.
|
|
1774
|
+
*/
|
|
1775
|
+
const promises = motors.map(portID => {
|
|
1776
|
+
const motor = this._peripheral.motor(portID);
|
|
1777
|
+
if (motor) {
|
|
1778
|
+
// to avoid a hanging block if power is 0, return an immediately resolving promise.
|
|
1779
|
+
if (motor.power === 0) return Promise.resolve();
|
|
1780
|
+
return new Promise(resolve => {
|
|
1781
|
+
motor.turnOnForDegrees(degrees, sign);
|
|
1782
|
+
motor.pendingRotationPromise = resolve;
|
|
1783
|
+
});
|
|
1784
|
+
}
|
|
1785
|
+
return null;
|
|
1786
|
+
});
|
|
1787
|
+
/**
|
|
1788
|
+
* Make sure all promises are resolved, i.e. all motor-commands have completed.
|
|
1789
|
+
* To prevent the block from returning a value, an empty function is added to the .then
|
|
1790
|
+
*/
|
|
1791
|
+
return Promise.all(promises).then(() => {});
|
|
1792
|
+
}
|
|
1793
|
+
|
|
1794
|
+
/**
|
|
1795
|
+
* Turn specified motor(s) on indefinitely.
|
|
1796
|
+
* @param {object} args - the block's arguments.
|
|
1797
|
+
* @property {MotorID} MOTOR_ID - the motor(s) to activate.
|
|
1798
|
+
* @return {Promise} - a Promise that resolves after some delay.
|
|
1799
|
+
*/
|
|
1800
|
+
motorOn (args) {
|
|
1801
|
+
// TODO: cast args.MOTOR_ID?
|
|
1802
|
+
this._forEachMotor(args.MOTOR_ID, motorIndex => {
|
|
1803
|
+
const motor = this._peripheral.motor(motorIndex);
|
|
1804
|
+
if (motor) motor.turnOnForever();
|
|
1805
|
+
});
|
|
1806
|
+
|
|
1807
|
+
return new Promise(resolve => {
|
|
1808
|
+
window.setTimeout(() => {
|
|
1809
|
+
resolve();
|
|
1810
|
+
}, BoostBLE.sendInterval);
|
|
1811
|
+
});
|
|
1812
|
+
}
|
|
1813
|
+
|
|
1814
|
+
/**
|
|
1815
|
+
* Turn specified motor(s) off.
|
|
1816
|
+
* @param {object} args - the block's arguments.
|
|
1817
|
+
* @property {MotorID} MOTOR_ID - the motor(s) to deactivate.
|
|
1818
|
+
* @return {Promise} - a Promise that resolves after some delay.
|
|
1819
|
+
*/
|
|
1820
|
+
motorOff (args) {
|
|
1821
|
+
// TODO: cast args.MOTOR_ID?
|
|
1822
|
+
this._forEachMotor(args.MOTOR_ID, motorIndex => {
|
|
1823
|
+
const motor = this._peripheral.motor(motorIndex);
|
|
1824
|
+
if (motor) motor.turnOff();
|
|
1825
|
+
});
|
|
1826
|
+
|
|
1827
|
+
return new Promise(resolve => {
|
|
1828
|
+
window.setTimeout(() => {
|
|
1829
|
+
resolve();
|
|
1830
|
+
}, BoostBLE.sendInterval);
|
|
1831
|
+
});
|
|
1832
|
+
}
|
|
1833
|
+
|
|
1834
|
+
/**
|
|
1835
|
+
* Set the power level of the specified motor(s).
|
|
1836
|
+
* @param {object} args - the block's arguments.
|
|
1837
|
+
* @property {MotorID} MOTOR_ID - the motor(s) to be affected.
|
|
1838
|
+
* @property {int} POWER - the new power level for the motor(s).
|
|
1839
|
+
* @return {Promise} - returns a promise to make sure the block yields.
|
|
1840
|
+
*/
|
|
1841
|
+
setMotorPower (args) {
|
|
1842
|
+
// TODO: cast args.MOTOR_ID?
|
|
1843
|
+
this._forEachMotor(args.MOTOR_ID, motorIndex => {
|
|
1844
|
+
const motor = this._peripheral.motor(motorIndex);
|
|
1845
|
+
if (motor) {
|
|
1846
|
+
motor.power = MathUtil.clamp(Cast.toNumber(args.POWER), 0, 100);
|
|
1847
|
+
switch (motor.status) {
|
|
1848
|
+
case BoostMotorState.ON_FOREVER:
|
|
1849
|
+
motor.turnOnForever();
|
|
1850
|
+
break;
|
|
1851
|
+
case BoostMotorState.ON_FOR_TIME:
|
|
1852
|
+
motor.turnOnFor(motor.pendingDurationTimeoutStartTime +
|
|
1853
|
+
motor.pendingDurationTimeoutDelay - Date.now());
|
|
1854
|
+
break;
|
|
1855
|
+
}
|
|
1856
|
+
}
|
|
1857
|
+
});
|
|
1858
|
+
return new Promise(resolve => {
|
|
1859
|
+
window.setTimeout(() => {
|
|
1860
|
+
resolve();
|
|
1861
|
+
}, BoostBLE.sendInterval);
|
|
1862
|
+
});
|
|
1863
|
+
}
|
|
1864
|
+
|
|
1865
|
+
/**
|
|
1866
|
+
* Set the direction of rotation for specified motor(s).
|
|
1867
|
+
* If the direction is 'reverse' the motor(s) will be reversed individually.
|
|
1868
|
+
* @param {object} args - the block's arguments.
|
|
1869
|
+
* @property {MotorID} MOTOR_ID - the motor(s) to be affected.
|
|
1870
|
+
* @property {MotorDirection} MOTOR_DIRECTION - the new direction for the motor(s).
|
|
1871
|
+
* @return {Promise} - returns a promise to make sure the block yields.
|
|
1872
|
+
*/
|
|
1873
|
+
setMotorDirection (args) {
|
|
1874
|
+
// TODO: cast args.MOTOR_ID?
|
|
1875
|
+
this._forEachMotor(args.MOTOR_ID, motorIndex => {
|
|
1876
|
+
const motor = this._peripheral.motor(motorIndex);
|
|
1877
|
+
if (motor) {
|
|
1878
|
+
switch (args.MOTOR_DIRECTION) {
|
|
1879
|
+
case BoostMotorDirection.FORWARD:
|
|
1880
|
+
motor.direction = 1;
|
|
1881
|
+
break;
|
|
1882
|
+
case BoostMotorDirection.BACKWARD:
|
|
1883
|
+
motor.direction = -1;
|
|
1884
|
+
break;
|
|
1885
|
+
case BoostMotorDirection.REVERSE:
|
|
1886
|
+
motor.direction = -motor.direction;
|
|
1887
|
+
break;
|
|
1888
|
+
default:
|
|
1889
|
+
log.warn(`Unknown motor direction in setMotorDirection: ${args.DIRECTION}`);
|
|
1890
|
+
break;
|
|
1891
|
+
}
|
|
1892
|
+
// keep the motor on if it's running, and update the pending timeout if needed
|
|
1893
|
+
if (motor) {
|
|
1894
|
+
switch (motor.status) {
|
|
1895
|
+
case BoostMotorState.ON_FOREVER:
|
|
1896
|
+
motor.turnOnForever();
|
|
1897
|
+
break;
|
|
1898
|
+
case BoostMotorState.ON_FOR_TIME:
|
|
1899
|
+
motor.turnOnFor(motor.pendingDurationTimeoutStartTime +
|
|
1900
|
+
motor.pendingDurationTimeoutDelay - Date.now());
|
|
1901
|
+
break;
|
|
1902
|
+
}
|
|
1903
|
+
}
|
|
1904
|
+
}
|
|
1905
|
+
});
|
|
1906
|
+
return new Promise(resolve => {
|
|
1907
|
+
window.setTimeout(() => {
|
|
1908
|
+
resolve();
|
|
1909
|
+
}, BoostBLE.sendInterval);
|
|
1910
|
+
});
|
|
1911
|
+
}
|
|
1912
|
+
|
|
1913
|
+
/**
|
|
1914
|
+
* @param {object} args - the block's arguments.
|
|
1915
|
+
* @return {number} - returns the motor's position.
|
|
1916
|
+
*/
|
|
1917
|
+
getMotorPosition (args) {
|
|
1918
|
+
let portID = null;
|
|
1919
|
+
switch (args.MOTOR_REPORTER_ID) {
|
|
1920
|
+
|
|
1921
|
+
case BoostMotorLabel.A:
|
|
1922
|
+
portID = BoostPort.A;
|
|
1923
|
+
break;
|
|
1924
|
+
case BoostMotorLabel.B:
|
|
1925
|
+
portID = BoostPort.B;
|
|
1926
|
+
break;
|
|
1927
|
+
case BoostMotorLabel.C:
|
|
1928
|
+
portID = BoostPort.C;
|
|
1929
|
+
break;
|
|
1930
|
+
case BoostMotorLabel.D:
|
|
1931
|
+
portID = BoostPort.D;
|
|
1932
|
+
break;
|
|
1933
|
+
default:
|
|
1934
|
+
log.warn('Asked for a motor position that doesnt exist!');
|
|
1935
|
+
return false;
|
|
1936
|
+
}
|
|
1937
|
+
if (portID !== null && this._peripheral.motor(portID)) {
|
|
1938
|
+
let val = this._peripheral.motor(portID).position;
|
|
1939
|
+
// Boost motor A position direction is reversed by design
|
|
1940
|
+
// so we have to reverse the position here
|
|
1941
|
+
if (portID === BoostPort.A) {
|
|
1942
|
+
val *= -1;
|
|
1943
|
+
}
|
|
1944
|
+
return MathUtil.wrapClamp(val, 0, 360);
|
|
1945
|
+
}
|
|
1946
|
+
return 0;
|
|
1947
|
+
}
|
|
1948
|
+
|
|
1949
|
+
/**
|
|
1950
|
+
* Call a callback for each motor indexed by the provided motor ID.
|
|
1951
|
+
* @param {MotorID} motorID - the ID specifier.
|
|
1952
|
+
* @param {Function} callback - the function to call with the numeric motor index for each motor.
|
|
1953
|
+
* @private
|
|
1954
|
+
*/
|
|
1955
|
+
_forEachMotor (motorID, callback) {
|
|
1956
|
+
let motors;
|
|
1957
|
+
switch (motorID) {
|
|
1958
|
+
case BoostMotorLabel.A:
|
|
1959
|
+
motors = [BoostPort.A];
|
|
1960
|
+
break;
|
|
1961
|
+
case BoostMotorLabel.B:
|
|
1962
|
+
motors = [BoostPort.B];
|
|
1963
|
+
break;
|
|
1964
|
+
case BoostMotorLabel.C:
|
|
1965
|
+
motors = [BoostPort.C];
|
|
1966
|
+
break;
|
|
1967
|
+
case BoostMotorLabel.D:
|
|
1968
|
+
motors = [BoostPort.D];
|
|
1969
|
+
break;
|
|
1970
|
+
case BoostMotorLabel.AB:
|
|
1971
|
+
motors = [BoostPort.A, BoostPort.B];
|
|
1972
|
+
break;
|
|
1973
|
+
case BoostMotorLabel.ALL:
|
|
1974
|
+
motors = [BoostPort.A, BoostPort.B, BoostPort.C, BoostPort.D];
|
|
1975
|
+
break;
|
|
1976
|
+
default:
|
|
1977
|
+
log.warn(`Invalid motor ID: ${motorID}`);
|
|
1978
|
+
motors = [];
|
|
1979
|
+
break;
|
|
1980
|
+
}
|
|
1981
|
+
for (const index of motors) {
|
|
1982
|
+
callback(index);
|
|
1983
|
+
}
|
|
1984
|
+
}
|
|
1985
|
+
|
|
1986
|
+
/**
|
|
1987
|
+
* Test whether the tilt sensor is currently tilted.
|
|
1988
|
+
* @param {object} args - the block's arguments.
|
|
1989
|
+
* @property {TiltDirection} TILT_DIRECTION_ANY - the tilt direction to test (up, down, left, right, or any).
|
|
1990
|
+
* @return {boolean} - true if the tilt sensor is tilted past a threshold in the specified direction.
|
|
1991
|
+
*/
|
|
1992
|
+
whenTilted (args) {
|
|
1993
|
+
return this._isTilted(args.TILT_DIRECTION_ANY);
|
|
1994
|
+
}
|
|
1995
|
+
|
|
1996
|
+
/**
|
|
1997
|
+
* Test whether the tilt sensor is currently tilted.
|
|
1998
|
+
* @param {object} args - the block's arguments.
|
|
1999
|
+
* @property {TiltDirection} TILT_DIRECTION_ANY - the tilt direction to test (up, down, left, right, or any).
|
|
2000
|
+
* @return {boolean} - true if the tilt sensor is tilted past a threshold in the specified direction.
|
|
2001
|
+
*/
|
|
2002
|
+
isTilted (args) {
|
|
2003
|
+
return this._isTilted(args.TILT_DIRECTION_ANY);
|
|
2004
|
+
}
|
|
2005
|
+
|
|
2006
|
+
/**
|
|
2007
|
+
* @param {object} args - the block's arguments.
|
|
2008
|
+
* @property {TiltDirection} TILT_DIRECTION - the direction (up, down, left, right) to check.
|
|
2009
|
+
* @return {number} - the tilt sensor's angle in the specified direction.
|
|
2010
|
+
* Note that getTiltAngle(up) = -getTiltAngle(down) and getTiltAngle(left) = -getTiltAngle(right).
|
|
2011
|
+
*/
|
|
2012
|
+
getTiltAngle (args) {
|
|
2013
|
+
return this._getTiltAngle(args.TILT_DIRECTION);
|
|
2014
|
+
}
|
|
2015
|
+
|
|
2016
|
+
/**
|
|
2017
|
+
* Test whether the tilt sensor is currently tilted.
|
|
2018
|
+
* @param {TiltDirection} direction - the tilt direction to test (up, down, left, right, or any).
|
|
2019
|
+
* @return {boolean} - true if the tilt sensor is tilted past a threshold in the specified direction.
|
|
2020
|
+
* @private
|
|
2021
|
+
*/
|
|
2022
|
+
_isTilted (direction) {
|
|
2023
|
+
switch (direction) {
|
|
2024
|
+
case BoostTiltDirection.ANY:
|
|
2025
|
+
return (Math.abs(this._peripheral.tiltX) >= Scratch3BoostBlocks.TILT_THRESHOLD) ||
|
|
2026
|
+
(Math.abs(this._peripheral.tiltY) >= Scratch3BoostBlocks.TILT_THRESHOLD);
|
|
2027
|
+
default:
|
|
2028
|
+
return this._getTiltAngle(direction) >= Scratch3BoostBlocks.TILT_THRESHOLD;
|
|
2029
|
+
}
|
|
2030
|
+
}
|
|
2031
|
+
|
|
2032
|
+
/**
|
|
2033
|
+
* @param {TiltDirection} direction - the direction (up, down, left, right) to check.
|
|
2034
|
+
* @return {number} - the tilt sensor's angle in the specified direction.
|
|
2035
|
+
* Note that getTiltAngle(up) = -getTiltAngle(down) and getTiltAngle(left) = -getTiltAngle(right).
|
|
2036
|
+
* @private
|
|
2037
|
+
*/
|
|
2038
|
+
_getTiltAngle (direction) {
|
|
2039
|
+
switch (direction) {
|
|
2040
|
+
case BoostTiltDirection.UP:
|
|
2041
|
+
return this._peripheral.tiltY > 90 ? 256 - this._peripheral.tiltY : -this._peripheral.tiltY;
|
|
2042
|
+
case BoostTiltDirection.DOWN:
|
|
2043
|
+
return this._peripheral.tiltY > 90 ? this._peripheral.tiltY - 256 : this._peripheral.tiltY;
|
|
2044
|
+
case BoostTiltDirection.LEFT:
|
|
2045
|
+
return this._peripheral.tiltX > 90 ? this._peripheral.tiltX - 256 : this._peripheral.tiltX;
|
|
2046
|
+
case BoostTiltDirection.RIGHT:
|
|
2047
|
+
return this._peripheral.tiltX > 90 ? 256 - this._peripheral.tiltX : -this._peripheral.tiltX;
|
|
2048
|
+
default:
|
|
2049
|
+
log.warn(`Unknown tilt direction in _getTiltAngle: ${direction}`);
|
|
2050
|
+
}
|
|
2051
|
+
}
|
|
2052
|
+
|
|
2053
|
+
/**
|
|
2054
|
+
* Edge-triggering hat function, for when the vision sensor is detecting
|
|
2055
|
+
* a certain color.
|
|
2056
|
+
* @param {object} args - the block's arguments.
|
|
2057
|
+
* @return {boolean} - true when the color sensor senses the specified color.
|
|
2058
|
+
*/
|
|
2059
|
+
whenColor (args) {
|
|
2060
|
+
if (args.COLOR === BoostColor.ANY) {
|
|
2061
|
+
// For "any" color, return true if the color is not "none", and
|
|
2062
|
+
// the color is different from the previous color detected. This
|
|
2063
|
+
// allows the hat to trigger when the color changes from one color
|
|
2064
|
+
// to another.
|
|
2065
|
+
return this._peripheral.color !== BoostColor.NONE &&
|
|
2066
|
+
this._peripheral.color !== this._peripheral.previousColor;
|
|
2067
|
+
}
|
|
2068
|
+
|
|
2069
|
+
return args.COLOR === this._peripheral.color;
|
|
2070
|
+
}
|
|
2071
|
+
|
|
2072
|
+
/**
|
|
2073
|
+
* A boolean reporter function, for whether the vision sensor is detecting
|
|
2074
|
+
* a certain color.
|
|
2075
|
+
* @param {object} args - the block's arguments.
|
|
2076
|
+
* @return {boolean} - true when the color sensor senses the specified color.
|
|
2077
|
+
*/
|
|
2078
|
+
seeingColor (args) {
|
|
2079
|
+
if (args.COLOR === BoostColor.ANY) {
|
|
2080
|
+
return this._peripheral.color !== BoostColor.NONE;
|
|
2081
|
+
}
|
|
2082
|
+
|
|
2083
|
+
return args.COLOR === this._peripheral.color;
|
|
2084
|
+
}
|
|
2085
|
+
|
|
2086
|
+
/**
|
|
2087
|
+
* Set the LED's hue.
|
|
2088
|
+
* @param {object} args - the block's arguments.
|
|
2089
|
+
* @property {number} HUE - the hue to set, in the range [0,100].
|
|
2090
|
+
* @return {Promise} - a Promise that resolves after some delay.
|
|
2091
|
+
*/
|
|
2092
|
+
setLightHue (args) {
|
|
2093
|
+
// Convert from [0,100] to [0,360]
|
|
2094
|
+
let inputHue = Cast.toNumber(args.HUE);
|
|
2095
|
+
inputHue = MathUtil.wrapClamp(inputHue, 0, 100);
|
|
2096
|
+
const hue = inputHue * 360 / 100;
|
|
2097
|
+
|
|
2098
|
+
const rgbObject = color.hsvToRgb({h: hue, s: 1, v: 1});
|
|
2099
|
+
|
|
2100
|
+
const rgbDecimal = color.rgbToDecimal(rgbObject);
|
|
2101
|
+
|
|
2102
|
+
this._peripheral._led = inputHue;
|
|
2103
|
+
this._peripheral.setLED(rgbDecimal);
|
|
2104
|
+
|
|
2105
|
+
return new Promise(resolve => {
|
|
2106
|
+
window.setTimeout(() => {
|
|
2107
|
+
resolve();
|
|
2108
|
+
}, BoostBLE.sendInterval);
|
|
2109
|
+
});
|
|
2110
|
+
}
|
|
2111
|
+
}
|
|
2112
|
+
|
|
2113
|
+
module.exports = Scratch3BoostBlocks;
|