@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,981 @@
|
|
|
1
|
+
const ArgumentType = require('../../extension-support/argument-type');
|
|
2
|
+
const BlockType = require('../../extension-support/block-type');
|
|
3
|
+
const log = require('../../util/log');
|
|
4
|
+
const formatMessage = require('format-message');
|
|
5
|
+
const MathUtil = require('../../util/math-util');
|
|
6
|
+
const BLE = require('../../io/ble');
|
|
7
|
+
const godirect = require('@vernier/godirect');
|
|
8
|
+
const ScratchLinkDeviceAdapter = require('./scratch-link-device-adapter');
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Icon png to be displayed at the left edge of each extension block, encoded as a data URI.
|
|
12
|
+
* @type {string}
|
|
13
|
+
*/
|
|
14
|
+
// eslint-disable-next-line max-len
|
|
15
|
+
const blockIconURI = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAABQCAYAAACOEfKtAAAABGdBTUEAALGPC/xhBQAACCNJREFUeAHtnGtsFFUUgM+dfXbbbbcWaKHSFgrlkWgkJCb6A4kmJfiHIBYBpcFfRg1GEkmEVAvhFYw/TExMxGoICAECiZEIIUQCiiT4gh+KILRQCi2ENIV2t/ue6zl3u2Upu4XuzO4csCe587iPmXO/OWfunTszV4ABWfflQU+0p+9bTcLzEmS5gUPlvagAcVMXcMpnK1u+evW8QLYKaNkWpHKxnt6dQsqFjxo80p10Jt1vx7t30n62Ys+2IJUTUpDlqUNomgYutwsjhZFD5r6slBAOhUHX9YTe6D1GTmrIAhFeBZ2c4JFCpBiggmwlBR7pTGLUewxZYBIUWV7yqgb7g8lotuukt5ihqyELHCSEbusk931ExMxbjSkWSNxEyr3vysxZLFHWnDuT0CtFV6OKmmOBRrV4hMubZoGmMZA6lHTfgsLeHnBEIiCxUY86XRDw+sBfOgZ0m820U5lxIFYAncF+GNvVDo5QaLBu1ClyYTyF4tvd8lZltQgXFA6mW73BxoVt0ShUXG2VCp4QQdDEFqez4Bm7p7gaO0of422r3x4Ji/KrbdIexu4SE2FjgWO6OkCLx6gt6gxOiNV92tiY+ni1Ye1nu7dpQfk35ikru9EBN6unsEDIwgLJPQv8dwCfT3WPt+iFIfAUqM3vL7vpjmuz0KX1gkAfOMN33dxKkjwA9vsTDIS8uubdBZcyAWlqWtohQbRSuru/L1O2vMazAGiLxRKVFqDgDEdAaHCN0kU8Ply2vKWxABhzJZ5ipC6qHlRzfJxVz99S49GdYQEw7PYkuAmokZJ6fumlQUqiNpVSQ56i9JnyHMsCYMRdADGHk0ZyHM1b976XicH0rXtWYR57FPNSGQ7CAiCBCJQ8oXhI0FdmBiPfVnl9ZZmz5DmFDcA+HwIUOEYMcjL2+e57PbBp04HxONI4ifIEKC8TYQMwhs+7IU+hwBFOYQvB5qF8grbwJnRfQXnIhbkIG4AExF+ScE00w0X3AZLwisrDyH1JH1YAA8UlIG029FRZsu6TPfVJiIltWYIjMTLgLUlGs1izeRYmGtS383t9wnu7G2J6fH/Tln2LNUdExGLxvZSOQ1qCS/+P9CFhBZAUuj12PHgCvRJHZ7w4EnhYjya6hXGHQ2Jaxj4ilbVC2AFEUNBVXSdKb3WC29+rmISKiqFn7ARBadyEHUACFHM64VZlDTdWafVh1Yik1ZB5JEsLJGaVtosw37ld4TscWQHX4+oRWO1zWrAEWCR6oMnTCEXijmI1234MVvsPgV+WcmKndGHpwlNtZwbhkZYEkuI4CkuAXfpk0HGAPym0TXEchaUL39Br4JvQeljk+lwxOxBeCRQ3UrFHI+AMBsEV6gcnhlwIS4BU0RORV1V42EqnwnLgSyo3AsM3eA9bPOt8bAEOV6NUWGRZ9FYvHSx6R0pfYgkMmk2DCH1+Z7KwB5gKazjLGgpLgUOAuRZWALnDSncxLAOYCmskbqjhe02h5d6y0sFKF5cXgI8LrLwB9PTeGew6POwNnptlpYOVLi4nFjjuWts957rnBk8tomoZ+bjhPcqOcCcnAG34EaTqOjxmsNKxzQnAkX5wronsOry6zIn66ThljLNcg+W1a2Gi55+MCg6XcKl3NuxrbxouS87TLAcY1V0QV5+8jLyuEekeeSGTS1gOcM/lZpOrlN/DsRzOyi8CY2fLuwUum/wR1BT+ZUzrDKUv9D4LB9rXZEjNTfRjZYFS5r86ebfA3W0bcmMKFh01/5fMoorm6rSjAA2SNc2F8dvmQVWCgdy8fxg8gcEN0pWez80QUyyQFAqn/N9mhmK5PAYN7adecCPnMsUCCZ7U8ari4IGb87wJeKFDA/MlmHXBDVkgTR1CV4/gaThKzBoeKYpuSzqSrqSzEiFuJDayWxqyQJp3RUhYSKfWUSEz5iDIrhrZl8I5b37JvrTBT3wdpd43cOqT/WiJhq6ikQpkW5a8BxuS/X219uXZHoPKmdMUGdEgpWzTll3Kr95Z8VJK7N3NL7b/qHY2rnmdjd6G7oF3q/b/3RoFaPDajwIcBWiQgMHioxZoEKChfqDBc2csnmxtM2ZglMDKArFvduhBbLDv9sOD8oymA0xBCHVtl6+c7ey6Ibdt+3ox7WOoxMCmD4i68PrZkBQaEDUe1tnVqSyyfl79+vr6evz1C2jKogkYWEEc0JnViiZRqKuoqJiZtEJcn0GIsykewzhW2jJVZjzBamxsfK79ase/5MoXL106TnEDwfq36qgIF6HGjKyqFsNkDGMwUNxEDEmIHQTxyNGjH1AchvumBcC4vAuXVpiA+TDYMFDXiiZFoN+SrmMI7tixo/v3337diNtQUzNpPq1RChIra5ccAFKDUEwYLra2fnXu3PmtA0gojqbaVUNl23ft+pPiPW73U7RGYdGH5QCQYCg93C73075S34I5c+ZQa0s/B1Njou51tVVVatJAXcrED3Q4EI5plgsHgAQiSiRCoRD9ECeam9fPo32UJzFQYwJLlix9mdZ9fb1naY2iyiQ2rVtyAEi199Pi5M8/tdB62vRpzceOH3+toaHBh61w2clTp96sqq5ehUnxw0eO7KA8KKpMYtO6JZcOKTUeNRhsp0+ffmtilYI1VLf4+Qvn1784d+5ezEfW144hMR05blglpDgHSbqxt6Wl5Y8ZM6afKq8oL7LZHd54PH7H7w+cOPj9dx8uXbLk+ICynbhm4cJDr7LVMKmhoP5dphaWoFGrHMTAQrgBJCjkFdQHpPntqCUmiWCge14PBsvdFnUYlP8AMAKfKIKmYukAAAAASUVORK5CYII=';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Icon png to be displayed in the blocks category menu, encoded as a data URI.
|
|
19
|
+
* @type {string}
|
|
20
|
+
*/
|
|
21
|
+
// eslint-disable-next-line max-len
|
|
22
|
+
const menuIconURI = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAYAAACM/rhtAAAABGdBTUEAALGPC/xhBQAAA9dJREFUWAnNmE2IFEcUgF/9dE/v7LoaM9kkK4JBRA0EFBIPRm85hBAvEXHXwyo5eFE87GFcReMkObgJiQnkkJzEg9n8HIJixKNe1IMKihgiCbviwV11V3d0d3pmuqsqr5ppcEnb3TNVggVFVVe9eu+r97qqq4tASqp8/fsboQgmU0TMugi571K29bPy9ovPU8Sf16HbpQj3EkYFBcJcr5Am2nZfs94AIWVfqMQeHNwhICUBZ4ypUIA/X2sbIm2AW8AJK0lkEP6TJpfqwXgg4QxmF/fB7Gtvxk1G5ZKHU1CqTgPJoSUXYJYeohSUJu+qrqdVUGh2/pVX4VFffx77WaqBZkrkEFj271+qWH0sXcU3FBzyQe/Mg7B//LbKMTRTxNiDbsMHHjTJlyM7HEJIBHXs2KXFj+oTNSdoQOCYLS5jD9IwBMm5H8NplwwPb/QV4yEIcycaAza9IuA76B38fuz1OF5RXUkmHCdu6rg0BpSMgV/sAe7DdzGFrvvdi0D3mSZjQA0wt7REQsY+iWF0XbfFzyal8SLRxuteD+Du4h4Z/flbqaBHibAQtZmQtcZaAZSMwtTylaR/4vaw1ju5YhWG10pwwAqghmp2FeHO2+t11WqyM80W0m7vAOhsM1kD7CGz8L57Jsq6bitZC/GcWgLf1H6KuHT92cTDAFy/BgXMXm0OCpgV50Bo9kK3BqiBboabQMMU/WoL5im4jToeq/AIgXsiRx5KKCjcwPEsiAv/BQMu9EwyDHXd/3kqCOSzDk6t5/YglQKKeJwq+PNRmJI8kwSTaj1HZy5AhSHqnXkIvU9mMUwEw4Q5wTM57LUtkg8QPw/cdcBJ+PhvKJ0Gj80nGq6JXrg6/XFiX97GXIBpyqTieKpKViOl+WEhWXMaUavvvdIZ8Giy5+Lh3bwKm/t+Be3JazMfxc1tldY26rastiHcsQevTG9pw0znovkAcRWHzSDKnZtaOJLSfMFLB5RqtRBS4LbCurqLCy0YPkU3C0IIPEimMqR2ei7ZX2+KQdRi/WahNT/GmfOD4Vyzhx/66pcjp85dUvcmp6J8+txldXh07PPskdkS+V6EbD0vTOKlB0x9B/O6BS8ULly9PgE6x4kDPR/XX5pyYKj8xcCucsUmkNUQE0JvKKm2VioVK5HRE7UKOHbi6B94RzP+93jtpC0vWgXUF0hr3ipuw8uadwd3jXxoA9IK4Pah8t6BneV9GgjD28Svw1mlxFobgFbeFTz13cKbth93fDryp2CEq0a4hTA+aAPQ/ESJFDdvXLzzzrqNjlTqOP6uDeFf0uhvJ0ZP2QD8D6ZzU6u8YIbBAAAAAElFTkSuQmCC';
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Enum for Vernier godirect protocol.
|
|
26
|
+
* @readonly
|
|
27
|
+
* @enum {string}
|
|
28
|
+
*/
|
|
29
|
+
const BLEUUID = {
|
|
30
|
+
service: 'd91714ef-28b9-4f91-ba16-f0d9a604f112',
|
|
31
|
+
commandChar: 'f4bf14a6-c7d5-4b6d-8aa8-df1a7c83adcb',
|
|
32
|
+
responseChar: 'b41e6675-a329-40e0-aa01-44d2f444babe'
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* A time interval to wait (in milliseconds) before reporting to the BLE socket
|
|
37
|
+
* that data has stopped coming from the peripheral.
|
|
38
|
+
*/
|
|
39
|
+
const BLETimeout = 4500;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* A string to report to the BLE socket when the GdxFor has stopped receiving data.
|
|
43
|
+
* @type {string}
|
|
44
|
+
*/
|
|
45
|
+
const BLEDataStoppedError = 'Force and Acceleration extension stopped receiving data';
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Sensor ID numbers for the GDX-FOR.
|
|
49
|
+
*/
|
|
50
|
+
const GDXFOR_SENSOR = {
|
|
51
|
+
FORCE: 1,
|
|
52
|
+
ACCELERATION_X: 2,
|
|
53
|
+
ACCELERATION_Y: 3,
|
|
54
|
+
ACCELERATION_Z: 4,
|
|
55
|
+
SPIN_SPEED_X: 5,
|
|
56
|
+
SPIN_SPEED_Y: 6,
|
|
57
|
+
SPIN_SPEED_Z: 7
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* The update rate, in milliseconds, for sensor data input from the peripheral.
|
|
62
|
+
*/
|
|
63
|
+
const GDXFOR_UPDATE_RATE = 80;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Threshold for pushing and pulling force, for the whenForcePushedOrPulled hat block.
|
|
67
|
+
* @type {number}
|
|
68
|
+
*/
|
|
69
|
+
const FORCE_THRESHOLD = 5;
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Threshold for acceleration magnitude, for the "shaken" gesture.
|
|
73
|
+
* @type {number}
|
|
74
|
+
*/
|
|
75
|
+
const SHAKEN_THRESHOLD = 30;
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Threshold for acceleration magnitude, to check if we are facing up.
|
|
79
|
+
* @type {number}
|
|
80
|
+
*/
|
|
81
|
+
const FACING_THRESHOLD = 9;
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* An offset for the facing threshold, used to check that we are no longer facing up.
|
|
85
|
+
* @type {number}
|
|
86
|
+
*/
|
|
87
|
+
const FACING_THRESHOLD_OFFSET = 5;
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Threshold for acceleration magnitude, below which we are in freefall.
|
|
91
|
+
* @type {number}
|
|
92
|
+
*/
|
|
93
|
+
const FREEFALL_THRESHOLD = 0.5;
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Factor used to account for influence of rotation during freefall.
|
|
97
|
+
* @type {number}
|
|
98
|
+
*/
|
|
99
|
+
const FREEFALL_ROTATION_FACTOR = 0.3;
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Threshold in degrees for reporting that the sensor is tilted.
|
|
103
|
+
* @type {number}
|
|
104
|
+
*/
|
|
105
|
+
const TILT_THRESHOLD = 15;
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Acceleration due to gravity, in m/s^2.
|
|
109
|
+
* @type {number}
|
|
110
|
+
*/
|
|
111
|
+
const GRAVITY = 9.8;
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Manage communication with a GDX-FOR peripheral over a Scratch Link client socket.
|
|
115
|
+
*/
|
|
116
|
+
class GdxFor {
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Construct a GDX-FOR communication object.
|
|
120
|
+
* @param {Runtime} runtime - the Scratch 3.0 runtime
|
|
121
|
+
* @param {string} extensionId - the id of the extension
|
|
122
|
+
*/
|
|
123
|
+
constructor (runtime, extensionId) {
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* The Scratch 3.0 runtime used to trigger the green flag button.
|
|
127
|
+
* @type {Runtime}
|
|
128
|
+
* @private
|
|
129
|
+
*/
|
|
130
|
+
this._runtime = runtime;
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* The BluetoothLowEnergy connection socket for reading/writing peripheral data.
|
|
134
|
+
* @type {BLE}
|
|
135
|
+
* @private
|
|
136
|
+
*/
|
|
137
|
+
this._ble = null;
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* An @vernier/godirect Device
|
|
141
|
+
* @type {Device}
|
|
142
|
+
* @private
|
|
143
|
+
*/
|
|
144
|
+
this._device = null;
|
|
145
|
+
|
|
146
|
+
this._runtime.registerPeripheralExtension(extensionId, this);
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* The id of the extension this peripheral belongs to.
|
|
150
|
+
*/
|
|
151
|
+
this._extensionId = extensionId;
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* The most recently received value for each sensor.
|
|
155
|
+
* @type {Object.<string, number>}
|
|
156
|
+
* @private
|
|
157
|
+
*/
|
|
158
|
+
this._sensors = {
|
|
159
|
+
force: 0,
|
|
160
|
+
accelerationX: 0,
|
|
161
|
+
accelerationY: 0,
|
|
162
|
+
accelerationZ: 0,
|
|
163
|
+
spinSpeedX: 0,
|
|
164
|
+
spinSpeedY: 0,
|
|
165
|
+
spinSpeedZ: 0
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Interval ID for data reading timeout.
|
|
170
|
+
* @type {number}
|
|
171
|
+
* @private
|
|
172
|
+
*/
|
|
173
|
+
this._timeoutID = null;
|
|
174
|
+
|
|
175
|
+
this.reset = this.reset.bind(this);
|
|
176
|
+
this._onConnect = this._onConnect.bind(this);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Called by the runtime when user wants to scan for a peripheral.
|
|
182
|
+
*/
|
|
183
|
+
scan () {
|
|
184
|
+
if (this._ble) {
|
|
185
|
+
this._ble.disconnect();
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
this._ble = new BLE(this._runtime, this._extensionId, {
|
|
189
|
+
filters: [
|
|
190
|
+
{namePrefix: 'GDX-FOR'}
|
|
191
|
+
],
|
|
192
|
+
optionalServices: [
|
|
193
|
+
BLEUUID.service
|
|
194
|
+
]
|
|
195
|
+
}, this._onConnect, this.reset);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Called by the runtime when user wants to connect to a certain peripheral.
|
|
200
|
+
* @param {number} id - the id of the peripheral to connect to.
|
|
201
|
+
*/
|
|
202
|
+
connect (id) {
|
|
203
|
+
if (this._ble) {
|
|
204
|
+
this._ble.connectPeripheral(id);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Called by the runtime when a user exits the connection popup.
|
|
210
|
+
* Disconnect from the GDX FOR.
|
|
211
|
+
*/
|
|
212
|
+
disconnect () {
|
|
213
|
+
if (this._ble) {
|
|
214
|
+
this._ble.disconnect();
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
this.reset();
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Reset all the state and timeout/interval ids.
|
|
222
|
+
*/
|
|
223
|
+
reset () {
|
|
224
|
+
this._sensors = {
|
|
225
|
+
force: 0,
|
|
226
|
+
accelerationX: 0,
|
|
227
|
+
accelerationY: 0,
|
|
228
|
+
accelerationZ: 0,
|
|
229
|
+
spinSpeedX: 0,
|
|
230
|
+
spinSpeedY: 0,
|
|
231
|
+
spinSpeedZ: 0
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
if (this._timeoutID) {
|
|
235
|
+
window.clearInterval(this._timeoutID);
|
|
236
|
+
this._timeoutID = null;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Return true if connected to the goforce device.
|
|
242
|
+
* @return {boolean} - whether the goforce is connected.
|
|
243
|
+
*/
|
|
244
|
+
isConnected () {
|
|
245
|
+
let connected = false;
|
|
246
|
+
if (this._ble) {
|
|
247
|
+
connected = this._ble.isConnected();
|
|
248
|
+
}
|
|
249
|
+
return connected;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Starts reading data from peripheral after BLE has connected to it.
|
|
254
|
+
* @private
|
|
255
|
+
*/
|
|
256
|
+
_onConnect () {
|
|
257
|
+
const adapter = new ScratchLinkDeviceAdapter(this._ble, BLEUUID);
|
|
258
|
+
godirect.createDevice(adapter, {open: true, startMeasurements: false}).then(device => {
|
|
259
|
+
// Setup device
|
|
260
|
+
this._device = device;
|
|
261
|
+
this._device.keepValues = false; // todo: possibly remove after updating Vernier godirect module
|
|
262
|
+
|
|
263
|
+
// Enable sensors
|
|
264
|
+
this._device.sensors.forEach(sensor => {
|
|
265
|
+
sensor.setEnabled(true);
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
// Set sensor value-update behavior
|
|
269
|
+
this._device.on('measurements-started', () => {
|
|
270
|
+
const enabledSensors = this._device.sensors.filter(s => s.enabled);
|
|
271
|
+
enabledSensors.forEach(sensor => {
|
|
272
|
+
sensor.on('value-changed', s => {
|
|
273
|
+
this._onSensorValueChanged(s);
|
|
274
|
+
});
|
|
275
|
+
});
|
|
276
|
+
this._timeoutID = window.setInterval(
|
|
277
|
+
() => this._ble.handleDisconnectError(BLEDataStoppedError),
|
|
278
|
+
BLETimeout
|
|
279
|
+
);
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
// Start device
|
|
283
|
+
this._device.start(GDXFOR_UPDATE_RATE);
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Handler for sensor value changes from the goforce device.
|
|
289
|
+
* @param {object} sensor - goforce device sensor whose value has changed
|
|
290
|
+
* @private
|
|
291
|
+
*/
|
|
292
|
+
_onSensorValueChanged (sensor) {
|
|
293
|
+
switch (sensor.number) {
|
|
294
|
+
case GDXFOR_SENSOR.FORCE:
|
|
295
|
+
// Normalize the force, which can be measured between -50 and 50 N,
|
|
296
|
+
// to be a value between -100 and 100.
|
|
297
|
+
this._sensors.force = MathUtil.clamp(sensor.value * 2, -100, 100);
|
|
298
|
+
break;
|
|
299
|
+
case GDXFOR_SENSOR.ACCELERATION_X:
|
|
300
|
+
this._sensors.accelerationX = sensor.value;
|
|
301
|
+
break;
|
|
302
|
+
case GDXFOR_SENSOR.ACCELERATION_Y:
|
|
303
|
+
this._sensors.accelerationY = sensor.value;
|
|
304
|
+
break;
|
|
305
|
+
case GDXFOR_SENSOR.ACCELERATION_Z:
|
|
306
|
+
this._sensors.accelerationZ = sensor.value;
|
|
307
|
+
break;
|
|
308
|
+
case GDXFOR_SENSOR.SPIN_SPEED_X:
|
|
309
|
+
this._sensors.spinSpeedX = this._spinSpeedFromGyro(sensor.value);
|
|
310
|
+
break;
|
|
311
|
+
case GDXFOR_SENSOR.SPIN_SPEED_Y:
|
|
312
|
+
this._sensors.spinSpeedY = this._spinSpeedFromGyro(sensor.value);
|
|
313
|
+
break;
|
|
314
|
+
case GDXFOR_SENSOR.SPIN_SPEED_Z:
|
|
315
|
+
this._sensors.spinSpeedZ = this._spinSpeedFromGyro(sensor.value);
|
|
316
|
+
break;
|
|
317
|
+
}
|
|
318
|
+
// cancel disconnect timeout and start a new one
|
|
319
|
+
window.clearInterval(this._timeoutID);
|
|
320
|
+
this._timeoutID = window.setInterval(
|
|
321
|
+
() => this._ble.handleDisconnectError(BLEDataStoppedError),
|
|
322
|
+
BLETimeout
|
|
323
|
+
);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
_spinSpeedFromGyro (val) {
|
|
327
|
+
const framesPerSec = 1000 / this._runtime.currentStepTime;
|
|
328
|
+
val = MathUtil.radToDeg(val);
|
|
329
|
+
val = val / framesPerSec; // convert to from degrees per sec to degrees per frame
|
|
330
|
+
val = val * -1;
|
|
331
|
+
return val;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
getForce () {
|
|
335
|
+
return this._sensors.force;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
getTiltFrontBack (back = false) {
|
|
339
|
+
const x = this.getAccelerationX();
|
|
340
|
+
const y = this.getAccelerationY();
|
|
341
|
+
const z = this.getAccelerationZ();
|
|
342
|
+
|
|
343
|
+
// Compute the yz unit vector
|
|
344
|
+
const y2 = y * y;
|
|
345
|
+
const z2 = z * z;
|
|
346
|
+
let value = y2 + z2;
|
|
347
|
+
value = Math.sqrt(value);
|
|
348
|
+
|
|
349
|
+
// For sufficiently small zy vector values we are essentially at 90 degrees.
|
|
350
|
+
// The following snaps to 90 and avoids divide-by-zero errors.
|
|
351
|
+
// The snap factor was derived through observation -- just enough to
|
|
352
|
+
// still allow single degree steps up to 90 (..., 87, 88, 89, 90).
|
|
353
|
+
if (value < 0.35) {
|
|
354
|
+
value = (x < 0) ? 90 : -90;
|
|
355
|
+
} else {
|
|
356
|
+
value = x / value;
|
|
357
|
+
value = Math.atan(value);
|
|
358
|
+
value = MathUtil.radToDeg(value) * -1;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// Back is the inverse of front
|
|
362
|
+
if (back) value *= -1;
|
|
363
|
+
|
|
364
|
+
return value;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
getTiltLeftRight (right = false) {
|
|
368
|
+
const x = this.getAccelerationX();
|
|
369
|
+
const y = this.getAccelerationY();
|
|
370
|
+
const z = this.getAccelerationZ();
|
|
371
|
+
|
|
372
|
+
// Compute the yz unit vector
|
|
373
|
+
const x2 = x * x;
|
|
374
|
+
const z2 = z * z;
|
|
375
|
+
let value = x2 + z2;
|
|
376
|
+
value = Math.sqrt(value);
|
|
377
|
+
|
|
378
|
+
// For sufficiently small zy vector values we are essentially at 90 degrees.
|
|
379
|
+
// The following snaps to 90 and avoids divide-by-zero errors.
|
|
380
|
+
// The snap factor was derived through observation -- just enough to
|
|
381
|
+
// still allow single degree steps up to 90 (..., 87, 88, 89, 90).
|
|
382
|
+
if (value < 0.35) {
|
|
383
|
+
value = (y < 0) ? 90 : -90;
|
|
384
|
+
} else {
|
|
385
|
+
value = y / value;
|
|
386
|
+
value = Math.atan(value);
|
|
387
|
+
value = MathUtil.radToDeg(value) * -1;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// Right is the inverse of left
|
|
391
|
+
if (right) value *= -1;
|
|
392
|
+
|
|
393
|
+
return value;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
getAccelerationX () {
|
|
397
|
+
return this._sensors.accelerationX;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
getAccelerationY () {
|
|
401
|
+
return this._sensors.accelerationY;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
getAccelerationZ () {
|
|
405
|
+
return this._sensors.accelerationZ;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
getSpinSpeedX () {
|
|
409
|
+
return this._sensors.spinSpeedX;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
getSpinSpeedY () {
|
|
413
|
+
return this._sensors.spinSpeedY;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
getSpinSpeedZ () {
|
|
417
|
+
return this._sensors.spinSpeedZ;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* Enum for pushed and pulled menu options.
|
|
423
|
+
* @readonly
|
|
424
|
+
* @enum {string}
|
|
425
|
+
*/
|
|
426
|
+
const PushPullValues = {
|
|
427
|
+
PUSHED: 'pushed',
|
|
428
|
+
PULLED: 'pulled'
|
|
429
|
+
};
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* Enum for motion gesture menu options.
|
|
433
|
+
* @readonly
|
|
434
|
+
* @enum {string}
|
|
435
|
+
*/
|
|
436
|
+
const GestureValues = {
|
|
437
|
+
SHAKEN: 'shaken',
|
|
438
|
+
STARTED_FALLING: 'started falling',
|
|
439
|
+
TURNED_FACE_UP: 'turned face up',
|
|
440
|
+
TURNED_FACE_DOWN: 'turned face down'
|
|
441
|
+
};
|
|
442
|
+
|
|
443
|
+
/**
|
|
444
|
+
* Enum for tilt axis menu options.
|
|
445
|
+
* @readonly
|
|
446
|
+
* @enum {string}
|
|
447
|
+
*/
|
|
448
|
+
const TiltAxisValues = {
|
|
449
|
+
FRONT: 'front',
|
|
450
|
+
BACK: 'back',
|
|
451
|
+
LEFT: 'left',
|
|
452
|
+
RIGHT: 'right',
|
|
453
|
+
ANY: 'any'
|
|
454
|
+
};
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* Enum for axis menu options.
|
|
458
|
+
* @readonly
|
|
459
|
+
* @enum {string}
|
|
460
|
+
*/
|
|
461
|
+
const AxisValues = {
|
|
462
|
+
X: 'x',
|
|
463
|
+
Y: 'y',
|
|
464
|
+
Z: 'z'
|
|
465
|
+
};
|
|
466
|
+
|
|
467
|
+
/**
|
|
468
|
+
* Scratch 3.0 blocks to interact with a GDX-FOR peripheral.
|
|
469
|
+
*/
|
|
470
|
+
class Scratch3GdxForBlocks {
|
|
471
|
+
|
|
472
|
+
/**
|
|
473
|
+
* @return {string} - the name of this extension.
|
|
474
|
+
*/
|
|
475
|
+
static get EXTENSION_NAME () {
|
|
476
|
+
return 'Force and Acceleration';
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
/**
|
|
480
|
+
* @return {string} - the ID of this extension.
|
|
481
|
+
*/
|
|
482
|
+
static get EXTENSION_ID () {
|
|
483
|
+
return 'gdxfor';
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
get AXIS_MENU () {
|
|
487
|
+
return [
|
|
488
|
+
{
|
|
489
|
+
text: 'x',
|
|
490
|
+
value: AxisValues.X
|
|
491
|
+
},
|
|
492
|
+
{
|
|
493
|
+
text: 'y',
|
|
494
|
+
value: AxisValues.Y
|
|
495
|
+
},
|
|
496
|
+
{
|
|
497
|
+
text: 'z',
|
|
498
|
+
value: AxisValues.Z
|
|
499
|
+
}
|
|
500
|
+
];
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
get TILT_MENU () {
|
|
504
|
+
return [
|
|
505
|
+
{
|
|
506
|
+
text: formatMessage({
|
|
507
|
+
id: 'gdxfor.tiltDirectionMenu.front',
|
|
508
|
+
default: 'front',
|
|
509
|
+
description: 'label for front element in tilt direction picker for gdxfor extension'
|
|
510
|
+
}),
|
|
511
|
+
value: TiltAxisValues.FRONT
|
|
512
|
+
},
|
|
513
|
+
{
|
|
514
|
+
text: formatMessage({
|
|
515
|
+
id: 'gdxfor.tiltDirectionMenu.back',
|
|
516
|
+
default: 'back',
|
|
517
|
+
description: 'label for back element in tilt direction picker for gdxfor extension'
|
|
518
|
+
}),
|
|
519
|
+
value: TiltAxisValues.BACK
|
|
520
|
+
},
|
|
521
|
+
{
|
|
522
|
+
text: formatMessage({
|
|
523
|
+
id: 'gdxfor.tiltDirectionMenu.left',
|
|
524
|
+
default: 'left',
|
|
525
|
+
description: 'label for left element in tilt direction picker for gdxfor extension'
|
|
526
|
+
}),
|
|
527
|
+
value: TiltAxisValues.LEFT
|
|
528
|
+
},
|
|
529
|
+
{
|
|
530
|
+
text: formatMessage({
|
|
531
|
+
id: 'gdxfor.tiltDirectionMenu.right',
|
|
532
|
+
default: 'right',
|
|
533
|
+
description: 'label for right element in tilt direction picker for gdxfor extension'
|
|
534
|
+
}),
|
|
535
|
+
value: TiltAxisValues.RIGHT
|
|
536
|
+
}
|
|
537
|
+
];
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
get TILT_MENU_ANY () {
|
|
541
|
+
return [
|
|
542
|
+
...this.TILT_MENU,
|
|
543
|
+
{
|
|
544
|
+
text: formatMessage({
|
|
545
|
+
id: 'gdxfor.tiltDirectionMenu.any',
|
|
546
|
+
default: 'any',
|
|
547
|
+
description: 'label for any direction element in tilt direction picker for gdxfor extension'
|
|
548
|
+
}),
|
|
549
|
+
value: TiltAxisValues.ANY
|
|
550
|
+
}
|
|
551
|
+
];
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
get PUSH_PULL_MENU () {
|
|
555
|
+
return [
|
|
556
|
+
{
|
|
557
|
+
text: formatMessage({
|
|
558
|
+
id: 'gdxfor.pushed',
|
|
559
|
+
default: 'pushed',
|
|
560
|
+
description: 'the force sensor was pushed inward'
|
|
561
|
+
}),
|
|
562
|
+
value: PushPullValues.PUSHED
|
|
563
|
+
},
|
|
564
|
+
{
|
|
565
|
+
text: formatMessage({
|
|
566
|
+
id: 'gdxfor.pulled',
|
|
567
|
+
default: 'pulled',
|
|
568
|
+
description: 'the force sensor was pulled outward'
|
|
569
|
+
}),
|
|
570
|
+
value: PushPullValues.PULLED
|
|
571
|
+
}
|
|
572
|
+
];
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
get GESTURE_MENU () {
|
|
576
|
+
return [
|
|
577
|
+
{
|
|
578
|
+
text: formatMessage({
|
|
579
|
+
id: 'gdxfor.shaken',
|
|
580
|
+
default: 'shaken',
|
|
581
|
+
description: 'the sensor was shaken'
|
|
582
|
+
}),
|
|
583
|
+
value: GestureValues.SHAKEN
|
|
584
|
+
},
|
|
585
|
+
{
|
|
586
|
+
text: formatMessage({
|
|
587
|
+
id: 'gdxfor.startedFalling',
|
|
588
|
+
default: 'started falling',
|
|
589
|
+
description: 'the sensor started free falling'
|
|
590
|
+
}),
|
|
591
|
+
value: GestureValues.STARTED_FALLING
|
|
592
|
+
},
|
|
593
|
+
{
|
|
594
|
+
text: formatMessage({
|
|
595
|
+
id: 'gdxfor.turnedFaceUp',
|
|
596
|
+
default: 'turned face up',
|
|
597
|
+
description: 'the sensor was turned to face up'
|
|
598
|
+
}),
|
|
599
|
+
value: GestureValues.TURNED_FACE_UP
|
|
600
|
+
},
|
|
601
|
+
{
|
|
602
|
+
text: formatMessage({
|
|
603
|
+
id: 'gdxfor.turnedFaceDown',
|
|
604
|
+
default: 'turned face down',
|
|
605
|
+
description: 'the sensor was turned to face down'
|
|
606
|
+
}),
|
|
607
|
+
value: GestureValues.TURNED_FACE_DOWN
|
|
608
|
+
}
|
|
609
|
+
];
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
/**
|
|
613
|
+
* Construct a set of GDX-FOR blocks.
|
|
614
|
+
* @param {Runtime} runtime - the Scratch 3.0 runtime.
|
|
615
|
+
*/
|
|
616
|
+
constructor (runtime) {
|
|
617
|
+
/**
|
|
618
|
+
* The Scratch 3.0 runtime.
|
|
619
|
+
* @type {Runtime}
|
|
620
|
+
*/
|
|
621
|
+
this.runtime = runtime;
|
|
622
|
+
|
|
623
|
+
// Create a new GdxFor peripheral instance
|
|
624
|
+
this._peripheral = new GdxFor(this.runtime, Scratch3GdxForBlocks.EXTENSION_ID);
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
/**
|
|
628
|
+
* @returns {object} metadata for this extension and its blocks.
|
|
629
|
+
*/
|
|
630
|
+
getInfo () {
|
|
631
|
+
return {
|
|
632
|
+
id: Scratch3GdxForBlocks.EXTENSION_ID,
|
|
633
|
+
name: Scratch3GdxForBlocks.EXTENSION_NAME,
|
|
634
|
+
blockIconURI: blockIconURI,
|
|
635
|
+
menuIconURI: menuIconURI,
|
|
636
|
+
showStatusButton: true,
|
|
637
|
+
blocks: [
|
|
638
|
+
{
|
|
639
|
+
opcode: 'whenGesture',
|
|
640
|
+
text: formatMessage({
|
|
641
|
+
id: 'gdxfor.whenGesture',
|
|
642
|
+
default: 'when [GESTURE]',
|
|
643
|
+
description: 'when the sensor detects a gesture'
|
|
644
|
+
}),
|
|
645
|
+
blockType: BlockType.HAT,
|
|
646
|
+
arguments: {
|
|
647
|
+
GESTURE: {
|
|
648
|
+
type: ArgumentType.STRING,
|
|
649
|
+
menu: 'gestureOptions',
|
|
650
|
+
defaultValue: GestureValues.SHAKEN
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
},
|
|
654
|
+
{
|
|
655
|
+
opcode: 'whenForcePushedOrPulled',
|
|
656
|
+
text: formatMessage({
|
|
657
|
+
id: 'gdxfor.whenForcePushedOrPulled',
|
|
658
|
+
default: 'when force sensor [PUSH_PULL]',
|
|
659
|
+
description: 'when the force sensor is pushed or pulled'
|
|
660
|
+
}),
|
|
661
|
+
blockType: BlockType.HAT,
|
|
662
|
+
arguments: {
|
|
663
|
+
PUSH_PULL: {
|
|
664
|
+
type: ArgumentType.STRING,
|
|
665
|
+
menu: 'pushPullOptions',
|
|
666
|
+
defaultValue: PushPullValues.PUSHED
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
},
|
|
670
|
+
{
|
|
671
|
+
opcode: 'getForce',
|
|
672
|
+
text: formatMessage({
|
|
673
|
+
id: 'gdxfor.getForce',
|
|
674
|
+
default: 'force',
|
|
675
|
+
description: 'gets force'
|
|
676
|
+
}),
|
|
677
|
+
blockType: BlockType.REPORTER
|
|
678
|
+
},
|
|
679
|
+
'---',
|
|
680
|
+
{
|
|
681
|
+
opcode: 'whenTilted',
|
|
682
|
+
text: formatMessage({
|
|
683
|
+
id: 'gdxfor.whenTilted',
|
|
684
|
+
default: 'when tilted [TILT]',
|
|
685
|
+
description: 'when the sensor detects tilt'
|
|
686
|
+
}),
|
|
687
|
+
blockType: BlockType.HAT,
|
|
688
|
+
arguments: {
|
|
689
|
+
TILT: {
|
|
690
|
+
type: ArgumentType.STRING,
|
|
691
|
+
menu: 'tiltAnyOptions',
|
|
692
|
+
defaultValue: TiltAxisValues.ANY
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
},
|
|
696
|
+
{
|
|
697
|
+
opcode: 'isTilted',
|
|
698
|
+
text: formatMessage({
|
|
699
|
+
id: 'gdxfor.isTilted',
|
|
700
|
+
default: 'tilted [TILT]?',
|
|
701
|
+
description: 'is the device tilted?'
|
|
702
|
+
}),
|
|
703
|
+
blockType: BlockType.BOOLEAN,
|
|
704
|
+
arguments: {
|
|
705
|
+
TILT: {
|
|
706
|
+
type: ArgumentType.STRING,
|
|
707
|
+
menu: 'tiltAnyOptions',
|
|
708
|
+
defaultValue: TiltAxisValues.ANY
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
},
|
|
712
|
+
{
|
|
713
|
+
opcode: 'getTilt',
|
|
714
|
+
text: formatMessage({
|
|
715
|
+
id: 'gdxfor.getTilt',
|
|
716
|
+
default: 'tilt angle [TILT]',
|
|
717
|
+
description: 'gets tilt'
|
|
718
|
+
}),
|
|
719
|
+
blockType: BlockType.REPORTER,
|
|
720
|
+
arguments: {
|
|
721
|
+
TILT: {
|
|
722
|
+
type: ArgumentType.STRING,
|
|
723
|
+
menu: 'tiltOptions',
|
|
724
|
+
defaultValue: TiltAxisValues.FRONT
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
},
|
|
728
|
+
'---',
|
|
729
|
+
{
|
|
730
|
+
opcode: 'isFreeFalling',
|
|
731
|
+
text: formatMessage({
|
|
732
|
+
id: 'gdxfor.isFreeFalling',
|
|
733
|
+
default: 'falling?',
|
|
734
|
+
description: 'is the device in free fall?'
|
|
735
|
+
}),
|
|
736
|
+
blockType: BlockType.BOOLEAN
|
|
737
|
+
},
|
|
738
|
+
{
|
|
739
|
+
opcode: 'getSpinSpeed',
|
|
740
|
+
text: formatMessage({
|
|
741
|
+
id: 'gdxfor.getSpin',
|
|
742
|
+
default: 'spin speed [DIRECTION]',
|
|
743
|
+
description: 'gets spin speed'
|
|
744
|
+
}),
|
|
745
|
+
blockType: BlockType.REPORTER,
|
|
746
|
+
arguments: {
|
|
747
|
+
DIRECTION: {
|
|
748
|
+
type: ArgumentType.STRING,
|
|
749
|
+
menu: 'axisOptions',
|
|
750
|
+
defaultValue: AxisValues.Z
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
},
|
|
754
|
+
{
|
|
755
|
+
opcode: 'getAcceleration',
|
|
756
|
+
text: formatMessage({
|
|
757
|
+
id: 'gdxfor.getAcceleration',
|
|
758
|
+
default: 'acceleration [DIRECTION]',
|
|
759
|
+
description: 'gets acceleration'
|
|
760
|
+
}),
|
|
761
|
+
blockType: BlockType.REPORTER,
|
|
762
|
+
arguments: {
|
|
763
|
+
DIRECTION: {
|
|
764
|
+
type: ArgumentType.STRING,
|
|
765
|
+
menu: 'axisOptions',
|
|
766
|
+
defaultValue: AxisValues.X
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
],
|
|
771
|
+
menus: {
|
|
772
|
+
pushPullOptions: {
|
|
773
|
+
acceptReporters: true,
|
|
774
|
+
items: this.PUSH_PULL_MENU
|
|
775
|
+
},
|
|
776
|
+
gestureOptions: {
|
|
777
|
+
acceptReporters: true,
|
|
778
|
+
items: this.GESTURE_MENU
|
|
779
|
+
},
|
|
780
|
+
axisOptions: {
|
|
781
|
+
acceptReporters: true,
|
|
782
|
+
items: this.AXIS_MENU
|
|
783
|
+
},
|
|
784
|
+
tiltOptions: {
|
|
785
|
+
acceptReporters: true,
|
|
786
|
+
items: this.TILT_MENU
|
|
787
|
+
},
|
|
788
|
+
tiltAnyOptions: {
|
|
789
|
+
acceptReporters: true,
|
|
790
|
+
items: this.TILT_MENU_ANY
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
};
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
whenForcePushedOrPulled (args) {
|
|
797
|
+
switch (args.PUSH_PULL) {
|
|
798
|
+
case PushPullValues.PUSHED:
|
|
799
|
+
return this._peripheral.getForce() < FORCE_THRESHOLD * -1;
|
|
800
|
+
case PushPullValues.PULLED:
|
|
801
|
+
return this._peripheral.getForce() > FORCE_THRESHOLD;
|
|
802
|
+
default:
|
|
803
|
+
log.warn(`unknown push/pull value in whenForcePushedOrPulled: ${args.PUSH_PULL}`);
|
|
804
|
+
return false;
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
getForce () {
|
|
809
|
+
return Math.round(this._peripheral.getForce());
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
whenGesture (args) {
|
|
813
|
+
switch (args.GESTURE) {
|
|
814
|
+
case GestureValues.SHAKEN:
|
|
815
|
+
return this.gestureMagnitude() > SHAKEN_THRESHOLD;
|
|
816
|
+
case GestureValues.STARTED_FALLING:
|
|
817
|
+
return this.isFreeFalling();
|
|
818
|
+
case GestureValues.TURNED_FACE_UP:
|
|
819
|
+
return this._isFacing(GestureValues.TURNED_FACE_UP);
|
|
820
|
+
case GestureValues.TURNED_FACE_DOWN:
|
|
821
|
+
return this._isFacing(GestureValues.TURNED_FACE_DOWN);
|
|
822
|
+
default:
|
|
823
|
+
log.warn(`unknown gesture value in whenGesture: ${args.GESTURE}`);
|
|
824
|
+
return false;
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
_isFacing (direction) {
|
|
829
|
+
if (typeof this._facingUp === 'undefined') {
|
|
830
|
+
this._facingUp = false;
|
|
831
|
+
}
|
|
832
|
+
if (typeof this._facingDown === 'undefined') {
|
|
833
|
+
this._facingDown = false;
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
// If the sensor is already facing up or down, reduce the threshold.
|
|
837
|
+
// This prevents small fluctations in acceleration while it is being
|
|
838
|
+
// turned from causing the hat block to trigger multiple times.
|
|
839
|
+
let threshold = FACING_THRESHOLD;
|
|
840
|
+
if (this._facingUp || this._facingDown) {
|
|
841
|
+
threshold -= FACING_THRESHOLD_OFFSET;
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
this._facingUp = this._peripheral.getAccelerationZ() > threshold;
|
|
845
|
+
this._facingDown = this._peripheral.getAccelerationZ() < threshold * -1;
|
|
846
|
+
|
|
847
|
+
switch (direction) {
|
|
848
|
+
case GestureValues.TURNED_FACE_UP:
|
|
849
|
+
return this._facingUp;
|
|
850
|
+
case GestureValues.TURNED_FACE_DOWN:
|
|
851
|
+
return this._facingDown;
|
|
852
|
+
default:
|
|
853
|
+
return false;
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
whenTilted (args) {
|
|
858
|
+
return this._isTilted(args.TILT);
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
isTilted (args) {
|
|
862
|
+
return this._isTilted(args.TILT);
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
getTilt (args) {
|
|
866
|
+
return this._getTiltAngle(args.TILT);
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
_isTilted (direction) {
|
|
870
|
+
switch (direction) {
|
|
871
|
+
case TiltAxisValues.ANY:
|
|
872
|
+
return this._getTiltAngle(TiltAxisValues.FRONT) > TILT_THRESHOLD ||
|
|
873
|
+
this._getTiltAngle(TiltAxisValues.BACK) > TILT_THRESHOLD ||
|
|
874
|
+
this._getTiltAngle(TiltAxisValues.LEFT) > TILT_THRESHOLD ||
|
|
875
|
+
this._getTiltAngle(TiltAxisValues.RIGHT) > TILT_THRESHOLD;
|
|
876
|
+
default:
|
|
877
|
+
return this._getTiltAngle(direction) > TILT_THRESHOLD;
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
_getTiltAngle (direction) {
|
|
882
|
+
// Tilt values are calculated using acceleration due to gravity,
|
|
883
|
+
// so we need to return 0 when the peripheral is not connected.
|
|
884
|
+
if (!this._peripheral.isConnected()) {
|
|
885
|
+
return 0;
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
switch (direction) {
|
|
889
|
+
case TiltAxisValues.FRONT:
|
|
890
|
+
return Math.round(this._peripheral.getTiltFrontBack(true));
|
|
891
|
+
case TiltAxisValues.BACK:
|
|
892
|
+
return Math.round(this._peripheral.getTiltFrontBack(false));
|
|
893
|
+
case TiltAxisValues.LEFT:
|
|
894
|
+
return Math.round(this._peripheral.getTiltLeftRight(true));
|
|
895
|
+
case TiltAxisValues.RIGHT:
|
|
896
|
+
return Math.round(this._peripheral.getTiltLeftRight(false));
|
|
897
|
+
default:
|
|
898
|
+
log.warn(`Unknown direction in getTilt: ${direction}`);
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
getSpinSpeed (args) {
|
|
903
|
+
switch (args.DIRECTION) {
|
|
904
|
+
case AxisValues.X:
|
|
905
|
+
return Math.round(this._peripheral.getSpinSpeedX());
|
|
906
|
+
case AxisValues.Y:
|
|
907
|
+
return Math.round(this._peripheral.getSpinSpeedY());
|
|
908
|
+
case AxisValues.Z:
|
|
909
|
+
return Math.round(this._peripheral.getSpinSpeedZ());
|
|
910
|
+
default:
|
|
911
|
+
log.warn(`Unknown direction in getSpinSpeed: ${args.DIRECTION}`);
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
getAcceleration (args) {
|
|
916
|
+
switch (args.DIRECTION) {
|
|
917
|
+
case AxisValues.X:
|
|
918
|
+
return Math.round(this._peripheral.getAccelerationX());
|
|
919
|
+
case AxisValues.Y:
|
|
920
|
+
return Math.round(this._peripheral.getAccelerationY());
|
|
921
|
+
case AxisValues.Z:
|
|
922
|
+
return Math.round(this._peripheral.getAccelerationZ());
|
|
923
|
+
default:
|
|
924
|
+
log.warn(`Unknown direction in getAcceleration: ${args.DIRECTION}`);
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
/**
|
|
929
|
+
* @param {number} x - x axis vector
|
|
930
|
+
* @param {number} y - y axis vector
|
|
931
|
+
* @param {number} z - z axis vector
|
|
932
|
+
* @return {number} - the magnitude of a three dimension vector.
|
|
933
|
+
*/
|
|
934
|
+
magnitude (x, y, z) {
|
|
935
|
+
return Math.sqrt((x * x) + (y * y) + (z * z));
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
accelMagnitude () {
|
|
939
|
+
return this.magnitude(
|
|
940
|
+
this._peripheral.getAccelerationX(),
|
|
941
|
+
this._peripheral.getAccelerationY(),
|
|
942
|
+
this._peripheral.getAccelerationZ()
|
|
943
|
+
);
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
gestureMagnitude () {
|
|
947
|
+
return this.accelMagnitude() - GRAVITY;
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
spinMagnitude () {
|
|
951
|
+
return this.magnitude(
|
|
952
|
+
this._peripheral.getSpinSpeedX(),
|
|
953
|
+
this._peripheral.getSpinSpeedY(),
|
|
954
|
+
this._peripheral.getSpinSpeedZ()
|
|
955
|
+
);
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
isFreeFalling () {
|
|
959
|
+
// When the peripheral is not connected, the acceleration magnitude
|
|
960
|
+
// is 0 instead of ~9.8, which ends up calculating as a positive
|
|
961
|
+
// free fall; so we need to return 'false' here to prevent returning 'true'.
|
|
962
|
+
if (!this._peripheral.isConnected()) {
|
|
963
|
+
return false;
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
const accelMag = this.accelMagnitude();
|
|
967
|
+
const spinMag = this.spinMagnitude();
|
|
968
|
+
|
|
969
|
+
// We want to account for rotation during freefall,
|
|
970
|
+
// so we tack on a an estimated "rotational effect"
|
|
971
|
+
// The FREEFALL_ROTATION_FACTOR const is used to both scale the
|
|
972
|
+
// gyro measurements and convert them to radians/second.
|
|
973
|
+
// So, we compare our accel magnitude against:
|
|
974
|
+
// FREEFALL_THRESHOLD + (some_scaled_magnitude_of_rotation).
|
|
975
|
+
const ffThresh = FREEFALL_THRESHOLD + (FREEFALL_ROTATION_FACTOR * spinMag);
|
|
976
|
+
|
|
977
|
+
return accelMag < ffThresh;
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
module.exports = Scratch3GdxForBlocks;
|