Merge pull request #1905 from bocoup/update-blocks

Add Vernier's GDX-FOR extension
This commit is contained in:
Eric Rosenbaum 2019-01-10 11:04:33 -05:00 committed by GitHub
commit 522b5e1a8e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 753 additions and 60 deletions

128
package-lock.json generated
View file

@ -1088,6 +1088,11 @@
}
}
},
"@vernier/godirect": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@vernier/godirect/-/godirect-1.3.0.tgz",
"integrity": "sha512-vDdl1yCiKwPm/L/ca87cjSSDoFviWPx7IvT3SQxMqplYsACjCWrcW4yIECBfKuK9ok9cTFrXCSsmnpmDDApI8A=="
},
"@webassemblyjs/ast": {
"version": "1.5.13",
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.5.13.tgz",
@ -1537,7 +1542,7 @@
"arr-flatten": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz",
"integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==",
"integrity": "sha1-NgSLv/TntH4TZkQxbJlmnqWukfE=",
"dev": true
},
"arr-union": {
@ -1713,7 +1718,7 @@
},
"aws4": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz",
"resolved": "http://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz",
"integrity": "sha1-g+9cqGCysy5KDe7e6MdxudtXRx4="
},
"babel-code-frame": {
@ -2177,7 +2182,7 @@
},
"bl": {
"version": "1.2.2",
"resolved": "http://registry.npmjs.org/bl/-/bl-1.2.2.tgz",
"resolved": "https://registry.npmjs.org/bl/-/bl-1.2.2.tgz",
"integrity": "sha512-e8tQYnZodmebYDWGH7KMRvtzKXaJHx3BbilrgZCfvyLUYdKpK1t5PSPmpkny/SgiTSCnjfLW7v5rlONXVFkQEA==",
"dev": true,
"requires": {
@ -2231,7 +2236,7 @@
"bn.js": {
"version": "4.11.8",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz",
"integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==",
"integrity": "sha1-LN4J617jQfSEdGuwMJsyU7GxRC8=",
"dev": true
},
"body-parser": {
@ -2338,7 +2343,7 @@
},
"brfs": {
"version": "1.6.1",
"resolved": "http://registry.npmjs.org/brfs/-/brfs-1.6.1.tgz",
"resolved": "https://registry.npmjs.org/brfs/-/brfs-1.6.1.tgz",
"integrity": "sha512-OfZpABRQQf+Xsmju8XE9bDjs+uU4vLREGolP7bDgcpsI17QREyZ4Bl+2KLxxx1kCgA0fAIhKQBaBYh+PEcCqYQ==",
"dev": true,
"requires": {
@ -2470,7 +2475,7 @@
"buffer-indexof": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/buffer-indexof/-/buffer-indexof-1.1.1.tgz",
"integrity": "sha512-4/rOEg86jivtPTeOUUT61jJO1Ya1TrR/OkqCSZDyq84WJh3LuuiphBYJN+fm5xufIk4XAFcEwte/8WzC8If/1g==",
"integrity": "sha1-Uvq8xqYG0aADAoAmSO9o9jnaJow=",
"dev": true
},
"buffer-shims": {
@ -2672,7 +2677,7 @@
"cipher-base": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz",
"integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==",
"integrity": "sha1-h2Dk7MJy9MNjUy+SbYdKriwTl94=",
"dev": true,
"requires": {
"inherits": "^2.0.1",
@ -2797,12 +2802,12 @@
"color-support": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz",
"integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==",
"integrity": "sha1-k4NDeaHMmgxh+C9S8NBDIiUb1aI=",
"dev": true
},
"colors": {
"version": "0.6.2",
"resolved": "http://registry.npmjs.org/colors/-/colors-0.6.2.tgz",
"resolved": "https://registry.npmjs.org/colors/-/colors-0.6.2.tgz",
"integrity": "sha1-JCP+ZnisDF2uiFLl0OW+CMmXq8w=",
"dev": true
},
@ -2945,7 +2950,7 @@
"content-type": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz",
"integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==",
"integrity": "sha1-4TjMdeBAxyexlm/l5fjJruJW/js=",
"dev": true
},
"convert-source-map": {
@ -3286,7 +3291,7 @@
"crypto-browserify": {
"version": "3.12.0",
"resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz",
"integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==",
"integrity": "sha1-OWz58xN/A+S45TLFj2mCVOAPgOw=",
"dev": true,
"requires": {
"browserify-cipher": "^1.0.0",
@ -4380,7 +4385,7 @@
"evp_bytestokey": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz",
"integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==",
"integrity": "sha1-f8vbGY3HGVlDLv4ThCaE4FJaywI=",
"dev": true,
"requires": {
"md5.js": "^1.3.4",
@ -4950,7 +4955,7 @@
"dependencies": {
"commander": {
"version": "2.1.0",
"resolved": "http://registry.npmjs.org/commander/-/commander-2.1.0.tgz",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.1.0.tgz",
"integrity": "sha1-0SG7roYNmZKj1Re6lvVliOR8Z4E=",
"dev": true
}
@ -5315,7 +5320,7 @@
"string-width": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
"integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",
"integrity": "sha1-q5Pyeo3BPSjKyBXEYhQ6bZASrp4=",
"dev": true,
"requires": {
"is-fullwidth-code-point": "^2.0.0",
@ -5509,14 +5514,12 @@
"balanced-match": {
"version": "1.0.0",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"brace-expansion": {
"version": "1.1.11",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@ -5531,20 +5534,17 @@
"code-point-at": {
"version": "1.1.0",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"concat-map": {
"version": "0.0.1",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"console-control-strings": {
"version": "1.1.0",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"core-util-is": {
"version": "1.0.2",
@ -5661,8 +5661,7 @@
"inherits": {
"version": "2.0.3",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"ini": {
"version": "1.3.5",
@ -5674,7 +5673,6 @@
"version": "1.0.0",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"number-is-nan": "^1.0.0"
}
@ -5689,7 +5687,6 @@
"version": "3.0.4",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"brace-expansion": "^1.1.7"
}
@ -5697,14 +5694,12 @@
"minimist": {
"version": "0.0.8",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"minipass": {
"version": "2.2.4",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"safe-buffer": "^5.1.1",
"yallist": "^3.0.0"
@ -5723,7 +5718,6 @@
"version": "0.5.1",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"minimist": "0.0.8"
}
@ -5804,8 +5798,7 @@
"number-is-nan": {
"version": "1.0.1",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"object-assign": {
"version": "4.1.1",
@ -5817,7 +5810,6 @@
"version": "1.4.0",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"wrappy": "1"
}
@ -5939,7 +5931,6 @@
"version": "1.0.2",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0",
@ -6857,12 +6848,12 @@
"is-buffer": {
"version": "1.1.6",
"resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
"integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==",
"integrity": "sha1-76ouqdqg16suoTqXsritUf776L4=",
"dev": true
},
"is-builtin-module": {
"version": "1.0.0",
"resolved": "http://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz",
"resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz",
"integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=",
"dev": true,
"requires": {
@ -7631,7 +7622,7 @@
},
"magic-string": {
"version": "0.22.5",
"resolved": "http://registry.npmjs.org/magic-string/-/magic-string-0.22.5.tgz",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.22.5.tgz",
"integrity": "sha512-oreip9rJZkzvA8Qzk9HFs8fZGF/u7H/gtrE8EN6RjKJ9kh2HlC+yQ2QezifqTZfGyiuAV0dRv5a+y/8gBb1m9w==",
"dev": true,
"requires": {
@ -7901,7 +7892,7 @@
"miller-rabin": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz",
"integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==",
"integrity": "sha1-8IA1HIZbDcViqEYpZtqlNUPHik0=",
"dev": true,
"requires": {
"bn.js": "^4.0.0",
@ -7911,7 +7902,7 @@
"mime": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz",
"integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==",
"integrity": "sha1-Eh+evEnjdm8xGnbh+hyAA8SwOqY=",
"dev": true
},
"mime-db": {
@ -8169,7 +8160,7 @@
},
"multipipe": {
"version": "0.3.1",
"resolved": "http://registry.npmjs.org/multipipe/-/multipipe-0.3.1.tgz",
"resolved": "https://registry.npmjs.org/multipipe/-/multipipe-0.3.1.tgz",
"integrity": "sha1-kmJVJXYboE/qoJYFtjgrziyR8R8=",
"dev": true,
"requires": {
@ -8335,7 +8326,7 @@
"normalize-package-data": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz",
"integrity": "sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==",
"integrity": "sha1-EvlaMH1YNSB1oEkHuErIvpisAS8=",
"dev": true,
"requires": {
"hosted-git-info": "^2.1.4",
@ -8419,7 +8410,6 @@
"version": "0.1.4",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"kind-of": "^3.0.2",
"longest": "^1.0.1",
@ -9602,8 +9592,7 @@
"longest": {
"version": "1.0.1",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"loose-envify": {
"version": "1.3.1",
@ -11270,7 +11259,7 @@
"p-map": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/p-map/-/p-map-1.2.0.tgz",
"integrity": "sha512-r6zKACMNhjPJMTl8KcFH4li//gkrXWfbD6feV8l6doRHlzljFWGJ2AP6iKaCJXyZmAUMOPtvbW7EXkbWO/pLEA==",
"integrity": "sha1-5OlPMR6rvIYzoeeZCBZfyiYkG2s=",
"dev": true
},
"p-try": {
@ -11460,7 +11449,7 @@
"pluralize": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/pluralize/-/pluralize-7.0.0.tgz",
"integrity": "sha512-ARhBOdzS3e41FbkW/XWrTEtukqqLoK5+Z/4UeDaLuSW+39JPeFgs4gCGqsrJHVZX0fUrx//4OF0K1CUGwlIFow==",
"integrity": "sha1-KYuJ34uTsCIdv0Ia0rGx6iP8Z3c=",
"dev": true
},
"pngjs": {
@ -12128,9 +12117,9 @@
}
},
"scratch-audio": {
"version": "0.1.0-prerelease.20181023202904",
"resolved": "https://registry.npmjs.org/scratch-audio/-/scratch-audio-0.1.0-prerelease.20181023202904.tgz",
"integrity": "sha512-0cf+snpT04RFWFgMsMzbztzNVyh2PkUaT8mjlwNNoIRy5p7yDN3EMC2zGbR71nZMus1tO2EDxqrpan2ix4IWDw==",
"version": "0.1.0-prerelease.20190108181031",
"resolved": "https://registry.npmjs.org/scratch-audio/-/scratch-audio-0.1.0-prerelease.20190108181031.tgz",
"integrity": "sha512-Ygu+pN2u9det8HTIo+2wj8ibqe0QjAA624N9GxC62nrdGH39NxDRJyiwheeuZH/oEjM9RTsCSSOH+C9fXA9ekA==",
"dev": true,
"requires": {
"audio-context": "1.0.1",
@ -12184,9 +12173,9 @@
}
},
"scratch-render": {
"version": "0.1.0-prerelease.20181220195236",
"resolved": "https://registry.npmjs.org/scratch-render/-/scratch-render-0.1.0-prerelease.20181220195236.tgz",
"integrity": "sha512-FcYezDaztkoQifUG9k4uOsIFbelMt8JaCMpHrQis0QVJmjSuQrCDB3DRUjwpMuyUrmV7B7GdBUvwt65VYPZJ6g==",
"version": "0.1.0-prerelease.20190109203013",
"resolved": "https://registry.npmjs.org/scratch-render/-/scratch-render-0.1.0-prerelease.20190109203013.tgz",
"integrity": "sha512-yrkBuF1zLHrXEHQmbQhpATDxot/wqoN/oDW2aIjzV9ylxCy+zNdGI2XJ9tRy10bCcM5bdC879ROX9fCr+n6FwQ==",
"dev": true,
"requires": {
"grapheme-breaker": "0.3.2",
@ -12196,8 +12185,29 @@
"minilog": "3.1.0",
"raw-loader": "^0.5.1",
"scratch-storage": "^1.0.0",
"scratch-svg-renderer": "0.2.0-prerelease.20181220183040",
"scratch-svg-renderer": "0.2.0-prerelease.20190109201344",
"twgl.js": "4.4.0"
},
"dependencies": {
"base64-js": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.2.1.tgz",
"integrity": "sha512-dwVUVIXsBZXwTuwnXI9RK8sBmgq09NDHzyR9SAph9eqk76gKK2JSQmZARC2zRC81JC2QTtxD0ARU5qTS25gIGw==",
"dev": true
},
"scratch-svg-renderer": {
"version": "0.2.0-prerelease.20190109201344",
"resolved": "https://registry.npmjs.org/scratch-svg-renderer/-/scratch-svg-renderer-0.2.0-prerelease.20190109201344.tgz",
"integrity": "sha512-pRMvQrM5UA2wcqleaXVpFx0Pi6Q3GsRA5elJ0tJksdr6k8HYm5D6sW62VtEtMHjnkQDa+EFyqfHq9IEPnzFjeQ==",
"dev": true,
"requires": {
"base64-js": "1.2.1",
"base64-loader": "1.0.0",
"minilog": "3.1.0",
"scratch-render-fonts": "1.0.0-prerelease.20180906193204",
"transformation-matrix": "1.14.1"
}
}
}
},
"scratch-render-fonts": {
@ -12450,7 +12460,7 @@
"setprototypeof": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz",
"integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==",
"integrity": "sha1-0L2FU2iHtv58DYGMuWLZ2RxU5lY=",
"dev": true
},
"sha.js": {
@ -12767,7 +12777,7 @@
"source-list-map": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.0.tgz",
"integrity": "sha512-I2UmuJSRr/T8jisiROLU3A3ltr+swpniSmNPI4Ml3ZCX6tVnDsuZzK7F2hl5jTqbZBWCEKlj5HRQiPExXLgE8A==",
"integrity": "sha1-qqR0A/eyRakvvJfqCPJQ1gh+0IU=",
"dev": true
},
"source-map": {
@ -13124,7 +13134,7 @@
"string-width": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
"integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",
"integrity": "sha1-q5Pyeo3BPSjKyBXEYhQ6bZASrp4=",
"dev": true,
"requires": {
"is-fullwidth-code-point": "^2.0.0",
@ -13542,7 +13552,7 @@
"tmp": {
"version": "0.0.33",
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
"integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==",
"integrity": "sha1-bTQzWIl2jSGyvNoKonfO07G/rfk=",
"dev": true,
"requires": {
"os-tmpdir": "~1.0.2"
@ -13863,7 +13873,7 @@
"dependencies": {
"pako": {
"version": "0.2.9",
"resolved": "http://registry.npmjs.org/pako/-/pako-0.2.9.tgz",
"resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz",
"integrity": "sha1-8/dSL073gjSNqBYbrZ7P1Rv4OnU=",
"dev": true
}

View file

@ -29,6 +29,7 @@
"version": "json -f package.json -I -e \"this.repository.sha = '$(git log -n1 --pretty=format:%H)'\""
},
"dependencies": {
"@vernier/godirect": "1.3.0",
"arraybuffer-loader": "^1.0.6",
"atob": "2.1.2",
"btoa": "1.2.1",

View file

@ -17,6 +17,7 @@ const Scratch3VideoSensingBlocks = require('../extensions/scratch3_video_sensing
const Scratch3Speech2TextBlocks = require('../extensions/scratch3_speech2text');
const Scratch3Ev3Blocks = require('../extensions/scratch3_ev3');
const Scratch3MakeyMakeyBlocks = require('../extensions/scratch3_makeymakey');
const Scratch3GdxForBlocks = require('../extensions/scratch3_gdx_for');
const builtinExtensions = {
pen: Scratch3PenBlocks,
@ -28,7 +29,8 @@ const builtinExtensions = {
videoSensing: Scratch3VideoSensingBlocks,
speech2text: Scratch3Speech2TextBlocks,
ev3: Scratch3Ev3Blocks,
makeymakey: Scratch3MakeyMakeyBlocks
makeymakey: Scratch3MakeyMakeyBlocks,
gdxfor: Scratch3GdxForBlocks
};
/**

View file

@ -0,0 +1,632 @@
const ArgumentType = require('../../extension-support/argument-type');
const BlockType = require('../../extension-support/block-type');
const log = require('../../util/log');
const Cast = require('../../util/cast');
const formatMessage = require('format-message');
const MathUtil = require('../../util/math-util');
const BLE = require('../../io/ble');
const godirect = require('@vernier/godirect/dist/godirect.min.umd.js');
const ScratchLinkDeviceAdapter = require('./scratch-link-device-adapter');
/**
* Icon png to be displayed at the left edge of each extension block, encoded as a data URI.
* @type {string}
*/
// eslint-disable-next-line max-len
const blockIconURI = 'data:image/png;base64,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';
/**
* Enum for Vernier godirect protocol.
* @readonly
* @enum {string}
*/
const BLEUUID = {
service: 'd91714ef-28b9-4f91-ba16-f0d9a604f112',
commandChar: 'f4bf14a6-c7d5-4b6d-8aa8-df1a7c83adcb',
responseChar: 'b41e6675-a329-40e0-aa01-44d2f444babe'
};
/**
* Manage communication with a GDX-FOR peripheral over a Scratch Link client socket.
*/
class GdxFor {
/**
* Construct a GDX-FOR communication object.
* @param {Runtime} runtime - the Scratch 3.0 runtime
* @param {string} extensionId - the id of the extension
*/
constructor (runtime, extensionId) {
/**
* The Scratch 3.0 runtime used to trigger the green flag button.
* @type {Runtime}
* @private
*/
this._runtime = runtime;
/**
* The BluetoothLowEnergy connection socket for reading/writing peripheral data.
* @type {BLE}
* @private
*/
this._scratchLinkSocket = null;
/**
* An @vernier/godirect Device
* @type {Device}
* @private
*/
this._device = null;
this._runtime.registerPeripheralExtension(extensionId, this);
/**
* The id of the extension this peripheral belongs to.
*/
this._extensionId = extensionId;
this.disconnect = this.disconnect.bind(this);
this._onConnect = this._onConnect.bind(this);
}
/**
* Called by the runtime when user wants to scan for a peripheral.
*/
scan () {
if (this._device) {
this._device.close();
}
this._scratchLinkSocket = new BLE(this._runtime, this._extensionId, {
filters: [
{namePrefix: 'GDX-FOR'}
],
optionalServices: [
BLEUUID.service
]
}, this._onConnect);
}
/**
* Called by the runtime when user wants to connect to a certain peripheral.
* @param {number} id - the id of the peripheral to connect to.
*/
connect (id) {
if (this._scratchLinkSocket) {
this._scratchLinkSocket.connectPeripheral(id);
}
}
/**
* Called by the runtime when a use exits the connection popup.
* Disconnect from the GDX FOR.
*/
disconnect () {
if (this._device) {
this._device.close();
}
}
/**
* Return true if connected to the goforce device.
* @return {boolean} - whether the goforce is connected.
*/
isConnected () {
let connected = false;
if (this._scratchLinkSocket) {
connected = this._scratchLinkSocket.isConnected();
}
return connected;
}
/**
* Starts reading data from peripheral after BLE has connected to it.
* @private
*/
_onConnect () {
const adapter = new ScratchLinkDeviceAdapter(this._scratchLinkSocket, BLEUUID);
godirect.createDevice(adapter, {open: true, startMeasurements: false}).then(device => {
this._device = device;
this._startMeasurements();
});
}
/**
* Enable and begin reading measurements
* @private
*/
_startMeasurements () {
this._device.sensors.forEach(sensor => {
sensor.setEnabled(true);
// For now, clear the save sensor values. The unlimited saving
// will be fixed in a future @vernier/godirect release.
sensor.on('value-changed', changedSensor => {
if (changedSensor.values.length > 1000) {
changedSensor.clear();
}
});
});
this._device.start(10); // Set the period to 10 milliseconds
}
getForce () {
if (this.isConnected()) {
let force = this._device.getSensor(1).value;
// Normalize the force, which can be measured between -50 and 50 N,
// to be a value between -100 and 100.
force = MathUtil.clamp(force * 2, -100, 100);
return force;
}
return 0;
}
getTiltX () {
if (this.isConnected()) {
let x = this.getAccelerationX();
let y = this.getAccelerationY();
let z = this.getAccelerationZ();
let xSign = 1;
let ySign = 1;
let zSign = 1;
if (x < 0.0) {
x *= -1.0; xSign = -1;
}
if (y < 0.0) {
y *= -1.0; ySign = -1;
}
if (z < 0.0) {
z *= -1.0; zSign = -1;
}
// Compute the yz unit vector
const z2 = z * z;
const y2 = y * y;
let value = z2 + y2;
value = Math.sqrt(value);
// For sufficiently small zy vector values we are essentially at 90 degrees.
// The following snaps to 90 and avoids divide-by-zero errors.
// The snap factor was derived through observation -- just enough to
// still allow single degree steps up to 90 (..., 87, 88, 89, 90).
if (value < 0.35) {
value = 90;
} else {
// Compute the x-axis angle
value = x / value;
value = Math.atan(value);
value *= 57.2957795; // convert from rad to deg
}
// Manage the sign of the result
let yzSign = ySign;
if (z > y) yzSign = zSign;
if (yzSign === -1) value = 180.0 - value;
value *= xSign;
// Round the result to the nearest degree
value += 0.5;
return value;
}
return 0;
}
getTiltY () {
if (this.isConnected()) {
let x = this.getAccelerationX();
let y = this.getAccelerationY();
let z = this.getAccelerationZ();
let xSign = 1;
let ySign = 1;
let zSign = 1;
if (x < 0.0) {
x *= -1.0; xSign = -1;
}
if (y < 0.0) {
y *= -1.0; ySign = -1;
}
if (z < 0.0) {
z *= -1.0; zSign = -1;
}
// Compute the yz unit vector
const z2 = z * z;
const x2 = x * x;
let value = z2 + x2;
value = Math.sqrt(value);
// For sufficiently small zy vector values we are essentially at 90 degrees.
// The following snaps to 90 and avoids divide-by-zero errors.
// The snap factor was derived through observation -- just enough to
// still allow single degree steps up to 90 (..., 87, 88, 89, 90).
if (value < 0.35) {
value = 90;
} else {
// Compute the x-axis angle
value = y / value;
value = Math.atan(value);
value *= 57.2957795; // convert from rad to deg
}
// Manage the sign of the result
let xzSign = xSign;
if (z > x) xzSign = zSign;
if (xzSign === -1) value = 180.0 - value;
value *= ySign;
// Round the result to the nearest degree
value += 0.5;
return value;
}
return 0;
}
getAccelerationX () {
if (this.isConnected()) {
return this._device.getSensor(2).value;
}
return 0;
}
getAccelerationY () {
if (this.isConnected()) {
return this._device.getSensor(3).value;
}
return 0;
}
getAccelerationZ () {
if (this.isConnected()) {
return this._device.getSensor(4).value;
}
return 0;
}
getSpinSpeedX () {
if (this.isConnected()) {
return this._device.getSensor(5).value * (180 / Math.PI);
}
return 0;
}
getSpinSpeedY () {
if (this.isConnected()) {
return this._device.getSensor(6).value * (180 / Math.PI);
}
return 0;
}
getSpinSpeedZ () {
if (this.isConnected()) {
return this._device.getSensor(7).value * (180 / Math.PI);
}
return 0;
}
}
/**
* Enum for comparison operations.
* @readonly
* @enum {string}
*/
const ComparisonOptions = {
LESS_THAN: 'less_than',
GREATER_THAN: 'greater_than'
};
/**
* Scratch 3.0 blocks to interact with a GDX-FOR peripheral.
*/
class Scratch3GdxForBlocks {
/**
* @return {string} - the name of this extension.
*/
static get EXTENSION_NAME () {
return 'GDX-FOR';
}
/**
* @return {string} - the ID of this extension.
*/
static get EXTENSION_ID () {
return 'gdxfor';
}
get DIRECTIONS_MENU () {
return [
{
text: 'x',
value: 'x'
},
{
text: 'y',
value: 'y'
},
{
text: 'z',
value: 'z'
}
];
}
get TILT_MENU () {
return [
{
text: 'x',
value: 'x'
},
{
text: 'y',
value: 'y'
}
];
}
get COMPARE_MENU () {
return [
{
text: '<',
value: ComparisonOptions.LESS_THAN
},
{
text: '>',
value: ComparisonOptions.GREATER_THAN
}
];
}
/**
* Construct a set of GDX-FOR blocks.
* @param {Runtime} runtime - the Scratch 3.0 runtime.
*/
constructor (runtime) {
/**
* The Scratch 3.0 runtime.
* @type {Runtime}
*/
this.runtime = runtime;
// Create a new GdxFor peripheral instance
this._peripheral = new GdxFor(this.runtime, Scratch3GdxForBlocks.EXTENSION_ID);
}
/**
* @returns {object} metadata for this extension and its blocks.
*/
getInfo () {
return {
id: Scratch3GdxForBlocks.EXTENSION_ID,
name: Scratch3GdxForBlocks.EXTENSION_NAME,
blockIconURI: blockIconURI,
showStatusButton: true,
blocks: [
{
opcode: 'whenAccelerationCompare',
text: formatMessage({
id: 'gdxfor.whenAccelerationCompare',
default: 'when acceleration [COMPARE] [VALUE]',
description: 'when the meters/second^2 value measured by the ' +
'acceleration sensor is compared to some value'
}),
blockType: BlockType.HAT,
arguments: {
COMPARE: {
type: ArgumentType.STRING,
menu: 'compareOptions',
defaultValue: ComparisonOptions.GREATER_THAN
},
VALUE: {
type: ArgumentType.NUMBER,
defaultValue: 5
}
}
},
{
opcode: 'whenSpinSpeedCompare',
text: formatMessage({
id: 'gdxfor.whenSpinSpeedCompare',
default: 'when spin speed [COMPARE] [VALUE]',
description: 'when the degrees/second value measured by the ' +
'gyroscope sensor is compared to some value'
}),
blockType: BlockType.HAT,
arguments: {
COMPARE: {
type: ArgumentType.STRING,
menu: 'compareOptions',
defaultValue: ComparisonOptions.GREATER_THAN
},
VALUE: {
type: ArgumentType.NUMBER,
defaultValue: 5
}
}
},
{
opcode: 'whenForceCompare',
text: formatMessage({
id: 'gdxfor.whenForceCompare',
default: 'when force [COMPARE] [VALUE]',
description: 'when the value measured by the force sensor is compared to some value'
}),
blockType: BlockType.HAT,
arguments: {
COMPARE: {
type: ArgumentType.STRING,
menu: 'compareOptions',
defaultValue: ComparisonOptions.GREATER_THAN
},
VALUE: {
type: ArgumentType.NUMBER,
defaultValue: 5
}
}
},
{
opcode: 'getAcceleration',
text: formatMessage({
id: 'gdxfor.getAcceleration',
default: 'acceleration [DIRECTION]',
description: 'gets acceleration'
}),
blockType: BlockType.REPORTER,
arguments: {
DIRECTION: {
type: ArgumentType.STRING,
menu: 'directionOptions',
defaultValue: 'x'
}
}
},
{
opcode: 'getSpinSpeed',
text: formatMessage({
id: 'gdxfor.getSpinSpeed',
default: 'spin speed [DIRECTION]',
description: 'gets spin speed'
}),
blockType: BlockType.REPORTER,
arguments: {
DIRECTION: {
type: ArgumentType.STRING,
menu: 'directionOptions',
defaultValue: 'x'
}
}
},
{
opcode: 'getTilt',
text: formatMessage({
id: 'gdxfor.getTilt',
default: 'tilt [TILT]',
description: 'gets tilt'
}),
blockType: BlockType.REPORTER,
arguments: {
TILT: {
type: ArgumentType.STRING,
menu: 'tiltOptions',
defaultValue: 'x'
}
}
},
{
opcode: 'getForce',
text: formatMessage({
id: 'gdxfor.getForce',
default: 'force',
description: 'gets force'
}),
blockType: BlockType.REPORTER
}
],
menus: {
directionOptions: this.DIRECTIONS_MENU,
compareOptions: this.COMPARE_MENU,
tiltOptions: this.TILT_MENU
}
};
}
/**
* @param {number} x - x axis vector
* @param {number} y - y axis vector
* @param {number} z - z axis vector
* @return {number} - the magnitude of a three dimension vector.
*/
magnitude (x, y, z) {
return Math.sqrt((x * x) + (y * y) + (z * z));
}
whenAccelerationCompare (args) {
const currentVal = this.magnitude(
this._peripheral.getAccelerationX(),
this._peripheral.getAccelerationY(),
this._peripheral.getAccelerationZ()
);
switch (args.COMPARE) {
case ComparisonOptions.LESS_THAN:
return currentVal < Cast.toNumber(args.VALUE);
case ComparisonOptions.GREATER_THAN:
return currentVal > Cast.toNumber(args.VALUE);
default:
log.warn(`Unknown comparison operator in whenAccelerationCompare: ${args.COMPARE}`);
return false;
}
}
whenSpinSpeedCompare (args) {
const currentVal = this.magnitude(
this._peripheral.getSpinSpeedX(),
this._peripheral.getSpinSpeedY(),
this._peripheral.getSpinSpeedZ()
);
switch (args.COMPARE) {
case ComparisonOptions.LESS_THAN:
return currentVal < Cast.toNumber(args.VALUE);
case ComparisonOptions.GREATER_THAN:
return currentVal > Cast.toNumber(args.VALUE);
default:
log.warn(`Unknown comparison operator in whenSpinSpeedCompare: ${args.COMPARE}`);
return false;
}
}
whenForceCompare (args) {
switch (args.COMPARE) {
case ComparisonOptions.LESS_THAN:
return this._peripheral.getForce() < Cast.toNumber(args.VALUE);
case ComparisonOptions.GREATER_THAN:
return this._peripheral.getForce() > Cast.toNumber(args.VALUE);
default:
log.warn(`Unknown comparison operator in whenForceCompare: ${args.COMPARE}`);
return false;
}
}
getAcceleration (args) {
switch (args.DIRECTION) {
case 'x':
return this._peripheral.getAccelerationX();
case 'y':
return this._peripheral.getAccelerationY();
case 'z':
return this._peripheral.getAccelerationZ();
default:
log.warn(`Unknown direction in getAcceleration: ${args.DIRECTION}`);
}
}
getSpinSpeed (args) {
switch (args.DIRECTION) {
case 'x':
return this._peripheral.getSpinSpeedX();
case 'y':
return this._peripheral.getSpinSpeedY();
case 'z':
return this._peripheral.getSpinSpeedZ();
default:
log.warn(`Unknown direction in getSpinSpeed: ${args.DIRECTION}`);
}
}
getTilt (args) {
switch (args.TILT) {
case 'x':
return this._peripheral.getTiltX();
case 'y':
return this._peripheral.getTiltY();
default:
log.warn(`Unknown direction in getTilt: ${args.TILT}`);
}
}
getForce () {
return this._peripheral.getForce();
}
}
module.exports = Scratch3GdxForBlocks;

View file

@ -0,0 +1,48 @@
const Base64Util = require('../../util/base64-util');
/**
* Adapter class
*/
class ScratchLinkDeviceAdapter {
constructor (scratchLinkSocket, {service, commandChar, responseChar}) {
this.scratchLinkSocket = scratchLinkSocket;
this._service = service;
this._commandChar = commandChar;
this._responseChar = responseChar;
this._onResponse = this._onResponse.bind(this);
this._deviceOnResponse = null;
}
get godirectAdapter () {
return true;
}
writeCommand (commandBuffer) {
const data = Base64Util.uint8ArrayToBase64(commandBuffer);
return this.scratchLinkSocket
.write(this._service, this._commandChar, data, 'base64', true);
}
setup ({onResponse}) {
this._deviceOnResponse = onResponse;
return this.scratchLinkSocket
.startNotifications(this._service, this._responseChar, this._onResponse);
// TODO:
// How do we find out from scratch link if communication closes?
}
_onResponse (base64) {
const array = Base64Util.base64ToUint8Array(base64);
const response = new DataView(array.buffer);
return this._deviceOnResponse(response);
}
close () {
return this.scratchLinkSocket.disconnect();
}
}
module.exports = ScratchLinkDeviceAdapter;