@scratch/scratch-vm 11.0.0-UEPR-176

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (178) hide show
  1. package/LICENSE +12 -0
  2. package/README.md +120 -0
  3. package/dist/node/extension-worker.js +2 -0
  4. package/dist/node/extension-worker.js.map +1 -0
  5. package/dist/node/scratch-vm.js +3 -0
  6. package/dist/node/scratch-vm.js.LICENSE.txt +205 -0
  7. package/dist/node/scratch-vm.js.map +1 -0
  8. package/dist/web/extension-worker.js +2 -0
  9. package/dist/web/extension-worker.js.map +1 -0
  10. package/dist/web/scratch-vm.js +3 -0
  11. package/dist/web/scratch-vm.js.LICENSE.txt +42 -0
  12. package/dist/web/scratch-vm.js.map +1 -0
  13. package/package.json +108 -0
  14. package/src/.eslintrc.js +7 -0
  15. package/src/blocks/scratch3_control.js +206 -0
  16. package/src/blocks/scratch3_core_example.js +69 -0
  17. package/src/blocks/scratch3_data.js +257 -0
  18. package/src/blocks/scratch3_event.js +137 -0
  19. package/src/blocks/scratch3_looks.js +612 -0
  20. package/src/blocks/scratch3_motion.js +288 -0
  21. package/src/blocks/scratch3_operators.js +154 -0
  22. package/src/blocks/scratch3_procedures.js +79 -0
  23. package/src/blocks/scratch3_sensing.js +336 -0
  24. package/src/blocks/scratch3_sound.js +350 -0
  25. package/src/dispatch/central-dispatch.js +143 -0
  26. package/src/dispatch/shared-dispatch.js +235 -0
  27. package/src/dispatch/worker-dispatch.js +110 -0
  28. package/src/engine/adapter.js +176 -0
  29. package/src/engine/block-utility.js +242 -0
  30. package/src/engine/blocks-execute-cache.js +19 -0
  31. package/src/engine/blocks-runtime-cache.js +78 -0
  32. package/src/engine/blocks.js +1299 -0
  33. package/src/engine/comment.js +56 -0
  34. package/src/engine/execute.js +580 -0
  35. package/src/engine/monitor-record.js +23 -0
  36. package/src/engine/mutation-adapter.js +48 -0
  37. package/src/engine/profiler.js +390 -0
  38. package/src/engine/runtime.js +2686 -0
  39. package/src/engine/scratch-blocks-constants.js +27 -0
  40. package/src/engine/sequencer.js +361 -0
  41. package/src/engine/stage-layering.js +29 -0
  42. package/src/engine/target.js +799 -0
  43. package/src/engine/thread.js +404 -0
  44. package/src/engine/variable.js +70 -0
  45. package/src/extension-support/argument-type.js +47 -0
  46. package/src/extension-support/block-type.js +50 -0
  47. package/src/extension-support/define-messages.js +18 -0
  48. package/src/extension-support/extension-manager.js +440 -0
  49. package/src/extension-support/extension-metadata.js +64 -0
  50. package/src/extension-support/extension-worker.js +59 -0
  51. package/src/extension-support/reporter-scope.js +18 -0
  52. package/src/extension-support/target-type.js +17 -0
  53. package/src/extensions/scratch3_boost/index.js +2113 -0
  54. package/src/extensions/scratch3_ev3/index.js +1355 -0
  55. package/src/extensions/scratch3_gdx_for/index.js +981 -0
  56. package/src/extensions/scratch3_gdx_for/scratch-link-device-adapter.js +44 -0
  57. package/src/extensions/scratch3_makeymakey/index.js +396 -0
  58. package/src/extensions/scratch3_microbit/index.js +984 -0
  59. package/src/extensions/scratch3_music/assets/drums/1-snare.mp3 +0 -0
  60. package/src/extensions/scratch3_music/assets/drums/10-wood-block.mp3 +0 -0
  61. package/src/extensions/scratch3_music/assets/drums/11-cowbell.mp3 +0 -0
  62. package/src/extensions/scratch3_music/assets/drums/12-triangle.mp3 +0 -0
  63. package/src/extensions/scratch3_music/assets/drums/13-bongo.mp3 +0 -0
  64. package/src/extensions/scratch3_music/assets/drums/14-conga.mp3 +0 -0
  65. package/src/extensions/scratch3_music/assets/drums/15-cabasa.mp3 +0 -0
  66. package/src/extensions/scratch3_music/assets/drums/16-guiro.mp3 +0 -0
  67. package/src/extensions/scratch3_music/assets/drums/17-vibraslap.mp3 +0 -0
  68. package/src/extensions/scratch3_music/assets/drums/18-cuica.mp3 +0 -0
  69. package/src/extensions/scratch3_music/assets/drums/2-bass-drum.mp3 +0 -0
  70. package/src/extensions/scratch3_music/assets/drums/3-side-stick.mp3 +0 -0
  71. package/src/extensions/scratch3_music/assets/drums/4-crash-cymbal.mp3 +0 -0
  72. package/src/extensions/scratch3_music/assets/drums/5-open-hi-hat.mp3 +0 -0
  73. package/src/extensions/scratch3_music/assets/drums/6-closed-hi-hat.mp3 +0 -0
  74. package/src/extensions/scratch3_music/assets/drums/7-tambourine.mp3 +0 -0
  75. package/src/extensions/scratch3_music/assets/drums/8-hand-clap.mp3 +0 -0
  76. package/src/extensions/scratch3_music/assets/drums/9-claves.mp3 +0 -0
  77. package/src/extensions/scratch3_music/assets/instruments/1-piano/108.mp3 +0 -0
  78. package/src/extensions/scratch3_music/assets/instruments/1-piano/24.mp3 +0 -0
  79. package/src/extensions/scratch3_music/assets/instruments/1-piano/36.mp3 +0 -0
  80. package/src/extensions/scratch3_music/assets/instruments/1-piano/48.mp3 +0 -0
  81. package/src/extensions/scratch3_music/assets/instruments/1-piano/60.mp3 +0 -0
  82. package/src/extensions/scratch3_music/assets/instruments/1-piano/72.mp3 +0 -0
  83. package/src/extensions/scratch3_music/assets/instruments/1-piano/84.mp3 +0 -0
  84. package/src/extensions/scratch3_music/assets/instruments/1-piano/96.mp3 +0 -0
  85. package/src/extensions/scratch3_music/assets/instruments/10-clarinet/48.mp3 +0 -0
  86. package/src/extensions/scratch3_music/assets/instruments/10-clarinet/60.mp3 +0 -0
  87. package/src/extensions/scratch3_music/assets/instruments/11-saxophone/36.mp3 +0 -0
  88. package/src/extensions/scratch3_music/assets/instruments/11-saxophone/60.mp3 +0 -0
  89. package/src/extensions/scratch3_music/assets/instruments/11-saxophone/84.mp3 +0 -0
  90. package/src/extensions/scratch3_music/assets/instruments/12-flute/60.mp3 +0 -0
  91. package/src/extensions/scratch3_music/assets/instruments/12-flute/72.mp3 +0 -0
  92. package/src/extensions/scratch3_music/assets/instruments/13-wooden-flute/60.mp3 +0 -0
  93. package/src/extensions/scratch3_music/assets/instruments/13-wooden-flute/72.mp3 +0 -0
  94. package/src/extensions/scratch3_music/assets/instruments/14-bassoon/36.mp3 +0 -0
  95. package/src/extensions/scratch3_music/assets/instruments/14-bassoon/48.mp3 +0 -0
  96. package/src/extensions/scratch3_music/assets/instruments/14-bassoon/60.mp3 +0 -0
  97. package/src/extensions/scratch3_music/assets/instruments/15-choir/48.mp3 +0 -0
  98. package/src/extensions/scratch3_music/assets/instruments/15-choir/60.mp3 +0 -0
  99. package/src/extensions/scratch3_music/assets/instruments/15-choir/72.mp3 +0 -0
  100. package/src/extensions/scratch3_music/assets/instruments/16-vibraphone/60.mp3 +0 -0
  101. package/src/extensions/scratch3_music/assets/instruments/16-vibraphone/72.mp3 +0 -0
  102. package/src/extensions/scratch3_music/assets/instruments/17-music-box/60.mp3 +0 -0
  103. package/src/extensions/scratch3_music/assets/instruments/18-steel-drum/60.mp3 +0 -0
  104. package/src/extensions/scratch3_music/assets/instruments/19-marimba/60.mp3 +0 -0
  105. package/src/extensions/scratch3_music/assets/instruments/2-electric-piano/60.mp3 +0 -0
  106. package/src/extensions/scratch3_music/assets/instruments/20-synth-lead/60.mp3 +0 -0
  107. package/src/extensions/scratch3_music/assets/instruments/21-synth-pad/60.mp3 +0 -0
  108. package/src/extensions/scratch3_music/assets/instruments/3-organ/60.mp3 +0 -0
  109. package/src/extensions/scratch3_music/assets/instruments/4-guitar/60.mp3 +0 -0
  110. package/src/extensions/scratch3_music/assets/instruments/5-electric-guitar/60.mp3 +0 -0
  111. package/src/extensions/scratch3_music/assets/instruments/6-bass/36.mp3 +0 -0
  112. package/src/extensions/scratch3_music/assets/instruments/6-bass/48.mp3 +0 -0
  113. package/src/extensions/scratch3_music/assets/instruments/7-pizzicato/60.mp3 +0 -0
  114. package/src/extensions/scratch3_music/assets/instruments/8-cello/36.mp3 +0 -0
  115. package/src/extensions/scratch3_music/assets/instruments/8-cello/48.mp3 +0 -0
  116. package/src/extensions/scratch3_music/assets/instruments/8-cello/60.mp3 +0 -0
  117. package/src/extensions/scratch3_music/assets/instruments/9-trombone/36.mp3 +0 -0
  118. package/src/extensions/scratch3_music/assets/instruments/9-trombone/48.mp3 +0 -0
  119. package/src/extensions/scratch3_music/assets/instruments/9-trombone/60.mp3 +0 -0
  120. package/src/extensions/scratch3_music/index.js +1333 -0
  121. package/src/extensions/scratch3_music/manifest.js +63 -0
  122. package/src/extensions/scratch3_pen/index.js +770 -0
  123. package/src/extensions/scratch3_speech2text/index.js +700 -0
  124. package/src/extensions/scratch3_text2speech/index.js +766 -0
  125. package/src/extensions/scratch3_translate/index.js +286 -0
  126. package/src/extensions/scratch3_video_sensing/debug.js +13 -0
  127. package/src/extensions/scratch3_video_sensing/index.js +594 -0
  128. package/src/extensions/scratch3_video_sensing/library.js +384 -0
  129. package/src/extensions/scratch3_video_sensing/math.js +76 -0
  130. package/src/extensions/scratch3_video_sensing/view.js +509 -0
  131. package/src/extensions/scratch3_wedo2/index.js +1616 -0
  132. package/src/import/load-costume.js +416 -0
  133. package/src/import/load-sound.js +116 -0
  134. package/src/index.js +10 -0
  135. package/src/io/ble.js +256 -0
  136. package/src/io/bt.js +202 -0
  137. package/src/io/clock.js +39 -0
  138. package/src/io/cloud.js +169 -0
  139. package/src/io/keyboard.js +146 -0
  140. package/src/io/mouse.js +145 -0
  141. package/src/io/mouseWheel.js +28 -0
  142. package/src/io/userData.js +24 -0
  143. package/src/io/video.js +210 -0
  144. package/src/playground/benchmark.css +83 -0
  145. package/src/playground/benchmark.js +753 -0
  146. package/src/playground/index.html +111 -0
  147. package/src/playground/suite.css +78 -0
  148. package/src/playground/suite.html +25 -0
  149. package/src/playground/suite.js +544 -0
  150. package/src/playground/video-sensing.html +18 -0
  151. package/src/playground/video-sensing.js +133 -0
  152. package/src/serialization/deserialize-assets.js +178 -0
  153. package/src/serialization/sb2.js +1295 -0
  154. package/src/serialization/sb2_specmap.js +1818 -0
  155. package/src/serialization/sb3.js +1320 -0
  156. package/src/serialization/serialize-assets.js +60 -0
  157. package/src/sprites/rendered-target.js +1115 -0
  158. package/src/sprites/sprite.js +177 -0
  159. package/src/util/base64-util.js +48 -0
  160. package/src/util/cast.js +218 -0
  161. package/src/util/clone.js +17 -0
  162. package/src/util/color.js +204 -0
  163. package/src/util/fetch-with-timeout.js +53 -0
  164. package/src/util/get-monitor-id.js +33 -0
  165. package/src/util/jsonrpc.js +114 -0
  166. package/src/util/log.js +4 -0
  167. package/src/util/math-util.js +122 -0
  168. package/src/util/maybe-format-message.js +18 -0
  169. package/src/util/new-block-ids.js +33 -0
  170. package/src/util/rateLimiter.js +73 -0
  171. package/src/util/scratch-link-websocket.js +133 -0
  172. package/src/util/string-util.js +94 -0
  173. package/src/util/task-queue.js +203 -0
  174. package/src/util/timer.js +103 -0
  175. package/src/util/uid.js +29 -0
  176. package/src/util/variable-util.js +48 -0
  177. package/src/util/xml-escape.js +33 -0
  178. package/src/virtual-machine.js +1586 -0
@@ -0,0 +1,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;