Compare commits
67 Commits
Author | SHA1 | Date | |
---|---|---|---|
473950e491 | |||
fd33610511 | |||
47ea4e01d2 | |||
16199cfcea | |||
8a4556e70c | |||
bfc2641637 | |||
ef7fdc2ef7 | |||
a8b427fd89 | |||
79c23e2e2a | |||
c11acce579 | |||
f79551073e | |||
c6c133ef9e | |||
817e86f5aa | |||
19e33d029d | |||
e3671ca809 | |||
1f3a2ab6fe | |||
dad3e89577 | |||
df9d3c4444 | |||
edc489c83d | |||
fb29af8011 | |||
516def7a3f | |||
b5cb8deb93 | |||
658083b4eb | |||
1441129355 | |||
2b87b26f00 | |||
741facc769 | |||
41a5bc72a1 | |||
9d5e93b879 | |||
32e0cb0fe8 | |||
a224259e74 | |||
c144f3a15d | |||
a3fa07463f | |||
aa20f2fa4d | |||
4a1399de59 | |||
0b8e5c12c3 | |||
445066776b | |||
6cb204c548 | |||
6ed6a525fd | |||
ba6dc9f136 | |||
ae17d4380e | |||
f79b726f87 | |||
0f3c7b8c0f | |||
dac6f5af73 | |||
99fb074952 | |||
66ba26586f | |||
e27c4de108 | |||
f19a110953 | |||
626055d3eb | |||
9085c98c7f | |||
85e3148f23 | |||
c2e37a2c6e | |||
b4ad4819a5 | |||
d9f2c7cc42 | |||
15638d2767 | |||
5ecd20583b | |||
4c27d62796 | |||
90da72a8de | |||
8f50beb938 | |||
52527dd584 | |||
fc0faf5181 | |||
cd9589e562 | |||
af7c51b954 | |||
a65e71f3b1 | |||
89e899cc79 | |||
a984778dfd | |||
008cbf543f | |||
61ee841431 |
@ -7,10 +7,16 @@ Register an event that will execute whenever the user attaches one side of the c
|
||||
|
||||
This example displays a random number every time the crocodile clip holds `GND` then connects and disconnects the `P0` pin. Each time the crocodile clip is firmly connected and disconnected from pin `P0`, the micro:bit will return a random Number between 0 and the parameter limit
|
||||
|
||||

|
||||
```blocks
|
||||
input.onPinPressed(TouchPin.P0, () => {
|
||||
basic.showNumber(Math.random(10))
|
||||
})
|
||||
```
|
||||
|
||||
### Connecting Crocodile Clips
|
||||
|
||||

|
||||
|
||||
### See also
|
||||
|
||||
[micro:bit pins](/device/pins), [pin is pressed](/reference/input/pin-is-pressed), [analog read pin](/reference/pins/analog-read-pin), [analog write pin](/reference/pins/analog-write-pin), [digital read pin](/reference/pins/digital-read-pin), [digital write pin](/reference/pins/digital-write-pin)
|
||||
|
@ -94,8 +94,6 @@ Connect a micro:bit to your computer using your USB cable
|
||||
|
||||
Click or tap the **Download** button for the seismograph program to run the program on the micro:bit.
|
||||
|
||||

|
||||
|
||||
## 9.
|
||||
|
||||
A black line should appear directly beneath the colored line. The black line measures the micro:bit acceleration. And the colored line measures micro:bit simulator acceleration.
|
||||
|
18
docs/offline.md
Normal file
@ -0,0 +1,18 @@
|
||||
# Offline editing
|
||||
|
||||
## Web application
|
||||
|
||||
**https://codethemicrobit.com is an HTML5 web application** that automatically gets cached locally by your browser.
|
||||
Once the web app is loaded and you have compiled at least once, you will have all the code needed to work without an internet connection.
|
||||
|
||||
## Command line interface
|
||||
|
||||
For more experience users, you can download the entire toolchain and use the [command line interface](/cli) (CLI) to compile
|
||||
and deploy your scripts locally. PXT provides a great out-of-the-box experience using [Visual Studio Code](/code),
|
||||
a lightweight cross-platform code editor.
|
||||
|
||||

|
||||
|
||||
## Native clients
|
||||
|
||||
There are no native clients available yet.
|
@ -1,9 +0,0 @@
|
||||
# String
|
||||
|
||||
```cards
|
||||
String.fromCharCode(0);
|
||||
```
|
||||
|
||||
### See Also
|
||||
|
||||
[fromCharCode](/reference//math/string-from-char-code)
|
@ -11,11 +11,16 @@ An *Image* is a matrix of pixels to show on the [LED screen](/device/screen)
|
||||
To display an image:
|
||||
|
||||
* click `Basic` , `Show LEDs`, and tap on the LEDs`
|
||||
* when you're done, return to your code
|
||||
|
||||

|
||||
|
||||
You should see code similar to this:
|
||||
```blocks
|
||||
basic.showLeds(`
|
||||
. . . . .
|
||||
. # . # .
|
||||
. . . . .
|
||||
# . . . #
|
||||
. # # # .
|
||||
`)
|
||||
```
|
||||
|
||||
### Creating an image
|
||||
|
||||
|
@ -20,7 +20,11 @@ led.plot(2, 2)
|
||||
led.toggleAll()
|
||||
```
|
||||
|
||||

|
||||
```sim
|
||||
basic.clearScreen()
|
||||
led.plot(2, 2)
|
||||
led.toggleAll()
|
||||
```
|
||||
|
||||
### See also
|
||||
|
||||
|
@ -1,26 +0,0 @@
|
||||
# Off-line support
|
||||
|
||||
The micro:bit pins.
|
||||
|
||||
## How to work offline
|
||||
|
||||
If you have loaded the web app at some time in the past (by clicking on "my scripts" from the home page), then if you later open the same browser (whether you are online or offline) and type in [https://codethemicrobit.com/](https://codethemicrobit.com/), you will be able to access all the features of the web app. Note that it is important to end the URL with "/".
|
||||
|
||||
## Save and load code using files
|
||||
|
||||

|
||||
|
||||
The micro:bit automatically saves and synchronises scripts for signed-in users through the cloud. We also decided to also support file save/load for offline support and sharing via email and other storage providers. Users are now able to import and export scripts as files. For example, they can simply email it or submit them in their classroom portal.
|
||||
|
||||

|
||||
|
||||
## The new in-browser compiler
|
||||
|
||||
The compilation from a script to ARM machine code is now done entirely in the browser (read the [in depth story](https://www.touchdevelop.com/docs/touch-develop-in-208-bits) about building the compiler). The new compiler is used by the Block Editor, Touch Develop and Code Kingdoms to create a .hex file solely within the confines of your web browser (no Internet connection is needed). The micro:bit compilation process is shown below:
|
||||
|
||||

|
||||
|
||||
The C++ compiler is now only used to compile the micro:bit runtime - this is done offline by the micro:bit team and the pre-compiled runtime is linked with your compiled script in the browser.
|
||||
|
||||
Compiled .hex files can also be imported back into the web site. This make it easy for a teacher to review the source of a script by simply drag and dropping the file into the editor.
|
||||
|
4
docs/static/hardware/.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
# don't check in until OSS request is approved
|
||||
sparkfun-*
|
||||
raspberrypi-*
|
||||
arduino-*
|
32
docs/static/hardware/neopixel.svg
vendored
Normal file
@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<svg viewBox="-5 -1 53 112" xmlns="http://www.w3.org/2000/svg" xmlns:bx="https://boxy-svg.com">
|
||||
<rect x="2.5" width="38" height="100" style="fill: rgb(68, 68, 68);"/>
|
||||
<rect x="11.748" y="3.2" width="1.391" height="2.553" style="fill: none; stroke-linejoin: round; stroke-width: 3; stroke: rgb(165, 103, 52);"/>
|
||||
<rect x="20.75" y="3.2" width="1.391" height="2.553" style="fill: none; stroke-linejoin: round; stroke-width: 3; stroke: rgb(165, 103, 52);"/>
|
||||
<rect x="29.75" y="3.2" width="1.391" height="2.553" style="fill: none; stroke-linejoin: round; stroke-width: 3; stroke: rgb(165, 103, 52);"/>
|
||||
<g>
|
||||
<rect x="9" y="16.562" width="25" height="3.238" style="fill: rgb(216, 216, 216);"/>
|
||||
<rect x="9" y="22.562" width="25" height="3.238" style="fill: rgb(216, 216, 216);"/>
|
||||
<rect x="9" y="28.563" width="25" height="3.238" style="fill: rgb(216, 216, 216);"/>
|
||||
<rect x="11.607" y="14.833" width="19.787" height="18.697" style="fill: rgb(0, 0, 0);"/>
|
||||
<ellipse style="fill: rgb(216, 216, 216);" cx="21.5" cy="24.181" rx="7" ry="7"/>
|
||||
</g>
|
||||
<path d="M -7.25 -103.2 L -2.5 -100.003 L -12 -100.003 L -7.25 -103.2 Z" style="fill: rgb(68, 68, 68);" transform="matrix(-1, 0, 0, -1, 0, 0)" bx:shape="triangle -12 -103.2 9.5 3.197 0.5 0 1@ad6f5cac"/>
|
||||
<path d="M -16.75 -103.197 L -12 -100 L -21.5 -100 L -16.75 -103.197 Z" style="fill: rgb(68, 68, 68);" transform="matrix(-1, 0, 0, -1, 0, 0)" bx:shape="triangle -21.5 -103.197 9.5 3.197 0.5 0 1@07d73149"/>
|
||||
<path d="M -26.25 -103.2 L -21.5 -100.003 L -31 -100.003 L -26.25 -103.2 Z" style="fill: rgb(68, 68, 68);" transform="matrix(-1, 0, 0, -1, 0, 0)" bx:shape="triangle -31 -103.2 9.5 3.197 0.5 0 1@54403e2d"/>
|
||||
<path d="M -35.75 -103.197 L -31 -100 L -40.5 -100 L -35.75 -103.197 Z" style="fill: rgb(68, 68, 68);" transform="matrix(-1, 0, 0, -1, 0, 0)" bx:shape="triangle -40.5 -103.197 9.5 3.197 0.5 0 1@21c9b772"/>
|
||||
<g transform="matrix(1, 0, 0, 1, 0.000002, 29.999994)">
|
||||
<rect x="9" y="16.562" width="25" height="3.238" style="fill: rgb(216, 216, 216);"/>
|
||||
<rect x="9" y="22.562" width="25" height="3.238" style="fill: rgb(216, 216, 216);"/>
|
||||
<rect x="9" y="28.563" width="25" height="3.238" style="fill: rgb(216, 216, 216);"/>
|
||||
<rect x="11.607" y="14.833" width="19.787" height="18.697" style="fill: rgb(0, 0, 0);"/>
|
||||
<ellipse style="fill: rgb(216, 216, 216);" cx="21.5" cy="24.181" rx="7" ry="7"/>
|
||||
</g>
|
||||
<g transform="matrix(1, 0, 0, 1, 0.000005, 59.999992)">
|
||||
<rect x="9" y="16.562" width="25" height="3.238" style="fill: rgb(216, 216, 216);"/>
|
||||
<rect x="9" y="22.562" width="25" height="3.238" style="fill: rgb(216, 216, 216);"/>
|
||||
<rect x="9" y="28.563" width="25" height="3.238" style="fill: rgb(216, 216, 216);"/>
|
||||
<rect x="11.607" y="14.833" width="19.787" height="18.697" style="fill: rgb(0, 0, 0);"/>
|
||||
<ellipse style="fill: rgb(216, 216, 216);" cx="21.5" cy="24.181" rx="7" ry="7"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.9 KiB |
35
docs/static/hardware/speaker.svg
vendored
Normal file
@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<svg viewBox="0 0 500 500" xmlns="http://www.w3.org/2000/svg">
|
||||
<g transform="matrix(1, 0, 0, 1, -0.00023, -58.230297)">
|
||||
<ellipse style="fill: rgb(70, 70, 70);" cx="250.58" cy="308.81" rx="215" ry="215"/>
|
||||
<ellipse style="fill: rgb(0, 0, 0);" transform="matrix(1, 0, 0, 1.000001, -232.069031, 248.780606)" cx="482.069" cy="198.188" rx="23.028" ry="23.028"/>
|
||||
<ellipse style="fill: rgb(0, 0, 0);" transform="matrix(1, 0, 0, 0.999999, -232.067871, 110.041956)" cx="482.067" cy="198.188" rx="23.028" ry="23.028"/>
|
||||
<ellipse style="fill: rgb(0, 0, 0);" cx="389.12" cy="308.23" rx="23.028" ry="23.028"/>
|
||||
<ellipse style="fill: rgb(0, 0, 0);" cx="110.88" cy="308.23" rx="23.028" ry="23.028"/>
|
||||
<ellipse style="fill: rgb(0, 0, 0);" cx="250" cy="169.393" rx="23.028" ry="23.028"/>
|
||||
<g transform="matrix(1, 0, 0, 1, -0.000009, 0.000015)">
|
||||
<ellipse style="fill: rgb(0, 0, 0);" cx="250" cy="238.513" rx="23.028" ry="23.028" transform="matrix(1.000001, 0, 0, 0.999999, 69.996739, 69.71816)"/>
|
||||
<ellipse style="fill: rgb(0, 0, 0);" transform="matrix(1, 0, 0, 0.999999, -302.064453, 110.043115)" cx="482.064" cy="198.188" rx="23.028" ry="23.028"/>
|
||||
</g>
|
||||
<g transform="matrix(0.866026, 0.5, -0.5, 0.866026, 7.386552, -105.261086)">
|
||||
<ellipse style="fill: rgb(0, 0, 0);" transform="matrix(0.999999, 0, 0, 0.999999, -65.212313, 177.387415)" cx="482.068" cy="198.188" rx="23.028" ry="23.028"/>
|
||||
<ellipse style="fill: rgb(0, 0, 0);" cx="555.975" cy="236.836" rx="23.028" ry="23.028"/>
|
||||
<ellipse style="fill: rgb(0, 0, 0);" cx="277.735" cy="236.836" rx="23.028" ry="23.028"/>
|
||||
<ellipse style="fill: rgb(0, 0, 0);" cx="416.855" cy="97.999" rx="23.028" ry="23.028"/>
|
||||
</g>
|
||||
<g transform="matrix(0.5, 0.866026, -0.866026, 0.5, 246.635941, -171.170502)">
|
||||
<ellipse style="fill: rgb(0, 0, 0);" transform="matrix(0.999999, 0, 0, 0.999999, -65.212313, 177.387415)" cx="482.068" cy="198.188" rx="23.028" ry="23.028"/>
|
||||
<ellipse style="fill: rgb(0, 0, 0);" cx="555.975" cy="236.836" rx="23.028" ry="23.028"/>
|
||||
<ellipse style="fill: rgb(0, 0, 0);" cx="277.735" cy="236.836" rx="23.028" ry="23.028"/>
|
||||
<ellipse style="fill: rgb(0, 0, 0);" cx="416.855" cy="97.999" rx="23.028" ry="23.028"/>
|
||||
</g>
|
||||
<g transform="matrix(-0.5, 0.866026, -0.866026, -0.5, 641.934998, 245.84082)">
|
||||
<ellipse style="fill: rgb(0, 0, 0);" cx="250" cy="238.513" rx="23.028" ry="23.028" transform="matrix(1.000001, 0, 0, 0.999999, 69.996739, 69.71816)"/>
|
||||
<ellipse style="fill: rgb(0, 0, 0);" transform="matrix(1, 0, 0, 0.999999, -302.064453, 110.043115)" cx="482.064" cy="198.188" rx="23.028" ry="23.028"/>
|
||||
</g>
|
||||
<g transform="matrix(-0.500001, -0.866026, 0.866026, -0.500001, 108.063393, 678.85083)">
|
||||
<ellipse style="fill: rgb(0, 0, 0);" cx="250" cy="238.513" rx="23.028" ry="23.028" transform="matrix(1.000001, 0, 0, 0.999999, 69.996739, 69.71816)"/>
|
||||
<ellipse style="fill: rgb(0, 0, 0);" transform="matrix(1, 0, 0, 0.999999, -302.064453, 110.043115)" cx="482.064" cy="198.188" rx="23.028" ry="23.028"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 3.1 KiB |
BIN
docs/static/mb/antenna-0.png
vendored
Before Width: | Height: | Size: 2.7 KiB |
BIN
docs/static/mb/boolean-0.png
vendored
Before Width: | Height: | Size: 2.4 KiB |
BIN
docs/static/mb/boolean-1.png
vendored
Before Width: | Height: | Size: 6.7 KiB |
BIN
docs/static/mb/boolean-2.png
vendored
Before Width: | Height: | Size: 12 KiB |
BIN
docs/static/mb/change-0.png
vendored
Before Width: | Height: | Size: 4.6 KiB |
BIN
docs/static/mb/change-score-by-0.png
vendored
Before Width: | Height: | Size: 5.8 KiB |
BIN
docs/static/mb/create-sprite-0.png
vendored
Before Width: | Height: | Size: 5.1 KiB |
BIN
docs/static/mb/crocodile-clips-0.png
vendored
Before Width: | Height: | Size: 9.9 KiB |
BIN
docs/static/mb/crocodile-clips-2.jpg
vendored
Before Width: | Height: | Size: 277 KiB After Width: | Height: | Size: 97 KiB |
BIN
docs/static/mb/csv.png
vendored
Before Width: | Height: | Size: 7.2 KiB After Width: | Height: | Size: 3.1 KiB |
BIN
docs/static/mb/data-4.png
vendored
Before Width: | Height: | Size: 9.6 KiB |
BIN
docs/static/mb/events-0.png
vendored
Before Width: | Height: | Size: 3.9 KiB |
BIN
docs/static/mb/game-library/add-point-to-score-0.png
vendored
Before Width: | Height: | Size: 5.9 KiB |
BIN
docs/static/mb/game-library/game-over-0.png
vendored
Before Width: | Height: | Size: 16 KiB |
BIN
docs/static/mb/game-library/if-on-edge-bounce-0.png
vendored
Before Width: | Height: | Size: 3.3 KiB |
BIN
docs/static/mb/game-library/move-0.png
vendored
Before Width: | Height: | Size: 3.4 KiB |
BIN
docs/static/mb/game-library/pic0.png
vendored
Before Width: | Height: | Size: 1.8 KiB |
BIN
docs/static/mb/game-library/pic1.png
vendored
Before Width: | Height: | Size: 3.9 KiB |
BIN
docs/static/mb/game-library/pic2.png
vendored
Before Width: | Height: | Size: 1.4 KiB |
BIN
docs/static/mb/game-library/pic3.png
vendored
Before Width: | Height: | Size: 6.6 KiB |
BIN
docs/static/mb/game-library/position-0.png
vendored
Before Width: | Height: | Size: 2.0 KiB |
BIN
docs/static/mb/game-library/reports-0.jpg
vendored
Before Width: | Height: | Size: 10 KiB |
BIN
docs/static/mb/game-library/reports-1.jpg
vendored
Before Width: | Height: | Size: 12 KiB |
BIN
docs/static/mb/game-library/reports-2.jpg
vendored
Before Width: | Height: | Size: 12 KiB |
BIN
docs/static/mb/game-library/start-countdown-0.png
vendored
Before Width: | Height: | Size: 17 KiB |
BIN
docs/static/mb/game-library/touching-0.png
vendored
Before Width: | Height: | Size: 2.9 KiB |
BIN
docs/static/mb/game-library/turn-0.png
vendored
Before Width: | Height: | Size: 5.7 KiB |
BIN
docs/static/mb/hourofcode-0.png
vendored
Before Width: | Height: | Size: 2.8 KiB |
BIN
docs/static/mb/lessons/bounce-image-0.png
vendored
Before Width: | Height: | Size: 6.5 KiB |
BIN
docs/static/mb/lessons/bounce-image-1.png
vendored
Before Width: | Height: | Size: 13 KiB |
BIN
docs/static/mb/lessons/bounce-image-2.png
vendored
Before Width: | Height: | Size: 19 KiB |
BIN
docs/static/mb/lessons/seismograph4.png
vendored
Before Width: | Height: | Size: 1.5 KiB |
BIN
docs/static/mb/object-disclaimer-0.png
vendored
Before Width: | Height: | Size: 13 KiB |
BIN
docs/static/mb/object-types-0.png
vendored
Before Width: | Height: | Size: 8.4 KiB |
BIN
docs/static/mb/object-types-1.png
vendored
Before Width: | Height: | Size: 14 KiB |
BIN
docs/static/mb/offline-0.png
vendored
Before Width: | Height: | Size: 12 KiB |
BIN
docs/static/mb/offline-1.png
vendored
Before Width: | Height: | Size: 6.3 KiB |
BIN
docs/static/mb/on-gamepad-button-0.png
vendored
Before Width: | Height: | Size: 3.4 KiB |
BIN
docs/static/mb/on-signal-strength-changed-0.png
vendored
Before Width: | Height: | Size: 6.9 KiB |
BIN
docs/static/mb/plot-leds-0.png
vendored
Before Width: | Height: | Size: 16 KiB |
BIN
docs/static/mb/raise-alert-to-0.png
vendored
Before Width: | Height: | Size: 5.2 KiB |
BIN
docs/static/mb/receive-number-0.png
vendored
Before Width: | Height: | Size: 2.4 KiB |
BIN
docs/static/mb/scroll-image-0.png
vendored
Before Width: | Height: | Size: 6.6 KiB |
BIN
docs/static/mb/show-image-0.png
vendored
Before Width: | Height: | Size: 4.0 KiB |
BIN
docs/static/mb/signal-strength-0.png
vendored
Before Width: | Height: | Size: 2.5 KiB |
BIN
docs/static/mb/simulator-0.png
vendored
Before Width: | Height: | Size: 199 KiB |
BIN
docs/static/mb/string-0.png
vendored
Before Width: | Height: | Size: 2.3 KiB |
BIN
docs/static/mb/tell-camera-to-0.png
vendored
Before Width: | Height: | Size: 5.4 KiB |
BIN
docs/static/mb/tell-microphone-to-0.png
vendored
Before Width: | Height: | Size: 3.9 KiB |
BIN
docs/static/mb/tell-remote-control-to-0.png
vendored
Before Width: | Height: | Size: 4.4 KiB |
BIN
docs/static/mb/toggle-all-0.png
vendored
Before Width: | Height: | Size: 262 KiB |
@ -183,6 +183,8 @@ namespace pxt {
|
||||
//%
|
||||
RefRecord* mkRecord(int reflen, int totallen);
|
||||
//%
|
||||
RefRecord* mkClassInstance(int offset);
|
||||
//%
|
||||
void debugMemLeaks();
|
||||
//%
|
||||
int incr(uint32_t e);
|
||||
|
@ -135,6 +135,7 @@ namespace music {
|
||||
*/
|
||||
//% help=music/play-tone weight=90
|
||||
//% blockId=device_play_note block="play|tone %note=device_note|for %duration=device_beat" icon="\uf025" blockGap=8
|
||||
//% parts="speaker"
|
||||
export function playTone(frequency: number, ms: number): void {
|
||||
pins.analogSetPitchPin(AnalogPin.P0);
|
||||
pins.analogPitch(frequency, ms);
|
||||
@ -146,6 +147,7 @@ namespace music {
|
||||
*/
|
||||
//% help=music/ring-tone weight=80
|
||||
//% blockId=device_ring block="ring tone (Hz)|%note=device_note" icon="\uf025" blockGap=8
|
||||
//% parts="speaker"
|
||||
export function ringTone(frequency: number): void {
|
||||
pins.analogSetPitchPin(AnalogPin.P0);
|
||||
pins.analogPitch(frequency, 0);
|
||||
@ -157,6 +159,7 @@ namespace music {
|
||||
*/
|
||||
//% help=music/rest weight=79
|
||||
//% blockId=device_rest block="rest(ms)|%duration=device_beat"
|
||||
//% parts="speaker"
|
||||
export function rest(ms: number): void {
|
||||
playTone(0, ms);
|
||||
}
|
||||
@ -168,6 +171,7 @@ namespace music {
|
||||
*/
|
||||
//% weight=50 help=music/note-frequency
|
||||
//% blockId=device_note block="%note"
|
||||
//% parts="speaker"
|
||||
//% shim=TD_ID
|
||||
export function noteFrequency(name: Note): number {
|
||||
return name;
|
||||
@ -182,6 +186,7 @@ namespace music {
|
||||
*/
|
||||
//% help=music/beat weight=49
|
||||
//% blockId=device_beat block="%fraction|beat"
|
||||
//% parts="speaker"
|
||||
export function beat(fraction?: BeatFraction): number {
|
||||
init();
|
||||
if (fraction == null) fraction = BeatFraction.Whole;
|
||||
@ -198,6 +203,7 @@ namespace music {
|
||||
*/
|
||||
//% help=music/tempo weight=40
|
||||
//% blockId=device_tempo block="tempo (bpm)" blockGap=8
|
||||
//% parts="speaker"
|
||||
export function tempo(): number {
|
||||
init();
|
||||
return beatsPerMinute;
|
||||
@ -209,8 +215,9 @@ namespace music {
|
||||
*/
|
||||
//% help=music/change-tempo weight=39
|
||||
//% blockId=device_change_tempo block="change tempo by (bpm)|%value" blockGap=8
|
||||
//% parts="speaker"
|
||||
export function changeTempoBy(bpm: number): void {
|
||||
init();
|
||||
init();
|
||||
setTempo(beatsPerMinute + bpm);
|
||||
}
|
||||
|
||||
@ -220,6 +227,7 @@ namespace music {
|
||||
*/
|
||||
//% help=music/set-tempo weight=38
|
||||
//% blockId=device_set_tempo block="set tempo to (bpm)|%value"
|
||||
//% parts="speaker"
|
||||
export function setTempo(bpm: number): void {
|
||||
init();
|
||||
if (bpm > 0) {
|
||||
|
@ -1,50 +0,0 @@
|
||||
# Assignment Operator
|
||||
|
||||
Set the value for local and global variables.
|
||||
|
||||
### @parent js/operators
|
||||
|
||||
|
||||
Set or change the value of a variable
|
||||
|
||||
### Block Editor
|
||||
|
||||

|
||||
|
||||
### Touch Develop
|
||||
|
||||
Use the assignment operator (:=) to set or change the value of a [local variable](/blocks/variables/var) or a [global variable](/js/data).
|
||||
|
||||
### Declare a variable
|
||||
|
||||
Declare a new *local* variable using the [var](/blocks/variables/var) statement and the assignment operator (`:=`). Like this:
|
||||
|
||||
```blocks
|
||||
let num1 = 7
|
||||
let name = "Joe"
|
||||
```
|
||||
|
||||
The variable's name is on the left of the assignment operator (`:=`) and the variable's value is on the right:
|
||||
|
||||
*variable name* `:=` *value*
|
||||
|
||||
See [global variable](/js/data) for info on declaring a global variable.
|
||||
|
||||
### Change a variable
|
||||
|
||||
After a global or local variable is defined, use the assignment operator (`:=`) to change the variable's value.
|
||||
|
||||
```
|
||||
g = 42
|
||||
num1 = 42
|
||||
```
|
||||
|
||||
### Notes
|
||||
|
||||
* Don't confuse the assignment operator `:=` with the equality operator `=`, which is used to compare values.
|
||||
* You can use the assignment operator `:=` with variables of each of the supported [types](/js/types).
|
||||
|
||||
### See also
|
||||
|
||||
[local variables](/reference/variables/var), [global variables](/js/data), [types](/js/types)
|
||||
|
@ -1,118 +0,0 @@
|
||||
# Boolean
|
||||
|
||||
true or false.
|
||||
|
||||
### @parent js/language
|
||||
|
||||
A Boolean has one of two possible values: `true`; `false`. Boolean (logical) operators (*and*, *or*, *not*) take Boolean inputs and yields a Boolean value. Comparison operators on other types ([numbers](/reference/types/number), [strings](/reference/types/string)) yields a Boolean value.
|
||||
|
||||
### Block Editor
|
||||
|
||||
In the Block Editor, the following blocks represent the true and false Boolean values, which can be plugged in anywhere a Boolean value is expected:
|
||||
|
||||

|
||||
|
||||
The next three blocks represent the three Boolean (logic) operators:
|
||||
|
||||

|
||||
|
||||
The next six blocks represent comparison operators that yield a Boolean value. Most comparisons you will do involve [numbers](/reference/types/number):
|
||||
|
||||

|
||||
|
||||
### Touch Develop
|
||||
|
||||
### ~hide
|
||||
|
||||
```
|
||||
let condition = true
|
||||
let condition2 = true
|
||||
```
|
||||
|
||||
### ~
|
||||
|
||||
Boolean values and operators are often used with an [if](/blocks/logic/if) or [while](/js/while) statement to determine which code will execute next. For example:
|
||||
|
||||
```
|
||||
if (condition && condition2) {
|
||||
// This code runs if both `condition` and `condition2` are `true`
|
||||
} else {
|
||||
// This code runs if either `condition` or `condition2` is `false`
|
||||
}
|
||||
```
|
||||
|
||||
### Functions that return a Boolean
|
||||
|
||||
Some functions return a Boolean value, which you can store in a Boolean variable. For example, the following code gets the on/off state of `point (1, 2)` and stores this in the Boolean variable named `on`. Then the code clears the screen if `on` is `true`:
|
||||
|
||||
```
|
||||
let on = led.point(1, 2)
|
||||
if (on) {
|
||||
basic.clearScreen()
|
||||
}
|
||||
```
|
||||
|
||||
### Boolean operators
|
||||
|
||||
Boolean operators take Boolean inputs and evaluate to a Boolean output:
|
||||
|
||||
### Conjunction: `A and B`
|
||||
|
||||
`A and B` evaluates to `true` if-and-only-if both A and B are true:
|
||||
|
||||
- `false and false` = `false`
|
||||
|
||||
- `false and true` = `false`
|
||||
|
||||
- `true and false` = `false`
|
||||
|
||||
- `true and true` = `true`
|
||||
|
||||
### Disjunction: `A or B`
|
||||
|
||||
`A or B` evaluates to `true` if-and-only-if either A is true or B is true:
|
||||
|
||||
- `false or false` = `false`
|
||||
|
||||
- `false or true` = `true`
|
||||
|
||||
- `true or false` = `true`
|
||||
|
||||
- `true or true` = `true`
|
||||
|
||||
### Negation: `not A`
|
||||
|
||||
`not A` evaluates to the opposite (negation) of A:
|
||||
|
||||
* `not false` = `true`
|
||||
* `not true` = `false`
|
||||
|
||||
### Example
|
||||
|
||||
This example turns on LED `3 , 3`, if LEDs `1 , 1` and `2 , 2` are both on:
|
||||
|
||||
```
|
||||
if (led.point(1, 1) && led.point(2, 2)) {
|
||||
led.plot(3, 3)
|
||||
}
|
||||
```
|
||||
|
||||
### Comparisons of numbers and strings
|
||||
|
||||
When you compare two Numbers, you get a Boolean value, such as the comparison `x < 5` in the code below:
|
||||
|
||||
```
|
||||
let x = Math.random(10)
|
||||
if (x < 5) {
|
||||
basic.showString("Low", 150)
|
||||
} else {
|
||||
basic.showString("High", 150)
|
||||
}
|
||||
```
|
||||
|
||||
See the documentation on [Numbers](/reference/types/number) for more information on comparing two Numbers. You can also [compare strings](/reference/types/string-functions) using the `equals` function.
|
||||
|
||||
### See also
|
||||
|
||||
[if](/reference/logic/if), [while](/js/while), [number](/reference/types/number)
|
||||
|
@ -1,112 +0,0 @@
|
||||
# Global Variables
|
||||
|
||||
How to define and use global variables.
|
||||
|
||||
### @parent js/language
|
||||
|
||||
A variable is a place where you can store data so that you can use it later in your code. A *global* variable is accessible from every point in your code.
|
||||
|
||||
### Block Editor
|
||||
|
||||
In the Block Editor, all variables are global. See [Block Editor](/blocks/editor) for info on creating global variables in a Block Editor script. The following block is used to set (assign) global variable's value:
|
||||
|
||||

|
||||
|
||||
The block below retrieves (gets) the current value of a global variable:
|
||||
|
||||

|
||||
|
||||
### Touch Develop
|
||||
|
||||
In Touch Develop variables are either [global](/js/data) or [local](/reference/variables/var). Variables have a name, a [type](/js/types), and value:
|
||||
|
||||
* the *name* is how you'll refer to the variable
|
||||
* the *type* refers to the kind of value a variable can store
|
||||
* the *value* refers to what's stored in the variable
|
||||
|
||||
[Global variables](/js/data) are variables that are available throughout your script. Unlike [local variables](/reference/variables/var), global variables are accessible across functions and in nested code blocks.
|
||||
|
||||
### Create a global variable
|
||||
|
||||
To create a new global variable:
|
||||
|
||||
1. In the Touch Develop [editor](/js/editor), click `script` (to the right of the search box).
|
||||
|
||||
2. Click `+` **add new**.
|
||||
|
||||
3. Click `data->` **data** and then choose a [type](/js/types).
|
||||
|
||||
4. Enter a name for your global variable and click **OK**.
|
||||
|
||||
### Set and use a global variable
|
||||
|
||||
To use a global variable that you've declared (using steps above):
|
||||
|
||||
1. In the Touch Develop [editor](/js/editor), click `data-> ` **data** or `data->` + *variable name*.
|
||||
|
||||
2. Click `:=` (assignment).
|
||||
|
||||
2. Click on the right-side of `:=` and type or click what you want to store in the variable.
|
||||
|
||||
Your code should look something like this:
|
||||
|
||||
// global number variable
|
||||
|
||||
```
|
||||
counter = 2
|
||||
```
|
||||
|
||||
// global string variable
|
||||
|
||||
```
|
||||
name2 = "Mike"
|
||||
```
|
||||
|
||||
// global boolean variable
|
||||
|
||||
```
|
||||
bool = true
|
||||
```
|
||||
|
||||
(for info on creating image variables, see [Image](/reference/image/image))
|
||||
|
||||
Once you've defined a variable and set it's initial value, use the variable's name whenever you need what's stored in the variable. For example, the following code gets the value stored in the global `counter` variable and shows it on the screen:
|
||||
|
||||
```
|
||||
basic.showNumber(counter, 100)
|
||||
```
|
||||
|
||||
To change the contents of a variable use the [assignment operator](/reference/variables/assign) `:=`. The following code increments `counter` by 10:
|
||||
|
||||
```
|
||||
counter = counter + 10
|
||||
```
|
||||
|
||||
### Promote, demote, and extract
|
||||
|
||||
To **promote** a local variable to a global variable:
|
||||
|
||||
* select the local variable name and click `promote to data`. The [var](/reference/variables/var) keyword changes to the data symbol `data->`.
|
||||
|
||||
To **demote** a global variable to a local variable:
|
||||
|
||||
* select the global variable name and click `demote to var`
|
||||
|
||||
To **extract** the content of a global variable to a local variable:
|
||||
|
||||
* select the global variable name and click `extract to var`
|
||||
|
||||
### See your global variables
|
||||
|
||||
To see a list of the global variables in your script:
|
||||
|
||||
* click `script` (along the top) and scroll down to the **vars** heading
|
||||
|
||||
### Lessons
|
||||
|
||||
[counter](/lessons/counter), [rotation animation](/lessons/rotation-animation), [digital pet](/lessons/digital-pet), [offset image](/lessons/offset-image)
|
||||
|
||||
### See also
|
||||
|
||||
[local variables](/reference/variables/var), [types](/js/types), [assignment operator](/reference/variables/assign)
|
||||
|
@ -1,112 +0,0 @@
|
||||
# Touch Develop Editor
|
||||
|
||||
The Touch Develop editor.
|
||||
|
||||
### @parent js/contents
|
||||
|
||||
|
||||
The Touch Develop editor is where you write and test your code. If you're new to Touch Develop, check out the [Touch Develop editor video](/getting-started/touchdevelop-editor).
|
||||
|
||||
To create a new Touch Develop script:
|
||||
|
||||
1. Go to the micro:bit website and click **Create Code** (along the top).
|
||||
|
||||
2. Under the Touch Develop editor heading, click **New project**.
|
||||
|
||||
3. Type a name for your script and click **create**.
|
||||
|
||||
An empty script with a [function](/js/function) called `main` is created.
|
||||
|
||||
## The Editor Menu Bar
|
||||
|
||||
The Touch Develop editor has a bar of options above the code area:
|
||||
|
||||

|
||||
|
||||
* `my scripts` takes you back to a list of your scripts (My Scripts). The open script is automatically saved (in the cloud) when you leave the editor.
|
||||
* `run` executes your script, showing you the results on the on-screen micro:bit device. See [run scripts in the browser](/js/simulator) for more about this.
|
||||
* `compile` sends your script to an ARM compiler, which creates a file that you can run on your micro:bit. See [run scripts on your micro:bit](/device/usb) for more info.
|
||||
* `undo` undoes changes that you made to your script.
|
||||
* `search code...` search for functions in libraries such as the micro:bit library.
|
||||
* `script` opens script options, where you can do things like publish and preview. See **script options** below.
|
||||
|
||||
Many of the above buttons aren't much use until you've written some code, so let's move on to the Code Keyboard.
|
||||
|
||||
## Code Keyboard
|
||||
|
||||
The Code Keyboard makes it easy to write code on a touch screen device or by using your mouse. You can also type code using your computer keyboard if you know what function or statement you want (see [Touch Develop documentation](/js/contents) for a complete list).
|
||||
|
||||
To open the Code Keyboard, click on a line of code:
|
||||
|
||||

|
||||
|
||||
An on-screen keyboard appears, with buttons that vary depending on what's selected.
|
||||
|
||||
### Statements
|
||||
|
||||
The first row of the Code Keyboard has Touch Develop [statements](/js/statements) that you can insert into your code. These buttons are blue and include things like [var](/reference/variables/var), [if](/reference/logic/if), [for](/reference/loops/for) , and [while](/js/while). Click `more` to see additional statements.
|
||||
|
||||
### The BBC micro:bit, math, and code buttons
|
||||
|
||||
* `micro:bit`: click to see all the [micro:bit functions](/js/contents); click `more` to scroll left to right. The micro:bit functions are also grouped together behind the following category buttons: `basic`, `control`, `input`, `image`, `led`, and`pins`
|
||||
* `code`: click to access functions you've written (see [call a function](/js/call) for more info)
|
||||
* `math`: click to see [math functions](/js/math); such as `abs` and `round`
|
||||
* `bits`: click to see functions for bit-level manipulation of integers
|
||||
|
||||
### Editing code: add, copy, paste, and cut
|
||||
|
||||
In the coding area...
|
||||
|
||||
* **add**: to add a new line, click on a line and then click a **+** to add a new line above or below the current line
|
||||
* **copy, paste, cut**: click on a line then click **copy** or **cut**. Then click on a new line, and click **paste**.
|
||||
|
||||
### Block editing
|
||||
|
||||
To copy, cut, or comment out a block of code (more than one line):
|
||||
|
||||
1. Click on a line of code.
|
||||
|
||||
2. Press and hold the `Shift` key, and then press the `Up arrow` or `Down arrow` key on your keyboard (this selects multiple lines).
|
||||
|
||||
3. Choose a block editing option like copy, cut, or [comment out](/js/comment).
|
||||
|
||||
### Script options
|
||||
|
||||
Click `script` (in the upper-right corner) to open the script options:
|
||||
|
||||

|
||||
|
||||
Here you'll find options like...
|
||||
|
||||
* `script properties`: the script name, description, and whether or not the script is a library (more info below)
|
||||
* `publish`: share a script with other users by [publishing](/js/publishing) it
|
||||
* `share`: share a link to a published script (see [publish as script](/js/publishing) for more info)
|
||||
* `preview`: preview a documentation script
|
||||
* `+` `add new`: add a new [function](/js/function), [global variable](/js/data), picture, or library to a script
|
||||
* *code*: the functions in your script; click a function to open it in the editor
|
||||
* *global vars*: the [global variables](/js/data) in your script; click a variable to go to that variable
|
||||
* *libraries*: the libraries added to your script
|
||||
* *art*: picture and video resources added to your script
|
||||
|
||||
### Script properties
|
||||
|
||||
To edit a script's properties, click `script` (in the upper-right corner), and then click the script name or script properties.
|
||||
|
||||

|
||||
|
||||
* `name`: the script's name (60 character limit)
|
||||
* `description`: a description of what your script does along with #hashtags for search (for example, #game or #maker). Hashtags are especially important if you publish your script (200 character limit).
|
||||
* `this script is a library`: click this check box to turn a script into a library
|
||||
|
||||
### Comments
|
||||
|
||||
Comments are notes within your scripts. To learn how to insert comments into your scripts, see [Comments](/js/comment). You can format your comments using [markdown syntax](/js/markdown).
|
||||
|
||||
### Share your scripts
|
||||
|
||||
Share your scripts with other people by publishing them. See [publish a script](/js/publishing) for more info.
|
||||
|
||||
### See also
|
||||
|
||||
[publish a script](/js/publishing), [Touch Develop documentation](/js/contents)
|
||||
|
@ -9,8 +9,6 @@ Repeat code a fixed number of times.
|
||||
|
||||
### Block Editor
|
||||
|
||||

|
||||
|
||||
The Block Editor *for* loop is different than the Touch Develop *for* loop in an important way. The above for loop will iterate *five* times, with the loop variable *i* taking on values 0, 1, 2, 3, and 4. The Touch Develop for loop shown below will iterate four times:
|
||||
|
||||
```
|
||||
|
@ -34,7 +34,6 @@ Overview of Touch Develop lessons for the BBC micro:bit.
|
||||
|
||||
### ~hide
|
||||
|
||||
* [Bounce image](/lessons/bounce-image), scroll an image across the screen on shake
|
||||
* [Magic logo](/lessons/magic-logo), show an image on logo up
|
||||
* [Glowing sword](/lessons/glowing-sword), make a glowing sword with fade in and fade out
|
||||
|
||||
|
@ -1,33 +0,0 @@
|
||||
# Add Points to Score
|
||||
|
||||
The game library supports simple single-player time-based games. The player will ** add points to score**.
|
||||
|
||||
The code below shows a simple game where the user gets to press the button ``A`` as much times as possible in 10 seconds.
|
||||
|
||||
```blocks
|
||||
input.onButtonPressed(Button.A, () => {
|
||||
game.addScore(1)
|
||||
})
|
||||
game.startCountdown(10000)
|
||||
```
|
||||
|
||||
### Score
|
||||
|
||||
When a player achieves a goal, you can increase the game score
|
||||
|
||||
* add score points to the current score
|
||||
|
||||
```
|
||||
export function addScore(points: number)
|
||||
```
|
||||
|
||||
* get the current score value
|
||||
|
||||
```
|
||||
export function score() : number
|
||||
```
|
||||
|
||||
### Lessons
|
||||
|
||||
[bop it](/lessons/bop-it) | [game of chance](/lessons/game-of-chance) | [game counter](/lessons/game-counter)
|
||||
|
@ -1,31 +0,0 @@
|
||||
# Change Score By
|
||||
|
||||
The game library supports simple single-player time-based games. The player will ** add points to score**.
|
||||
|
||||
```blocks
|
||||
input.onButtonPressed(Button.A, () => {
|
||||
game.addScore(1)
|
||||
})
|
||||
game.startCountdown(10000)
|
||||
```
|
||||
|
||||
### Score
|
||||
|
||||
When a player achieves a goal, you can increase the game score
|
||||
|
||||
* add score points to the current score
|
||||
|
||||
```
|
||||
export function addScore(points: number)
|
||||
```
|
||||
|
||||
* get the current score value
|
||||
|
||||
```
|
||||
export function score() : number
|
||||
```
|
||||
|
||||
### Lessons
|
||||
|
||||
[bop it](/lessons/bop-it) | [game of chance](/lessons/game-of-chance) | [game counter](/lessons/game-counter)
|
||||
|
@ -1,12 +0,0 @@
|
||||
# Game Over
|
||||
|
||||
You can end the game by calling the `game -> game over` function:
|
||||
|
||||
```blocks
|
||||
game.gameOver()
|
||||
```
|
||||
|
||||
### Lessons
|
||||
|
||||
[game of chance](/lessons/game-of-chance)
|
||||
|
@ -1,59 +0,0 @@
|
||||
# Score
|
||||
|
||||
The game library #docs
|
||||
|
||||
The game library supports simple single-player games. The player has a **score**.
|
||||
|
||||
## Block Editor
|
||||
|
||||
The code below shows a simple game where the user gets to press the button ``A`` and adds 1 point to score that will be displayed on the BBC micro:bit screen
|
||||
|
||||

|
||||
|
||||
## Touch Develop
|
||||
|
||||
The code below shows a simple game where the user gets to press the button ``A`` as much times as possible in 10 seconds.
|
||||
|
||||
```
|
||||
input.onButtonPressed(Button.A, () => {
|
||||
game.addScore(1)
|
||||
})
|
||||
game.startCountdown(10000)
|
||||
```
|
||||
|
||||
### Score
|
||||
|
||||
When a player achieves a goal, you can increase the game score
|
||||
|
||||
* add score points to the current score
|
||||
|
||||
```
|
||||
export function addScore(points: number)
|
||||
```
|
||||
|
||||
* set the current score to a particular value.
|
||||
|
||||
```
|
||||
export function setScore(value: number)
|
||||
```
|
||||
|
||||
* get the current score value
|
||||
|
||||
```
|
||||
export function score() : number
|
||||
```
|
||||
|
||||
### Countdown
|
||||
|
||||
If your game has a time limit, you can start a countdown in which case `game->current time` returns the remaining time.
|
||||
|
||||
* start a countdown with the maximum duration of the game in milliseconds.
|
||||
|
||||
```
|
||||
export function startCountdown(ms: number)
|
||||
```
|
||||
|
||||
### Lessons
|
||||
|
||||
[bop it](/lessons/bop-it) | [game of chance](/lessons/game-of-chance) | [game counter](/lessons/game-counter)
|
||||
|
@ -1,57 +0,0 @@
|
||||
# Start Countdown
|
||||
|
||||
The game library #docs
|
||||
|
||||
The game library supports simple single-player time-based games. The general goal of a game will be to achieve a top score before time runs out of time.
|
||||
|
||||
## Block Editor
|
||||
|
||||

|
||||
|
||||
## Touch Develop
|
||||
|
||||
The code below shows a simple game where the user gets to press the button ``A`` as much times as possible in 10 seconds.
|
||||
|
||||
```
|
||||
input.onButtonPressed(Button.A, () => {
|
||||
game.addScore(1)
|
||||
})
|
||||
game.startCountdown(10000)
|
||||
```
|
||||
|
||||
### Score
|
||||
|
||||
When a player achieves a goal, you can increase the game score
|
||||
|
||||
* add score points to the current score
|
||||
|
||||
```
|
||||
export function addScore(points: number)
|
||||
```
|
||||
|
||||
* set the current score to a particular value.
|
||||
|
||||
```
|
||||
export function setScore(value: number)
|
||||
```
|
||||
|
||||
* get the current score value
|
||||
|
||||
```
|
||||
export function score() : number
|
||||
```
|
||||
|
||||
### Countdown
|
||||
|
||||
If your game has a time limit, you can start a countdown in which case `game->current time` returns the remaining time.
|
||||
|
||||
* start a countdown with the maximum duration of the game in milliseconds.
|
||||
|
||||
```
|
||||
export function startCountdown(ms: number)
|
||||
```
|
||||
|
||||
### Lessons
|
||||
|
||||
[bop it](/lessons/bop-it) | [game of chance](/lessons/game-of-chance) | [game counter](/lessons/game-counter)
|
||||
|
@ -17,7 +17,6 @@ Overview of Games for the BBC micro:bit.
|
||||
* [Lucky 7](/lessons/lucky-7), show a number on the LED screen with show number
|
||||
* [Snowflake fall](/lessons/snowflake-fall), repeat an animation with forever
|
||||
* [Answering machine](/lessons/answering-machine), show a text message with show string
|
||||
* [Bounce image](/lessons/bounce-image), scroll an image across the screen on shake
|
||||
* [Magic logo](/lessons/magic-logo), show an image on logo up
|
||||
* [Screen wipe](/lessons/screen-wipe), turn off the LEDs with clear screen
|
||||
* [Blink](/lessons/blink), turn an LED on and off with plot
|
||||
|
@ -9,8 +9,6 @@ Conditionally run code depending on whether a [Boolean](/reference/types/boolean
|
||||
|
||||
### Block Editor
|
||||
|
||||

|
||||
|
||||
In the Block Editor, click on the dark blue gear icon (see above) to add an *else* or *if* to the current block.
|
||||
|
||||
### Touch Develop
|
||||
|
@ -62,7 +62,6 @@ Images that you create in the [Touch Develop editor](/js/editor) are [local vari
|
||||
|
||||
### Lessons
|
||||
|
||||
* [bounce image ](/lessons/bounce-image)
|
||||
* [offset image](/lessons/offset-image)
|
||||
|
||||
### See also
|
||||
|
@ -22,12 +22,6 @@ Overview of Touch Develop lessons for the BBC micro:bit.
|
||||
* [Answering Machine](/lessons/answering-machine), show a text message with show string
|
||||
* [Snowflake Fall](/lessons/snowflake-fall), repeat an animation with forever
|
||||
|
||||
### ~hide
|
||||
|
||||
* [Bounce Image](/lessons/bounce-image), scroll an image across the screen on shake
|
||||
|
||||
### ~
|
||||
|
||||
* [Magic Logo](/lessons/magic-logo), show an image on logo up
|
||||
* [Screen Wipe](/lessons/screen-wipe), turn off the LEDs with clear screen
|
||||
* [Flashing Heart](/lessons/flashing-heart), display images with a pause
|
||||
|
@ -1,106 +0,0 @@
|
||||
# bounce image lesson
|
||||
|
||||
scroll an image across the screen.
|
||||
|
||||
## Topic
|
||||
|
||||
Basic- Show Animation
|
||||
|
||||
## Quick Links
|
||||
|
||||
* [tutorial](/lessons/bounce-image/tutorial)
|
||||
* [quiz](/lessons/bounce-image/quiz)
|
||||
* [quiz answers](/lessons/bounce-image/quiz-answers)
|
||||
* [challenges](/lessons/bounce-image/challenges)
|
||||
|
||||
## Class
|
||||
|
||||
Year 7
|
||||
|
||||
## Prior learning / place of lesson in scheme of work
|
||||
|
||||
Learn how to creating an **animation**, `basic->show animation` to display a series of images. We will be learning how to create a counter app using a forever loop, the input on shake, and show animation.
|
||||
|
||||
## What the teacher needs to know / QuickStart Computing Glossary
|
||||
|
||||
* Algorithm: An unambiguous set of rules or a precise step-by-step guide to solve a problem or achieve a particular objective.
|
||||
* Command: An instruction for the computer to execute, written in a particular programming language.
|
||||
* Hardware: The physical systems and components of digital devices; see also software.
|
||||
* Input: Data provided to a computer system, such as via a keyboard, mouse, microphone, camera or physical sensors.
|
||||
* Loop: A block of code repeated automatically under the program’s control.
|
||||
* Output: The information produced by a computer system for its user, typically on a screen, through speakers or on a printer, but possibly through the control of motors in physical systems.
|
||||
* Programmable toys: Robots designed for children to use, accepting input, storing short sequences of simple instructions and moving according to this stored program.
|
||||
* Program: A stored set of instructions encoded in a language understood by the computer that does some form of computation, processing input and/or stored data to generate output.
|
||||
* Selection: A programming construct in which one section of code or another is executed depending on whether a particular condition is met.
|
||||
* Sequence: To place program instructions in order, with each executed one after the other.
|
||||
* Simulation: Using a computer to model the state and behaviour of real-world (or imaginary) systems, including physical or social systems; an integral part of most computer games.
|
||||
|
||||
## Documentation
|
||||
|
||||
* **forever** : [read more...](/reference/basic/forever)
|
||||
* **show animation** : [read more...](/reference/basic/show-animation)
|
||||
* **on shake** : [read more...](/reference/input/on-gesture)
|
||||
|
||||
## Resources
|
||||
|
||||
* Activity: [tutorial](/lessons/bounce-image/tutorial)
|
||||
* Activity: [quiz](/lessons/bounce-image/quiz)
|
||||
* Extended Activity: [challenges](/lessons/bounce-image/challenges)
|
||||
|
||||
## Objectives
|
||||
|
||||
* learn how to repeat code in the background forever
|
||||
* learn how to show a series of image frames on the LED screen, pausing the specified time after each frame
|
||||
* learn how to run code when the micro:bit is shaken; when running code in the web browser, moving the mouse quickly simulates shaking
|
||||
|
||||
## Links to the National Curriculum Programmes of Study for Computing
|
||||
|
||||
## Progression Pathways / Computational Thinking Framework
|
||||
|
||||
#### Algorithms
|
||||
|
||||
* Uses diagrams to express solutions.(AB)
|
||||
* Uses logical reasoning to predict outputs, showing an awareness of inputs (AL)
|
||||
* Understands that iteration is the repetition of a process such as a loop (AL)
|
||||
* Represents solutions using a structured notation (AL) (AB)
|
||||
|
||||
#### Programming & Development
|
||||
|
||||
* Creates programs that implement algorithms to achieve given goals (AL)
|
||||
* Selects the appropriate data types(AL) (AB)
|
||||
|
||||
#### Hardware & Processing
|
||||
|
||||
* Knows that computers collect data from various input devices, including sensors and application software (AB)
|
||||
|
||||
#### Communication Networks
|
||||
|
||||
* Demonstrates responsible use of technologies and online services, and knows a range of ways to report concerns Understands how search engines rank search results (AL)
|
||||
|
||||
#### Information Technology
|
||||
|
||||
* Collects, organizes, and presents data and information in digital content (AB)
|
||||
* Makes appropriate improvements to solutions based on feedback received, and can comment on the success of the solution (EV)
|
||||
* Recognises ethical issues surrounding the application of information technology beyond school.
|
||||
|
||||
Computational Thinking Concept: AB = Abstraction; DE = Decomposition; AL = Algorithmic Thinking; EV = Evaluation; GE = Generalisation
|
||||
|
||||
## Activity
|
||||
|
||||
* time: 20 min.
|
||||
* [tutorial](/lessons/bounce-image/tutorial)
|
||||
* [quiz](/lessons/bounce-image/quiz)
|
||||
|
||||
## Extended Activity
|
||||
|
||||
* time: 20 min.
|
||||
* [challenges](/lessons/bounce-image/challenges)
|
||||
|
||||
## Homework
|
||||
|
||||
* Extended Activity: [challenges](/lessons/bounce-image/challenges)
|
||||
|
||||
## Intended follow on
|
||||
|
||||
Publish script to the classroom.
|
||||
|
@ -1,86 +0,0 @@
|
||||
# bounce image challenges
|
||||
|
||||
Coding challenges for the bounce image tutorial. #docs
|
||||
|
||||
## Before we get started
|
||||
|
||||
Complete the following guided tutorial:
|
||||
|
||||
* [tutorial](/lessons/bounce-image/tutorial)
|
||||
|
||||
At the end of the tutorial, click `keep editing`. Your code should look like this:
|
||||
|
||||
```
|
||||
basic.forever(() => {
|
||||
basic.showAnimation(`
|
||||
# . . . . . # . . . . . # . . . . . # . . . . . #
|
||||
# . . . . . # . . . . . # . . . . . # . . . . . #
|
||||
# . . . . . # . . . . . # . . . . . # . . . . . #
|
||||
# . . . . . # . . . . . # . . . . . # . . . . . #
|
||||
# . . . . . # . . . . . # . . . . . # . . . . . #
|
||||
`, 200)
|
||||
})
|
||||
```
|
||||
|
||||
### Challenge 1
|
||||
|
||||
Now, let's add frames to reverse the animation so it looks like the bar is bouncing off the right edge of the display.
|
||||
|
||||
```
|
||||
basic.forever(() => {
|
||||
basic.showAnimation(`
|
||||
# . . . . . # . . . . . # . . . . . # . . . . . # . . . # . . . # . . . # . . . # . . . .
|
||||
# . . . . . # . . . . . # . . . . . # . . . . . # . . . # . . . # . . . # . . . # . . . .
|
||||
# . . . . . # . . . . . # . . . . . # . . . . . # . . . # . . . # . . . # . . . # . . . .
|
||||
# . . . . . # . . . . . # . . . . . # . . . . . # . . . # . . . # . . . # . . . # . . . .
|
||||
# . . . . . # . . . . . # . . . . . # . . . . . # . . . # . . . # . . . # . . . # . . . .
|
||||
`, 200) // ***
|
||||
})
|
||||
```
|
||||
|
||||
* Run the code to see if it works as expected.
|
||||
|
||||
### Challenge 2
|
||||
|
||||
Let's add a condition for on shake!
|
||||
|
||||
```
|
||||
basic.forever(() => {
|
||||
basic.showAnimation(`
|
||||
# . . . . . # . . . . . # . . . . . # . . . . . # . . . # . . . # . . . # . . . # . . . .
|
||||
# . . . . . # . . . . . # . . . . . # . . . . . # . . . # . . . # . . . # . . . # . . . .
|
||||
# . . . . . # . . . . . # . . . . . # . . . . . # . . . # . . . # . . . # . . . # . . . .
|
||||
# . . . . . # . . . . . # . . . . . # . . . . . # . . . # . . . # . . . # . . . # . . . .
|
||||
# . . . . . # . . . . . # . . . . . # . . . . . # . . . # . . . # . . . # . . . # . . . .
|
||||
`, 200)
|
||||
})
|
||||
input.onGesture(Gesture.Shake, () => {
|
||||
}) // ***
|
||||
```
|
||||
|
||||
### Challenge 3
|
||||
|
||||
When the BBC micro:bit is shaken we want to show a new animation. Here is an example, but you can create your own. Be creative!
|
||||
|
||||
```
|
||||
basic.forever(() => {
|
||||
basic.showAnimation(`
|
||||
# . . . . . # . . . . . # . . . . . # . . . . . # . . . # . . . # . . . # . . . # . . . .
|
||||
# . . . . . # . . . . . # . . . . . # . . . . . # . . . # . . . # . . . # . . . # . . . .
|
||||
# . . . . . # . . . . . # . . . . . # . . . . . # . . . # . . . # . . . # . . . # . . . .
|
||||
# . . . . . # . . . . . # . . . . . # . . . . . # . . . # . . . # . . . # . . . # . . . .
|
||||
# . . . . . # . . . . . # . . . . . # . . . . . # . . . # . . . # . . . # . . . # . . . .
|
||||
`, 200)
|
||||
})
|
||||
input.onGesture(Gesture.Shake, () => {
|
||||
basic.showAnimation(`
|
||||
. . . . . . . . . . # # # # # . . . . . . . . . .
|
||||
. . . . . . # # # . # # # # # . # # # . . . . . .
|
||||
. . # . . . # # # . # # # # # . # # # . . . # . .
|
||||
. . . . . . # # # . # # # # # . # # # . . . . . .
|
||||
. . . . . . . . . . # # # # # . . . . . . . . . .
|
||||
`, 200) // ***
|
||||
})
|
||||
```
|
||||
|
||||
* Run the code to see if it works as expected.
|
@ -1,60 +0,0 @@
|
||||
# bounce image quiz answers
|
||||
|
||||
scroll an image on the BBC micro:bit.
|
||||
|
||||
This is the answer key for the [bounce image quiz](/lessons/bounce-image/quiz).
|
||||
|
||||
## 1. What does it mean to 'add frames' ?
|
||||
|
||||
Adding frames modifies the animation by including more still images in each animation.
|
||||
|
||||
## 2. Write the code that will display this animation.
|
||||
|
||||

|
||||
|
||||
<br/>
|
||||
|
||||
```
|
||||
basic.showAnimation(`
|
||||
# . . . .
|
||||
# . . . .
|
||||
# . . . .
|
||||
# . . . .
|
||||
# . . . .
|
||||
`, 400)
|
||||
```
|
||||
|
||||
## 3. Write the code that will display this animation with two frames.
|
||||
|
||||

|
||||
|
||||
<br/>
|
||||
|
||||
```
|
||||
basic.showAnimation(`
|
||||
# . . . . . # . . .
|
||||
# . . . . . # . . .
|
||||
# . . . . . # . . .
|
||||
# . . . . . # . . .
|
||||
# . . . . . # . . .
|
||||
`, 400)
|
||||
```
|
||||
|
||||
## 4. Write the code that will display this animation with three frames.
|
||||
|
||||

|
||||
|
||||
<br/>
|
||||
|
||||
```
|
||||
basic.showAnimation(`
|
||||
# . . . . . # . . . . . # . .
|
||||
# . . . . . # . . . . . # . .
|
||||
# . . . . . # . . . . . # . .
|
||||
# . . . . . # . . . . . # . .
|
||||
# . . . . . # . . . . . # . .
|
||||
`, 400)
|
||||
```
|
||||
|
||||
<br/>
|
||||
|
@ -1,40 +0,0 @@
|
||||
# bounce image quiz
|
||||
|
||||
scroll an image on the BBC micro:bit.
|
||||
|
||||
## Name
|
||||
|
||||
## Directions
|
||||
|
||||
Use this document to guide your work in the [bounce image tutorial](/lessons/bounce-image/tutorial) !
|
||||
|
||||
Answer the questions while completing the tutorial. Pay attention to the dialogues!
|
||||
|
||||
## 1. What does it mean to 'add frames' ?
|
||||
|
||||
<br/>
|
||||
|
||||
<br/>
|
||||
|
||||
## 2. Write the code that will display this animation.
|
||||
|
||||

|
||||
|
||||
<br/>
|
||||
|
||||
## 3. Write the code that will display this animation with two frames.
|
||||
|
||||

|
||||
|
||||
<br/>
|
||||
|
||||
<br/>
|
||||
|
||||
## 4. Write the code that will display this animation with three frames.
|
||||
|
||||

|
||||
|
||||
<br/>
|
||||
|
||||
<br/>
|
||||
|
@ -8,7 +8,6 @@ While you're writing and testing your scripts, you'll mostly be running scripts
|
||||
|
||||
When you click `run main` in the Touch Develop editor, your code executes and the results are simulated on-screen, using an image of the BBC micro:bit device, like this:
|
||||
|
||||

|
||||
|
||||
In the picture above, [plot image](/reference/led/plot-image) create a heart image that appears on the BBC micro:bit simulator.
|
||||
|
||||
|
@ -17,7 +17,6 @@ let condition = false
|
||||
|
||||
### Block Editor
|
||||
|
||||

|
||||
|
||||
### Touch Develop
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "pxt-microbit",
|
||||
"version": "0.3.56",
|
||||
"version": "0.3.64",
|
||||
"description": "micro:bit target for PXT",
|
||||
"keywords": [
|
||||
"JavaScript",
|
||||
@ -29,6 +29,6 @@
|
||||
"typescript": "^1.8.7"
|
||||
},
|
||||
"dependencies": {
|
||||
"pxt-core": "0.3.65"
|
||||
"pxt-core": "0.3.74"
|
||||
}
|
||||
}
|
||||
|
@ -75,13 +75,23 @@
|
||||
},
|
||||
"simulator": {
|
||||
"autoRun": true,
|
||||
"aspectRatio": 1.22
|
||||
"aspectRatio": 1.22,
|
||||
"partsAspectRatio": 0.69,
|
||||
"builtinParts": {
|
||||
"accelerometer": true,
|
||||
"buttonpair": true,
|
||||
"ledmatrix": true,
|
||||
"speaker": true,
|
||||
"bluetooth": true,
|
||||
"thermometer": true,
|
||||
"compass": true
|
||||
}
|
||||
},
|
||||
"compileService": {
|
||||
"yottaTarget": "bbc-microbit-classic-gcc",
|
||||
"yottaCorePackage": "pxt-microbit-core",
|
||||
"githubCorePackage": "microsoft/pxt-microbit-core",
|
||||
"gittag": "v0.4.1",
|
||||
"gittag": "v0.4.2",
|
||||
"serviceId": "ws"
|
||||
},
|
||||
"serial": {
|
||||
@ -136,4 +146,4 @@
|
||||
"userVoiceApiKey": "WEkkIGaj1WtJnSUF59iwaA",
|
||||
"userVoiceForumId": 402381
|
||||
}
|
||||
}
|
||||
}
|
432
sim/allocator.ts
Normal file
@ -0,0 +1,432 @@
|
||||
|
||||
namespace pxsim {
|
||||
export interface AllocatorOpts {
|
||||
boardDef: BoardDefinition,
|
||||
cmpDefs: Map<PartDefinition>,
|
||||
fnArgs: any,
|
||||
getBBCoord: (loc: BBRowCol) => visuals.Coord,
|
||||
cmpList: string[]
|
||||
};
|
||||
export interface AllocatorResult {
|
||||
powerWires: WireInst[],
|
||||
components: CmpAndWireInst[]
|
||||
}
|
||||
|
||||
export interface CmpAndWireInst {
|
||||
component: CmpInst,
|
||||
wires: WireInst[]
|
||||
}
|
||||
export interface CmpInst {
|
||||
name: string,
|
||||
breadboardStartColumn: number,
|
||||
breadboardStartRow: string,
|
||||
assemblyStep: number,
|
||||
visual: string | PartVisualDefinition,
|
||||
microbitPins: string[],
|
||||
otherArgs?: string[],
|
||||
}
|
||||
export interface WireInst {
|
||||
start: Loc,
|
||||
end: Loc,
|
||||
color: string,
|
||||
assemblyStep: number
|
||||
};
|
||||
interface PartialCmpAlloc {
|
||||
name: string,
|
||||
def: PartDefinition,
|
||||
pinsAssigned: string[],
|
||||
pinsNeeded: number | number[],
|
||||
breadboardColumnsNeeded: number,
|
||||
otherArgs?: string[],
|
||||
}
|
||||
|
||||
interface AllocLocOpts {
|
||||
nearestBBPin?: BBRowCol,
|
||||
startColumn?: number,
|
||||
cmpGPIOPins?: string[],
|
||||
};
|
||||
interface AllocWireOpts {
|
||||
startColumn: number,
|
||||
cmpGPIOPins: string[],
|
||||
}
|
||||
interface AllocBlock {
|
||||
cmpIdx: number,
|
||||
cmpBlkIdx: number,
|
||||
gpioNeeded: number,
|
||||
gpioAssigned: string[]
|
||||
}
|
||||
function copyDoubleArray(a: string[][]) {
|
||||
return a.map(b => b.map(p => p));
|
||||
}
|
||||
function readPin(arg: string): string {
|
||||
U.assert(!!arg, "Invalid pin: " + arg);
|
||||
let pin = arg.split("DigitalPin.")[1];
|
||||
return pin;
|
||||
}
|
||||
function mkReverseMap(map: {[key: string]: string}) {
|
||||
let origKeys: string[] = [];
|
||||
let origVals: string[] = [];
|
||||
for (let key in map) {
|
||||
origKeys.push(key);
|
||||
origVals.push(map[key]);
|
||||
}
|
||||
let newMap: {[key: string]: string} = {};
|
||||
for (let i = 0; i < origKeys.length; i++) {
|
||||
let newKey = origVals[i];
|
||||
let newVal = origKeys[i];
|
||||
newMap[newKey] = newVal;
|
||||
}
|
||||
return newMap;
|
||||
}
|
||||
class Allocator {
|
||||
private opts: AllocatorOpts;
|
||||
private availablePowerPins = {
|
||||
top: {
|
||||
threeVolt: mkRange(26, 51).map(n => <BBRowCol>["+", `${n}`]),
|
||||
ground: mkRange(26, 51).map(n => <BBRowCol>["-", `${n}`]),
|
||||
},
|
||||
bottom: {
|
||||
threeVolt: mkRange(1, 26).map(n => <BBRowCol>["+", `${n}`]),
|
||||
ground: mkRange(1, 26).map(n => <BBRowCol>["-", `${n}`]),
|
||||
},
|
||||
};
|
||||
|
||||
constructor(opts: AllocatorOpts) {
|
||||
this.opts = opts;
|
||||
}
|
||||
|
||||
private allocateLocation(location: WireLocationDefinition, opts: AllocLocOpts): Loc {
|
||||
if (location === "ground" || location === "threeVolt") {
|
||||
U.assert(!!opts.nearestBBPin);
|
||||
let nearLoc = opts.nearestBBPin;
|
||||
let nearestCoord = this.opts.getBBCoord(nearLoc);
|
||||
let firstTopAndBot = [
|
||||
this.availablePowerPins.top.ground[0] || this.availablePowerPins.top.threeVolt[0],
|
||||
this.availablePowerPins.bottom.ground[0] || this.availablePowerPins.bottom.threeVolt[0]
|
||||
].map(loc => {
|
||||
return this.opts.getBBCoord(loc);
|
||||
});
|
||||
if (!firstTopAndBot[0] || !firstTopAndBot[1]) {
|
||||
console.debug(`No more available "${location}" locations!`);
|
||||
//TODO
|
||||
}
|
||||
let nearTop = visuals.findClosestCoordIdx(nearestCoord, firstTopAndBot) == 0;
|
||||
let pins: BBRowCol[];
|
||||
if (nearTop) {
|
||||
if (location === "ground") {
|
||||
pins = this.availablePowerPins.top.ground;
|
||||
} else if (location === "threeVolt") {
|
||||
pins = this.availablePowerPins.top.threeVolt;
|
||||
}
|
||||
} else {
|
||||
if (location === "ground") {
|
||||
pins = this.availablePowerPins.bottom.ground;
|
||||
} else if (location === "threeVolt") {
|
||||
pins = this.availablePowerPins.bottom.threeVolt;
|
||||
}
|
||||
}
|
||||
let pinCoords = pins.map(rowCol => {
|
||||
return this.opts.getBBCoord(rowCol);
|
||||
});
|
||||
let pinIdx = visuals.findClosestCoordIdx(nearestCoord, pinCoords);
|
||||
let pin = pins[pinIdx];
|
||||
if (nearTop) {
|
||||
this.availablePowerPins.top.ground.splice(pinIdx, 1);
|
||||
this.availablePowerPins.top.threeVolt.splice(pinIdx, 1);
|
||||
} else {
|
||||
this.availablePowerPins.bottom.ground.splice(pinIdx, 1);
|
||||
this.availablePowerPins.bottom.threeVolt.splice(pinIdx, 1);
|
||||
}
|
||||
return {type: "breadboard", rowCol: pin};
|
||||
} else if (location[0] === "breadboard") {
|
||||
U.assert(!!opts.startColumn);
|
||||
let row = <string>location[1];
|
||||
let col = (<number>location[2] + opts.startColumn).toString();
|
||||
return {type: "breadboard", rowCol: [row, col]}
|
||||
} else if (location[0] === "GPIO") {
|
||||
U.assert(!!opts.cmpGPIOPins);
|
||||
let idx = <number>location[1];
|
||||
let pin = opts.cmpGPIOPins[idx];
|
||||
return {type: "dalboard", pin: pin};
|
||||
} else {
|
||||
//TODO
|
||||
U.assert(false);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
private allocatePowerWires(): WireInst[] {
|
||||
let boardGround = this.opts.boardDef.groundPins[0] || null;
|
||||
if (!boardGround) {
|
||||
console.log("No available ground pin on board!");
|
||||
//TODO
|
||||
}
|
||||
let threeVoltPin = this.opts.boardDef.threeVoltPins[0] || null;
|
||||
if (!threeVoltPin) {
|
||||
console.log("No available 3.3V pin on board!");
|
||||
//TODO
|
||||
}
|
||||
let topLeft: BBRowCol = ["-", "26"];
|
||||
let botLeft: BBRowCol = ["-", "1"];
|
||||
let topRight: BBRowCol = ["-", "50"];
|
||||
let botRight: BBRowCol = ["-", "25"];
|
||||
let top: BBRowCol, bot: BBRowCol;
|
||||
if (this.opts.boardDef.attachPowerOnRight) {
|
||||
top = topRight;
|
||||
bot = botRight;
|
||||
} else {
|
||||
top = topLeft;
|
||||
bot = botLeft;
|
||||
}
|
||||
const GROUND_COLOR = "blue";
|
||||
const POWER_COLOR = "red";
|
||||
const wires: WireInst[] = [
|
||||
{start: this.allocateLocation("ground", {nearestBBPin: top}),
|
||||
end: this.allocateLocation("ground", {nearestBBPin: bot}),
|
||||
color: GROUND_COLOR, assemblyStep: 0},
|
||||
{start: this.allocateLocation("ground", {nearestBBPin: top}),
|
||||
end: {type: "dalboard", pin: boardGround},
|
||||
color: GROUND_COLOR, assemblyStep: 0},
|
||||
{start: this.allocateLocation("threeVolt", {nearestBBPin: top}),
|
||||
end: this.allocateLocation("threeVolt", {nearestBBPin: bot}),
|
||||
color: POWER_COLOR, assemblyStep: 1},
|
||||
{start: this.allocateLocation("threeVolt", {nearestBBPin: top}),
|
||||
end: {type: "dalboard", pin: threeVoltPin},
|
||||
color: POWER_COLOR, assemblyStep: 1},
|
||||
];
|
||||
return wires;
|
||||
}
|
||||
private allocateWire(wireDef: WireDefinition, opts: AllocWireOpts): WireInst {
|
||||
let ends = [wireDef.start, wireDef.end];
|
||||
let endIsPower = ends.map(e => e === "ground" || e === "threeVolt");
|
||||
let endInsts = ends.map((e, idx) => !endIsPower[idx] ? this.allocateLocation(e, opts) : null)
|
||||
endInsts = endInsts.map((e, idx) => {
|
||||
if (e)
|
||||
return e;
|
||||
let locInst = <BBLoc>endInsts[1 - idx];
|
||||
let l = this.allocateLocation(ends[idx], {
|
||||
nearestBBPin: locInst.rowCol,
|
||||
startColumn: opts.startColumn,
|
||||
cmpGPIOPins: opts.cmpGPIOPins
|
||||
});
|
||||
return l;
|
||||
});
|
||||
return {start: endInsts[0], end: endInsts[1], color: wireDef.color, assemblyStep: wireDef.assemblyStep};
|
||||
}
|
||||
private allocatePartialCmps(): PartialCmpAlloc[] {
|
||||
let cmpNmAndDefs = this.opts.cmpList.map(cmpName => <[string, PartDefinition]>[cmpName, this.opts.cmpDefs[cmpName]]).filter(d => !!d[1]);
|
||||
let cmpNmsList = cmpNmAndDefs.map(p => p[0]);
|
||||
let cmpDefsList = cmpNmAndDefs.map(p => p[1]);
|
||||
let partialCmps: PartialCmpAlloc[] = [];
|
||||
cmpDefsList.forEach((def, idx) => {
|
||||
let nm = cmpNmsList[idx];
|
||||
if (def.pinAllocation.type === "predefined") {
|
||||
let mbPins = (<PredefinedPinAlloc>def.pinAllocation).pins;
|
||||
let pinsAssigned = mbPins.map(p => this.opts.boardDef.gpioPinMap[p]);
|
||||
partialCmps.push({
|
||||
name: nm,
|
||||
def: def,
|
||||
pinsAssigned: pinsAssigned,
|
||||
pinsNeeded: 0,
|
||||
breadboardColumnsNeeded: def.breadboardColumnsNeeded,
|
||||
});
|
||||
} else if (def.pinAllocation.type === "factoryfunction") {
|
||||
let fnPinAlloc = (<FactoryFunctionPinAlloc>def.pinAllocation);
|
||||
let fnNm = fnPinAlloc.functionName;
|
||||
let fnsAndArgs = <string[]>this.opts.fnArgs[fnNm];
|
||||
let success = false;
|
||||
if (fnsAndArgs && fnsAndArgs.length) {
|
||||
let pinArgPoses = fnPinAlloc.pinArgPositions;
|
||||
let otherArgPoses = fnPinAlloc.otherArgPositions || [];
|
||||
fnsAndArgs.forEach(fnArgsStr => {
|
||||
let fnArgsSplit = fnArgsStr.split(",");
|
||||
let pinArgs: string[] = [];
|
||||
pinArgPoses.forEach(i => {
|
||||
pinArgs.push(fnArgsSplit[i]);
|
||||
});
|
||||
let mbPins = pinArgs.map(arg => readPin(arg));
|
||||
let otherArgs: string[] = [];
|
||||
otherArgPoses.forEach(i => {
|
||||
otherArgs.push(fnArgsSplit[i]);
|
||||
});
|
||||
let pinsAssigned = mbPins.map(p => this.opts.boardDef.gpioPinMap[p]);
|
||||
partialCmps.push({
|
||||
name: nm,
|
||||
def: def,
|
||||
pinsAssigned: pinsAssigned,
|
||||
pinsNeeded: 0,
|
||||
breadboardColumnsNeeded: def.breadboardColumnsNeeded,
|
||||
otherArgs: otherArgs.length ? otherArgs : null,
|
||||
});
|
||||
});
|
||||
} else {
|
||||
// failed to find pin allocation from callsites
|
||||
console.debug("Failed to read pin(s) from callsite for: " + fnNm);
|
||||
let pinsNeeded = fnPinAlloc.pinArgPositions.length;
|
||||
partialCmps.push({
|
||||
name: nm,
|
||||
def: def,
|
||||
pinsAssigned: [],
|
||||
pinsNeeded: pinsNeeded,
|
||||
breadboardColumnsNeeded: def.breadboardColumnsNeeded,
|
||||
});
|
||||
}
|
||||
} else if (def.pinAllocation.type === "auto") {
|
||||
let pinsNeeded = (<AutoPinAlloc>def.pinAllocation).gpioPinsNeeded;
|
||||
partialCmps.push({
|
||||
name: nm,
|
||||
def: def,
|
||||
pinsAssigned: [],
|
||||
pinsNeeded: pinsNeeded,
|
||||
breadboardColumnsNeeded: def.breadboardColumnsNeeded,
|
||||
});
|
||||
}
|
||||
});
|
||||
return partialCmps;
|
||||
}
|
||||
private allocateGPIOPins(partialCmps: PartialCmpAlloc[]): string[][] {
|
||||
let availableGPIOBlocks = copyDoubleArray(this.opts.boardDef.gpioPinBlocks);
|
||||
let sortAvailableGPIOBlocks = () => availableGPIOBlocks.sort((a, b) => a.length - b.length); //smallest blocks first
|
||||
// determine blocks needed
|
||||
let blockAssignments: AllocBlock[] = [];
|
||||
let preassignedPins: string[] = [];
|
||||
partialCmps.forEach((cmp, idx) => {
|
||||
if (cmp.pinsAssigned && cmp.pinsAssigned.length) {
|
||||
//already assigned
|
||||
blockAssignments.push({cmpIdx: idx, cmpBlkIdx: 0, gpioNeeded: 0, gpioAssigned: cmp.pinsAssigned});
|
||||
preassignedPins = preassignedPins.concat(cmp.pinsAssigned);
|
||||
} else if (cmp.pinsNeeded) {
|
||||
if (typeof cmp.pinsNeeded === "number") {
|
||||
//individual pins
|
||||
for (let i = 0; i < cmp.pinsNeeded; i++) {
|
||||
blockAssignments.push(
|
||||
{cmpIdx: idx, cmpBlkIdx: 0, gpioNeeded: 1, gpioAssigned: []});
|
||||
}
|
||||
} else {
|
||||
//blocks of pins
|
||||
let blocks = <number[]>cmp.pinsNeeded;
|
||||
blocks.forEach((numNeeded, blkIdx) => {
|
||||
blockAssignments.push(
|
||||
{cmpIdx: idx, cmpBlkIdx: blkIdx, gpioNeeded: numNeeded, gpioAssigned: []});
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
// remove assigned blocks
|
||||
availableGPIOBlocks.forEach(blks => {
|
||||
for (let i = blks.length - 1; 0 <= i; i--) {
|
||||
let pin = blks[i];
|
||||
if (0 <= preassignedPins.indexOf(pin)) {
|
||||
blks.splice(i, 1);
|
||||
}
|
||||
}
|
||||
});
|
||||
// sort by size of blocks
|
||||
let sortBlockAssignments = () => blockAssignments.sort((a, b) => b.gpioNeeded - a.gpioNeeded); //largest blocks first
|
||||
// allocate each block
|
||||
if (0 < blockAssignments.length && 0 < availableGPIOBlocks.length) {
|
||||
do {
|
||||
sortBlockAssignments();
|
||||
sortAvailableGPIOBlocks();
|
||||
let assignment = blockAssignments[0];
|
||||
let smallestAvailableBlockThatFits: string[];
|
||||
for (let j = 0; j < availableGPIOBlocks.length; j++) {
|
||||
smallestAvailableBlockThatFits = availableGPIOBlocks[j];
|
||||
if (assignment.gpioNeeded <= availableGPIOBlocks[j].length) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!smallestAvailableBlockThatFits || smallestAvailableBlockThatFits.length <= 0) {
|
||||
break; // out of pins
|
||||
}
|
||||
while (0 < assignment.gpioNeeded && 0 < smallestAvailableBlockThatFits.length) {
|
||||
assignment.gpioNeeded--;
|
||||
let pin = smallestAvailableBlockThatFits[0];
|
||||
smallestAvailableBlockThatFits.splice(0, 1);
|
||||
assignment.gpioAssigned.push(pin);
|
||||
}
|
||||
sortBlockAssignments();
|
||||
} while (0 < blockAssignments[0].gpioNeeded);
|
||||
}
|
||||
if (0 < blockAssignments.length && 0 < blockAssignments[0].gpioNeeded) {
|
||||
console.debug("Not enough GPIO pins!");
|
||||
return null;
|
||||
}
|
||||
let cmpGPIOPinBlocks: string[][][] = partialCmps.map((def, cmpIdx) => {
|
||||
if (!def)
|
||||
return null;
|
||||
let assignments = blockAssignments.filter(a => a.cmpIdx === cmpIdx);
|
||||
let gpioPins: string[][] = [];
|
||||
for (let i = 0; i < assignments.length; i++) {
|
||||
let a = assignments[i];
|
||||
let blk = gpioPins[a.cmpBlkIdx] || (gpioPins[a.cmpBlkIdx] = []);
|
||||
a.gpioAssigned.forEach(p => blk.push(p));
|
||||
}
|
||||
return gpioPins;
|
||||
});
|
||||
let cmpGPIOPins = cmpGPIOPinBlocks.map(blks => blks.reduce((p, n) => p.concat(n), []));
|
||||
return cmpGPIOPins;
|
||||
}
|
||||
private allocateColumns(partialCmps: PartialCmpAlloc[]): number[] {
|
||||
let componentsCount = partialCmps.length;
|
||||
let totalAvailableSpace = 30; //TODO allow multiple breadboards
|
||||
let totalSpaceNeeded = partialCmps.map(d => d.breadboardColumnsNeeded).reduce((p, n) => p + n, 0);
|
||||
let extraSpace = totalAvailableSpace - totalSpaceNeeded;
|
||||
if (extraSpace <= 0) {
|
||||
console.log("Not enough breadboard space!");
|
||||
//TODO
|
||||
}
|
||||
let padding = Math.floor(extraSpace / (componentsCount - 1 + 2));
|
||||
let componentSpacing = padding; //Math.floor(extraSpace/(componentsCount-1));
|
||||
let totalCmpPadding = extraSpace - componentSpacing * (componentsCount - 1);
|
||||
let leftPadding = Math.floor(totalCmpPadding / 2);
|
||||
let rightPadding = Math.ceil(totalCmpPadding / 2);
|
||||
let nextAvailableCol = 1 + leftPadding;
|
||||
let cmpStartCol = partialCmps.map(cmp => {
|
||||
let col = nextAvailableCol;
|
||||
nextAvailableCol += cmp.breadboardColumnsNeeded + componentSpacing;
|
||||
return col;
|
||||
});
|
||||
return cmpStartCol;
|
||||
}
|
||||
private allocateComponent(partialCmp: PartialCmpAlloc, startColumn: number, microbitPins: string[]): CmpInst {
|
||||
return {
|
||||
name: partialCmp.name,
|
||||
breadboardStartColumn: startColumn,
|
||||
breadboardStartRow: partialCmp.def.breadboardStartRow,
|
||||
assemblyStep: partialCmp.def.assemblyStep,
|
||||
visual: partialCmp.def.visual,
|
||||
microbitPins: microbitPins,
|
||||
otherArgs: partialCmp.otherArgs,
|
||||
};
|
||||
}
|
||||
public allocateAll(): AllocatorResult {
|
||||
let cmpList = this.opts.cmpList;
|
||||
let basicWires: WireInst[] = [];
|
||||
let cmpsAndWires: CmpAndWireInst[] = [];
|
||||
if (cmpList.length > 0) {
|
||||
basicWires = this.allocatePowerWires();
|
||||
let partialCmps = this.allocatePartialCmps();
|
||||
let cmpGPIOPins = this.allocateGPIOPins(partialCmps);
|
||||
let reverseMap = mkReverseMap(this.opts.boardDef.gpioPinMap);
|
||||
let cmpMicrobitPins = cmpGPIOPins.map(pins => pins.map(p => reverseMap[p]));
|
||||
let cmpStartCol = this.allocateColumns(partialCmps);
|
||||
let cmps = partialCmps.map((c, idx) => this.allocateComponent(c, cmpStartCol[idx], cmpMicrobitPins[idx]));
|
||||
let wires = partialCmps.map((c, idx) => c.def.wires.map(d => this.allocateWire(d, {
|
||||
cmpGPIOPins: cmpGPIOPins[idx],
|
||||
startColumn: cmpStartCol[idx],
|
||||
})));
|
||||
cmpsAndWires = cmps.map((c, idx) => {
|
||||
return {component: c, wires: wires[idx]}
|
||||
});
|
||||
}
|
||||
return {
|
||||
powerWires: basicWires,
|
||||
components: cmpsAndWires
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export function allocateDefinitions(opts: AllocatorOpts): AllocatorResult {
|
||||
return new Allocator(opts).allocateAll();
|
||||
}
|
||||
}
|
96
sim/dalboard.ts
Normal file
@ -0,0 +1,96 @@
|
||||
namespace pxsim {
|
||||
export class DalBoard extends BaseBoard {
|
||||
id: string;
|
||||
|
||||
// the bus
|
||||
bus: EventBus;
|
||||
|
||||
// state & update logic for component services
|
||||
ledMatrixState: LedMatrixState;
|
||||
edgeConnectorState: EdgeConnectorState;
|
||||
serialState: SerialState;
|
||||
accelerometerState: AccelerometerState;
|
||||
compassState: CompassState;
|
||||
thermometerState: ThermometerState;
|
||||
lightSensorState: LightSensorState;
|
||||
buttonPairState: ButtonPairState;
|
||||
radioState: RadioState;
|
||||
neopixelState: NeoPixelState;
|
||||
|
||||
// updates
|
||||
updateSubscribers: (() => void)[];
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
this.id = "b" + Math_.random(2147483647);
|
||||
this.bus = new EventBus(runtime);
|
||||
|
||||
// components
|
||||
this.ledMatrixState = new LedMatrixState(runtime);
|
||||
this.buttonPairState = new ButtonPairState();
|
||||
this.edgeConnectorState = new EdgeConnectorState();
|
||||
this.radioState = new RadioState(runtime);
|
||||
this.accelerometerState = new AccelerometerState(runtime);
|
||||
this.serialState = new SerialState();
|
||||
this.thermometerState = new ThermometerState();
|
||||
this.lightSensorState = new LightSensorState();
|
||||
this.compassState = new CompassState();
|
||||
this.neopixelState = new NeoPixelState();
|
||||
|
||||
// updates
|
||||
this.updateSubscribers = []
|
||||
this.updateView = () => {
|
||||
this.updateSubscribers.forEach(sub => sub());
|
||||
}
|
||||
}
|
||||
|
||||
receiveMessage(msg: SimulatorMessage) {
|
||||
if (!runtime || runtime.dead) return;
|
||||
|
||||
switch (msg.type || "") {
|
||||
case "eventbus":
|
||||
let ev = <SimulatorEventBusMessage>msg;
|
||||
this.bus.queue(ev.id, ev.eventid, ev.value);
|
||||
break;
|
||||
case "serial":
|
||||
let data = (<SimulatorSerialMessage>msg).data || "";
|
||||
this.serialState.recieveData(data);
|
||||
break;
|
||||
case "radiopacket":
|
||||
let packet = <SimulatorRadioPacketMessage>msg;
|
||||
this.radioState.recievePacket(packet);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
kill() {
|
||||
super.kill();
|
||||
AudioContextManager.stop();
|
||||
}
|
||||
|
||||
initAsync(msg: SimulatorRunMessage): Promise<void> {
|
||||
let options = (msg.options || {}) as RuntimeOptions;
|
||||
|
||||
let boardDef = CURRENT_BOARD; //TODO: read from pxt.json/pxttarget.json
|
||||
|
||||
let cmpsList = msg.parts;
|
||||
let cmpDefs = PART_DEFINITIONS; //TODO: read from pxt.json/pxttarget.json
|
||||
let fnArgs = msg.fnArgs;
|
||||
|
||||
let viewHost = new visuals.BoardHost({
|
||||
state: this,
|
||||
boardDef: boardDef,
|
||||
cmpsList: cmpsList,
|
||||
cmpDefs: cmpDefs,
|
||||
fnArgs: fnArgs,
|
||||
maxWidth: "100%",
|
||||
maxHeight: "100%",
|
||||
});
|
||||
|
||||
document.body.innerHTML = ""; // clear children
|
||||
document.body.appendChild(viewHost.getView());
|
||||
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
}
|
214
sim/definitions.ts
Normal file
@ -0,0 +1,214 @@
|
||||
/// <reference path="../node_modules/pxt-core/typings/bluebird/bluebird.d.ts"/>
|
||||
/// <reference path="../node_modules/pxt-core/built/pxtsim.d.ts"/>
|
||||
/// <reference path="../libs/microbit/dal.d.ts"/>
|
||||
/// <reference path="./visuals/neopixel.ts"/>
|
||||
|
||||
namespace pxsim {
|
||||
export interface PinBlockDefinition {
|
||||
x: number,
|
||||
y: number,
|
||||
labelPosition: "above" | "below";
|
||||
labels: string[]
|
||||
}
|
||||
export interface BoardImageDefinition {
|
||||
image: string,
|
||||
outlineImage?: string,
|
||||
width: number,
|
||||
height: number,
|
||||
pinDist: number,
|
||||
pinBlocks: PinBlockDefinition[],
|
||||
};
|
||||
export interface BoardDefinition {
|
||||
visual: BoardImageDefinition | string,
|
||||
gpioPinBlocks?: string[][],
|
||||
gpioPinMap: {[pin: string]: string},
|
||||
groundPins: string[],
|
||||
threeVoltPins: string[],
|
||||
attachPowerOnRight?: boolean,
|
||||
onboardComponents?: string[]
|
||||
useCrocClips?: boolean,
|
||||
marginWhenBreadboarding?: [number, number, number, number],
|
||||
}
|
||||
export interface FactoryFunctionPinAlloc {
|
||||
type: "factoryfunction",
|
||||
functionName: string,
|
||||
pinArgPositions: number[],
|
||||
otherArgPositions?: number[],
|
||||
}
|
||||
export interface PredefinedPinAlloc {
|
||||
type: "predefined",
|
||||
pins: string[],
|
||||
}
|
||||
export interface AutoPinAlloc {
|
||||
type: "auto",
|
||||
gpioPinsNeeded: number | number[],
|
||||
}
|
||||
export interface PartVisualDefinition {
|
||||
image: string,
|
||||
width: number,
|
||||
height: number,
|
||||
pinDist: number,
|
||||
extraColumnOffset?: number,
|
||||
firstPin: [number, number],
|
||||
}
|
||||
export interface PartDefinition {
|
||||
visual: string | PartVisualDefinition,
|
||||
breadboardColumnsNeeded: number,
|
||||
breadboardStartRow: string,
|
||||
wires: WireDefinition[],
|
||||
assemblyStep: number,
|
||||
pinAllocation: FactoryFunctionPinAlloc | PredefinedPinAlloc | AutoPinAlloc,
|
||||
}
|
||||
export interface WireDefinition {
|
||||
start: WireLocationDefinition,
|
||||
end: WireLocationDefinition,
|
||||
color: string,
|
||||
assemblyStep: number
|
||||
};
|
||||
export type WireLocationDefinition =
|
||||
["breadboard", string, number] | ["GPIO", number] | "ground" | "threeVolt";
|
||||
|
||||
export const MICROBIT_DEF: BoardDefinition = {
|
||||
visual: "microbit",
|
||||
gpioPinBlocks: [
|
||||
["P0"], ["P1"], ["P2"],
|
||||
["P3"],
|
||||
["P4", "P5", "P6", "P7"],
|
||||
["P8", "P9", "P10", "P11", "P12"],
|
||||
["P13", "P14", "P15", "P16"],
|
||||
["P19", "P20"],
|
||||
],
|
||||
gpioPinMap: {
|
||||
"P0": "P0",
|
||||
"P1": "P1",
|
||||
"P2": "P2",
|
||||
"P3": "P3",
|
||||
"P4": "P4",
|
||||
"P5": "P5",
|
||||
"P6": "P6",
|
||||
"P7": "P7",
|
||||
"P8": "P8",
|
||||
"P9": "P9",
|
||||
"P10": "P10",
|
||||
"P11": "P11",
|
||||
"P12": "P12",
|
||||
"P13": "P13",
|
||||
"P14": "P14",
|
||||
"P15": "P15",
|
||||
"P16": "P16",
|
||||
"P19": "P19",
|
||||
"P20": "P20",
|
||||
},
|
||||
groundPins: ["GND"],
|
||||
threeVoltPins: ["+3v3"],
|
||||
attachPowerOnRight: true,
|
||||
onboardComponents: ["buttonpair", "ledmatrix", "speaker"],
|
||||
useCrocClips: true,
|
||||
marginWhenBreadboarding: [0, 0, 80, 0],
|
||||
}
|
||||
|
||||
export const PART_DEFINITIONS: Map<PartDefinition> = {
|
||||
"ledmatrix": {
|
||||
visual: "ledmatrix",
|
||||
breadboardColumnsNeeded: 8,
|
||||
breadboardStartRow: "h",
|
||||
pinAllocation: {
|
||||
type: "auto",
|
||||
gpioPinsNeeded: [5, 5],
|
||||
},
|
||||
assemblyStep: 0,
|
||||
wires: [
|
||||
{start: ["breadboard", `j`, 0], end: ["GPIO", 5], color: "purple", assemblyStep: 1},
|
||||
{start: ["breadboard", `j`, 1], end: ["GPIO", 6], color: "purple", assemblyStep: 1},
|
||||
{start: ["breadboard", `j`, 2], end: ["GPIO", 7], color: "purple", assemblyStep: 1},
|
||||
{start: ["breadboard", `j`, 3], end: ["GPIO", 8], color: "purple", assemblyStep: 1},
|
||||
{start: ["breadboard", `a`, 7], end: ["GPIO", 9], color: "purple", assemblyStep: 1},
|
||||
{start: ["breadboard", `a`, 0], end: ["GPIO", 0], color: "green", assemblyStep: 2},
|
||||
{start: ["breadboard", `a`, 1], end: ["GPIO", 1], color: "green", assemblyStep: 2},
|
||||
{start: ["breadboard", `a`, 2], end: ["GPIO", 2], color: "green", assemblyStep: 2},
|
||||
{start: ["breadboard", `a`, 3], end: ["GPIO", 3], color: "green", assemblyStep: 2},
|
||||
{start: ["breadboard", `j`, 4], end: ["GPIO", 4], color: "green", assemblyStep: 2},
|
||||
]
|
||||
},
|
||||
"buttonpair": {
|
||||
visual: "buttonpair",
|
||||
breadboardColumnsNeeded: 6,
|
||||
breadboardStartRow: "f",
|
||||
pinAllocation: {
|
||||
type: "predefined",
|
||||
pins: ["P13", "P12"],
|
||||
},
|
||||
assemblyStep: 0,
|
||||
wires: [
|
||||
{start: ["breadboard", "j", 0], end: ["GPIO", 0], color: "yellow", assemblyStep: 1},
|
||||
{start: ["breadboard", "a", 2], end: "ground", color: "blue", assemblyStep: 1},
|
||||
{start: ["breadboard", "j", 3], end: ["GPIO", 1], color: "orange", assemblyStep: 2},
|
||||
{start: ["breadboard", "a", 5], end: "ground", color: "blue", assemblyStep: 2},
|
||||
],
|
||||
},
|
||||
"neopixel": {
|
||||
visual: "neopixel",
|
||||
breadboardColumnsNeeded: 5,
|
||||
breadboardStartRow: "h",
|
||||
pinAllocation: {
|
||||
type: "factoryfunction",
|
||||
functionName: "neopixel.create",
|
||||
pinArgPositions: [0],
|
||||
otherArgPositions: [1],
|
||||
},
|
||||
assemblyStep: 0,
|
||||
wires: [
|
||||
{start: ["breadboard", "j", 1], end: "ground", color: "blue", assemblyStep: 1},
|
||||
{start: ["breadboard", "j", 2], end: "threeVolt", color: "red", assemblyStep: 2},
|
||||
{start: ["breadboard", "j", 3], end: ["GPIO", 0], color: "green", assemblyStep: 2},
|
||||
],
|
||||
},
|
||||
"speaker": {
|
||||
visual: {
|
||||
image: "/static/hardware/speaker.svg",
|
||||
width: 500,
|
||||
height: 500,
|
||||
firstPin: [180, 135],
|
||||
pinDist: 70,
|
||||
extraColumnOffset: 1,
|
||||
},
|
||||
breadboardColumnsNeeded: 5,
|
||||
breadboardStartRow: "f",
|
||||
pinAllocation: {
|
||||
type: "auto",
|
||||
gpioPinsNeeded: 1,
|
||||
},
|
||||
assemblyStep: 0,
|
||||
wires: [
|
||||
{start: ["breadboard", "j", 1], end: ["GPIO", 0], color: "#ff80fa", assemblyStep: 1},
|
||||
{start: ["breadboard", "j", 3], end: "ground", color: "blue", assemblyStep: 1},
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
export const builtinComponentSimVisual: Map<() => visuals.IBoardComponent<any>> = {
|
||||
"buttonpair": () => new visuals.ButtonPairView(),
|
||||
"ledmatrix": () => new visuals.LedMatrixView(),
|
||||
"neopixel": () => new visuals.NeoPixelView(),
|
||||
};
|
||||
export const builtinComponentSimState: Map<(d: DalBoard) => any> = {
|
||||
"buttonpair": (d: DalBoard) => d.buttonPairState,
|
||||
"ledmatrix": (d: DalBoard) => d.ledMatrixState,
|
||||
"edgeconnector": (d: DalBoard) => d.edgeConnectorState,
|
||||
"serial": (d: DalBoard) => d.serialState,
|
||||
"radio": (d: DalBoard) => d.radioState,
|
||||
"thermometer": (d: DalBoard) => d.thermometerState,
|
||||
"accelerometer": (d: DalBoard) => d.accelerometerState,
|
||||
"compass": (d: DalBoard) => d.compassState,
|
||||
"lightsensor": (d: DalBoard) => d.lightSensorState,
|
||||
"neopixel": (d: DalBoard) => d.neopixelState,
|
||||
};
|
||||
export const builtinComponentPartVisual: Map<(xy: visuals.Coord) => visuals.SVGElAndSize> = {
|
||||
"buttonpair": (xy: visuals.Coord) => visuals.mkBtnSvg(xy),
|
||||
"ledmatrix": (xy: visuals.Coord) => visuals.mkLedMatrixSvg(xy, 8, 8),
|
||||
"neopixel": (xy: visuals.Coord) => visuals.mkNeoPixelPart(xy),
|
||||
};
|
||||
|
||||
//TODO: add multiple board support
|
||||
export const CURRENT_BOARD = MICROBIT_DEF;
|
||||
}
|
672
sim/instructions/instructions.ts
Normal file
@ -0,0 +1,672 @@
|
||||
/// <reference path="../../node_modules/pxt-core/typings/bluebird/bluebird.d.ts"/>
|
||||
/// <reference path="../../node_modules/pxt-core/built/pxtsim.d.ts"/>
|
||||
/// <reference path="../../node_modules/pxt-core/built/pxtrunner.d.ts"/>
|
||||
/// <reference path="../../libs/microbit/dal.d.ts"/>
|
||||
/// <reference path="../visuals/genericboard.ts"/>
|
||||
/// <reference path="../visuals/wiring.ts"/>
|
||||
|
||||
//HACK: allows instructions.html to access pxtblocks without requiring simulator.html to import blocks as well
|
||||
if (!(<any>window).pxt) (<any>window).pxt = {};
|
||||
import pxtrunner = pxt.runner;
|
||||
import pxtdocs = pxt.docs;
|
||||
|
||||
namespace pxsim.instructions {
|
||||
const LOC_LBL_SIZE = 10;
|
||||
const QUANT_LBL_SIZE = 30;
|
||||
const QUANT_LBL = (q: number) => `${q}x`;
|
||||
const WIRE_QUANT_LBL_SIZE = 20;
|
||||
const LBL_VERT_PAD = 3;
|
||||
const LBL_RIGHT_PAD = 5;
|
||||
const LBL_LEFT_PAD = 5;
|
||||
const REQ_WIRE_HEIGHT = 45;
|
||||
const REQ_CMP_HEIGHT = 55;
|
||||
const REQ_CMP_SCALE = 0.5;
|
||||
type Orientation = "landscape" | "portrait";
|
||||
const ORIENTATION: Orientation = "portrait";
|
||||
const PPI = 96.0;
|
||||
const [FULL_PAGE_WIDTH, FULL_PAGE_HEIGHT]
|
||||
= (ORIENTATION == "portrait" ? [PPI * 8.5, PPI * 11.0] : [PPI * 11.0, PPI * 8.5]);
|
||||
const PAGE_MARGIN = PPI * 0.45;
|
||||
const PAGE_WIDTH = FULL_PAGE_WIDTH - PAGE_MARGIN * 2;
|
||||
const PAGE_HEIGHT = FULL_PAGE_HEIGHT - PAGE_MARGIN * 2;
|
||||
const BORDER_COLOR = "gray";
|
||||
const BORDER_RADIUS = 5;
|
||||
const BORDER_WIDTH = 2;
|
||||
const [PANEL_ROWS, PANEL_COLS] = [2, 2];
|
||||
const PANEL_MARGIN = 20;
|
||||
const PANEL_PADDING = 8;
|
||||
const PANEL_WIDTH = PAGE_WIDTH / PANEL_COLS - (PANEL_MARGIN + PANEL_PADDING + BORDER_WIDTH) * PANEL_COLS;
|
||||
const PANEL_HEIGHT = PAGE_HEIGHT / PANEL_ROWS - (PANEL_MARGIN + PANEL_PADDING + BORDER_WIDTH) * PANEL_ROWS;
|
||||
const BOARD_WIDTH = 240;
|
||||
const BOARD_LEFT = (PANEL_WIDTH - BOARD_WIDTH) / 2.0 + PANEL_PADDING;
|
||||
const BOARD_BOT = PANEL_PADDING;
|
||||
const NUM_BOX_SIZE = 60;
|
||||
const NUM_FONT = 40;
|
||||
const NUM_MARGIN = 5;
|
||||
const FRONT_PAGE_BOARD_WIDTH = 200;
|
||||
const PARTS_BOARD_SCALE = 0.17;
|
||||
const PARTS_BB_SCALE = 0.25;
|
||||
const PARTS_CMP_SCALE = 0.3;
|
||||
const PARTS_WIRE_SCALE = 0.23;
|
||||
const STYLE = `
|
||||
.instr-panel {
|
||||
margin: ${PANEL_MARGIN}px;
|
||||
padding: ${PANEL_PADDING}px;
|
||||
border-width: ${BORDER_WIDTH}px;
|
||||
border-color: ${BORDER_COLOR};
|
||||
border-style: solid;
|
||||
border-radius: ${BORDER_RADIUS}px;
|
||||
display: inline-block;
|
||||
width: ${PANEL_WIDTH}px;
|
||||
height: ${PANEL_HEIGHT}px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
.board-svg {
|
||||
margin: 0 auto;
|
||||
display: block;
|
||||
position: absolute;
|
||||
bottom: ${BOARD_BOT}px;
|
||||
left: ${BOARD_LEFT}px;
|
||||
}
|
||||
.panel-num-outer {
|
||||
position: absolute;
|
||||
left: ${-BORDER_WIDTH}px;
|
||||
top: ${-BORDER_WIDTH}px;
|
||||
width: ${NUM_BOX_SIZE}px;
|
||||
height: ${NUM_BOX_SIZE}px;
|
||||
border-width: ${BORDER_WIDTH}px;
|
||||
border-style: solid;
|
||||
border-color: ${BORDER_COLOR};
|
||||
border-radius: ${BORDER_RADIUS}px 0 ${BORDER_RADIUS}px 0;
|
||||
}
|
||||
.panel-num {
|
||||
margin: ${NUM_MARGIN}px 0;
|
||||
text-align: center;
|
||||
font-size: ${NUM_FONT}px;
|
||||
}
|
||||
.cmp-div {
|
||||
display: inline-block;
|
||||
}
|
||||
.reqs-div {
|
||||
margin-left: ${PANEL_PADDING + NUM_BOX_SIZE}px;
|
||||
}
|
||||
.partslist-wire,
|
||||
.partslist-cmp {
|
||||
margin: 5px;
|
||||
}
|
||||
.partslist-wire {
|
||||
display: inline-block;
|
||||
}
|
||||
`;
|
||||
|
||||
function addClass(el: HTMLElement, cls: string) {
|
||||
//TODO move to library
|
||||
if (el.classList) el.classList.add(cls);
|
||||
//BUG: won't work if element has class that is prefix of new class
|
||||
//TODO: make github issue (same issue exists svg.addClass)
|
||||
else if (!el.className.indexOf(cls)) el.className += " " + cls;
|
||||
}
|
||||
function mkTxt(p: [number, number], txt: string, size: number) {
|
||||
let el = svg.elt("text")
|
||||
let [x, y] = p;
|
||||
svg.hydrate(el, { x: x, y: y, style: `font-size:${size}px;` });
|
||||
el.textContent = txt;
|
||||
return el;
|
||||
}
|
||||
type mkCmpDivOpts = {
|
||||
top?: string,
|
||||
topSize?: number,
|
||||
right?: string,
|
||||
rightSize?: number,
|
||||
left?: string,
|
||||
leftSize?: number,
|
||||
bot?: string,
|
||||
botSize?: number,
|
||||
wireClr?: string,
|
||||
cmpWidth?: number,
|
||||
cmpHeight?: number,
|
||||
cmpScale?: number
|
||||
};
|
||||
function mkBoardImgSvg(def: string | BoardImageDefinition): visuals.SVGElAndSize {
|
||||
let boardView: visuals.BoardView;
|
||||
if (def === "microbit") {
|
||||
boardView = new visuals.MicrobitBoardSvg({
|
||||
theme: visuals.randomTheme()
|
||||
})
|
||||
} else {
|
||||
boardView = new visuals.GenericBoardSvg({
|
||||
visualDef: <BoardImageDefinition>def
|
||||
})
|
||||
}
|
||||
return boardView.getView();
|
||||
}
|
||||
function mkBBSvg(): visuals.SVGElAndSize {
|
||||
let bb = new visuals.Breadboard({});
|
||||
return bb.getSVGAndSize();
|
||||
}
|
||||
function wrapSvg(el: visuals.SVGElAndSize, opts: mkCmpDivOpts): HTMLElement {
|
||||
//TODO: Refactor this function; it is too complicated. There is a lot of error-prone math being done
|
||||
// to scale and place all elements which could be simplified with more forethought.
|
||||
let svgEl = <SVGSVGElement>document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
||||
let dims = {l: 0, t: 0, w: 0, h: 0};
|
||||
|
||||
let cmpSvgEl = <SVGSVGElement>document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
||||
svgEl.appendChild(cmpSvgEl);
|
||||
|
||||
cmpSvgEl.appendChild(el.el);
|
||||
let cmpSvgAtts = {
|
||||
"viewBox": `${el.x} ${el.y} ${el.w} ${el.h}`,
|
||||
"preserveAspectRatio": "xMidYMid",
|
||||
};
|
||||
dims.w = el.w;
|
||||
dims.h = el.h;
|
||||
let scale = (scaler: number) => {
|
||||
dims.h *= scaler;
|
||||
dims.w *= scaler;
|
||||
(<any>cmpSvgAtts).width = dims.w;
|
||||
(<any>cmpSvgAtts).height = dims.h;
|
||||
}
|
||||
if (opts.cmpScale) {
|
||||
scale(opts.cmpScale)
|
||||
}
|
||||
if (opts.cmpWidth && opts.cmpWidth < dims.w) {
|
||||
scale(opts.cmpWidth / dims.w);
|
||||
} else if (opts.cmpHeight && opts.cmpHeight < dims.h) {
|
||||
scale(opts.cmpHeight / dims.h)
|
||||
}
|
||||
svg.hydrate(cmpSvgEl, cmpSvgAtts);
|
||||
let elDims = {l: dims.l, t: dims.t, w: dims.w, h: dims.h};
|
||||
|
||||
let updateL = (newL: number) => {
|
||||
if (newL < dims.l) {
|
||||
let extraW = dims.l - newL;
|
||||
dims.l = newL;
|
||||
dims.w += extraW;
|
||||
}
|
||||
}
|
||||
let updateR = (newR: number) => {
|
||||
let oldR = dims.l + dims.w;
|
||||
if (oldR < newR) {
|
||||
let extraW = newR - oldR;
|
||||
dims.w += extraW;
|
||||
}
|
||||
}
|
||||
let updateT = (newT: number) => {
|
||||
if (newT < dims.t) {
|
||||
let extraH = dims.t - newT;
|
||||
dims.t = newT;
|
||||
dims.h += extraH;
|
||||
}
|
||||
}
|
||||
let updateB = (newB: number) => {
|
||||
let oldB = dims.t + dims.h;
|
||||
if (oldB < newB) {
|
||||
let extraH = newB - oldB;
|
||||
dims.h += extraH;
|
||||
}
|
||||
}
|
||||
|
||||
//labels
|
||||
let [xOff, yOff] = [-0.3, 0.3]; //HACK: these constants tweak the way "mkTxt" knows how to center the text
|
||||
const txtAspectRatio = [1.4, 1.0];
|
||||
if (opts && opts.top) {
|
||||
let size = opts.topSize;
|
||||
let txtW = size / txtAspectRatio[0];
|
||||
let txtH = size / txtAspectRatio[1];
|
||||
let [cx, y] = [elDims.l + elDims.w / 2, elDims.t - LBL_VERT_PAD - txtH / 2];
|
||||
let lbl = visuals.mkTxt(cx, y, size, 0, opts.top, xOff, yOff);
|
||||
svg.addClass(lbl, "cmp-lbl");
|
||||
svgEl.appendChild(lbl);
|
||||
|
||||
let len = txtW * opts.top.length;
|
||||
updateT(y - txtH / 2);
|
||||
updateL(cx - len / 2);
|
||||
updateR(cx + len / 2);
|
||||
}
|
||||
if (opts && opts.bot) {
|
||||
let size = opts.botSize;
|
||||
let txtW = size / txtAspectRatio[0];
|
||||
let txtH = size / txtAspectRatio[1];
|
||||
let [cx, y] = [elDims.l + elDims.w / 2, elDims.t + elDims.h + LBL_VERT_PAD + txtH / 2];
|
||||
let lbl = visuals.mkTxt(cx, y, size, 0, opts.bot, xOff, yOff);
|
||||
svg.addClass(lbl, "cmp-lbl");
|
||||
svgEl.appendChild(lbl);
|
||||
|
||||
let len = txtW * opts.bot.length;
|
||||
updateB(y + txtH / 2);
|
||||
updateL(cx - len / 2);
|
||||
updateR(cx + len / 2);
|
||||
}
|
||||
if (opts && opts.right) {
|
||||
let size = opts.rightSize;
|
||||
let txtW = size / txtAspectRatio[0];
|
||||
let txtH = size / txtAspectRatio[1];
|
||||
let len = txtW * opts.right.length;
|
||||
let [cx, cy] = [elDims.l + elDims.w + LBL_RIGHT_PAD + len / 2, elDims.t + elDims.h / 2];
|
||||
let lbl = visuals.mkTxt(cx, cy, size, 0, opts.right, xOff, yOff);
|
||||
svg.addClass(lbl, "cmp-lbl");
|
||||
svgEl.appendChild(lbl);
|
||||
|
||||
updateT(cy - txtH / 2);
|
||||
updateR(cx + len / 2);
|
||||
updateB(cy + txtH / 2);
|
||||
}
|
||||
if (opts && opts.left) {
|
||||
let size = opts.leftSize;
|
||||
let txtW = size / txtAspectRatio[0];
|
||||
let txtH = size / txtAspectRatio[1];
|
||||
let len = txtW * opts.left.length;
|
||||
let [cx, cy] = [elDims.l - LBL_LEFT_PAD - len / 2, elDims.t + elDims.h / 2];
|
||||
let lbl = visuals.mkTxt(cx, cy, size, 0, opts.left, xOff, yOff);
|
||||
svg.addClass(lbl, "cmp-lbl");
|
||||
svgEl.appendChild(lbl);
|
||||
|
||||
updateT(cy - txtH / 2);
|
||||
updateL(cx - len / 2);
|
||||
updateB(cy + txtH / 2);
|
||||
}
|
||||
|
||||
let svgAtts = {
|
||||
"viewBox": `${dims.l} ${dims.t} ${dims.w} ${dims.h}`,
|
||||
"width": dims.w,
|
||||
"height": dims.h,
|
||||
"preserveAspectRatio": "xMidYMid",
|
||||
};
|
||||
svg.hydrate(svgEl, svgAtts);
|
||||
let div = document.createElement("div");
|
||||
div.appendChild(svgEl);
|
||||
return div;
|
||||
}
|
||||
function mkCmpDiv(cmp: "wire" | string | PartVisualDefinition, opts: mkCmpDivOpts): HTMLElement {
|
||||
let el: visuals.SVGElAndSize;
|
||||
if (cmp == "wire") {
|
||||
//TODO: support non-croc wire parts
|
||||
el = visuals.mkWirePart([0, 0], opts.wireClr || "red", true);
|
||||
} else if (typeof cmp == "string") {
|
||||
let builtinVis = <string>cmp;
|
||||
let cnstr = builtinComponentPartVisual[builtinVis];
|
||||
el = cnstr([0, 0]);
|
||||
} else {
|
||||
let partVis = <PartVisualDefinition> cmp;
|
||||
el = visuals.mkGenericPartSVG(partVis);
|
||||
}
|
||||
return wrapSvg(el, opts);
|
||||
}
|
||||
type BoardProps = {
|
||||
boardDef: BoardDefinition,
|
||||
cmpDefs: Map<PartDefinition>,
|
||||
fnArgs: any,
|
||||
allAlloc: AllocatorResult,
|
||||
stepToWires: WireInst[][],
|
||||
stepToCmps: CmpInst[][]
|
||||
allWires: WireInst[],
|
||||
allCmps: CmpInst[],
|
||||
lastStep: number,
|
||||
colorToWires: Map<WireInst[]>,
|
||||
allWireColors: string[],
|
||||
};
|
||||
function mkBoardProps(allocOpts: AllocatorOpts): BoardProps {
|
||||
let allocRes = allocateDefinitions(allocOpts);
|
||||
let {powerWires, components} = allocRes;
|
||||
let stepToWires: WireInst[][] = [];
|
||||
let stepToCmps: CmpInst[][] = [];
|
||||
powerWires.forEach(w => {
|
||||
let step = w.assemblyStep + 1;
|
||||
(stepToWires[step] || (stepToWires[step] = [])).push(w)
|
||||
});
|
||||
let getMaxStep = (ns: {assemblyStep: number}[]) => ns.reduce((m, n) => Math.max(m, n.assemblyStep), 0);
|
||||
let stepOffset = getMaxStep(powerWires) + 2;
|
||||
components.forEach(cAndWs => {
|
||||
let {component, wires} = cAndWs;
|
||||
let cStep = component.assemblyStep + stepOffset;
|
||||
let arr = stepToCmps[cStep] || (stepToCmps[cStep] = []);
|
||||
arr.push(component);
|
||||
let wSteps = wires.map(w => w.assemblyStep + stepOffset);
|
||||
wires.forEach((w, i) => {
|
||||
let wStep = wSteps[i];
|
||||
let arr = stepToWires[wStep] || (stepToWires[wStep] = []);
|
||||
arr.push(w);
|
||||
})
|
||||
stepOffset = Math.max(cStep, wSteps.reduce((m, n) => Math.max(m, n), 0)) + 1;
|
||||
});
|
||||
let lastStep = stepOffset - 1;
|
||||
let allCmps = components.map(p => p.component);
|
||||
let allWires = powerWires.concat(components.map(p => p.wires).reduce((p, n) => p.concat(n), []));
|
||||
let colorToWires: Map<WireInst[]> = {}
|
||||
let allWireColors: string[] = [];
|
||||
allWires.forEach(w => {
|
||||
if (!colorToWires[w.color]) {
|
||||
colorToWires[w.color] = [];
|
||||
allWireColors.push(w.color);
|
||||
}
|
||||
colorToWires[w.color].push(w);
|
||||
});
|
||||
return {
|
||||
boardDef: allocOpts.boardDef,
|
||||
cmpDefs: allocOpts.cmpDefs,
|
||||
fnArgs: allocOpts.fnArgs,
|
||||
allAlloc: allocRes,
|
||||
stepToWires: stepToWires,
|
||||
stepToCmps: stepToCmps,
|
||||
allWires: allWires,
|
||||
allCmps: allCmps,
|
||||
lastStep: lastStep,
|
||||
colorToWires: colorToWires,
|
||||
allWireColors: allWireColors,
|
||||
};
|
||||
}
|
||||
function mkBlankBoardAndBreadboard(boardDef: BoardDefinition, cmpDefs: Map<PartDefinition>, fnArgs: any, width: number, buildMode: boolean = false): visuals.BoardHost {
|
||||
let state = runtime.board as pxsim.DalBoard;
|
||||
let boardHost = new visuals.BoardHost({
|
||||
state: state,
|
||||
boardDef: boardDef,
|
||||
forceBreadboard: true,
|
||||
cmpDefs: cmpDefs,
|
||||
maxWidth: `${width}px`,
|
||||
fnArgs: fnArgs,
|
||||
wireframe: buildMode,
|
||||
});
|
||||
let view = boardHost.getView();
|
||||
svg.addClass(view, "board-svg");
|
||||
|
||||
//set smiley
|
||||
//HACK
|
||||
// let img = board.board.displayCmp.image;
|
||||
// img.set(1, 0, 255);
|
||||
// img.set(3, 0, 255);
|
||||
// img.set(0, 2, 255);
|
||||
// img.set(1, 3, 255);
|
||||
// img.set(2, 3, 255);
|
||||
// img.set(3, 3, 255);
|
||||
// img.set(4, 2, 255);
|
||||
// board.updateState();
|
||||
|
||||
return boardHost;
|
||||
}
|
||||
function drawSteps(board: visuals.BoardHost, step: number, props: BoardProps) {
|
||||
let view = board.getView();
|
||||
if (step > 0) {
|
||||
svg.addClass(view, "grayed");
|
||||
}
|
||||
|
||||
for (let i = 0; i <= step; i++) {
|
||||
let wires = props.stepToWires[i];
|
||||
if (wires) {
|
||||
wires.forEach(w => {
|
||||
let wire = board.addWire(w)
|
||||
//last step
|
||||
if (i === step) {
|
||||
//location highlights
|
||||
if (w.start.type == "breadboard") {
|
||||
let lbls = board.highlightBreadboardPin((<BBLoc>w.start).rowCol);
|
||||
} else {
|
||||
board.highlightBoardPin((<BoardLoc>w.start).pin);
|
||||
}
|
||||
if (w.end.type == "breadboard") {
|
||||
let [row, col] = (<BBLoc>w.end).rowCol;
|
||||
let lbls = board.highlightBreadboardPin((<BBLoc>w.end).rowCol);
|
||||
} else {
|
||||
board.highlightBoardPin((<BoardLoc>w.end).pin);
|
||||
}
|
||||
//highlight wire
|
||||
board.highlightWire(wire);
|
||||
}
|
||||
});
|
||||
}
|
||||
let cmps = props.stepToCmps[i];
|
||||
if (cmps) {
|
||||
cmps.forEach(cmpInst => {
|
||||
let cmp = board.addComponent(cmpInst)
|
||||
let colOffset = (<any>cmpInst.visual).breadboardStartColIdx || 0;
|
||||
let rowCol: BBRowCol = [`${cmpInst.breadboardStartRow}`, `${colOffset + cmpInst.breadboardStartColumn}`];
|
||||
//last step
|
||||
if (i === step) {
|
||||
board.highlightBreadboardPin(rowCol);
|
||||
if (cmpInst.visual === "buttonpair") {
|
||||
//TODO: don't specialize this
|
||||
let rowCol2: BBRowCol = [`${cmpInst.breadboardStartRow}`, `${cmpInst.breadboardStartColumn + 3}`];
|
||||
board.highlightBreadboardPin(rowCol2);
|
||||
}
|
||||
svg.addClass(cmp.element, "notgrayed");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
function mkPanel() {
|
||||
//panel
|
||||
let panel = document.createElement("div");
|
||||
addClass(panel, "instr-panel");
|
||||
|
||||
return panel;
|
||||
}
|
||||
function mkPartsPanel(props: BoardProps) {
|
||||
let panel = mkPanel();
|
||||
|
||||
// board and breadboard
|
||||
let boardImg = mkBoardImgSvg(props.boardDef.visual);
|
||||
let board = wrapSvg(boardImg, {left: QUANT_LBL(1), leftSize: QUANT_LBL_SIZE, cmpScale: PARTS_BOARD_SCALE});
|
||||
panel.appendChild(board);
|
||||
let bbRaw = mkBBSvg();
|
||||
let bb = wrapSvg(bbRaw, {left: QUANT_LBL(1), leftSize: QUANT_LBL_SIZE, cmpScale: PARTS_BB_SCALE});
|
||||
panel.appendChild(bb);
|
||||
|
||||
// components
|
||||
let cmps = props.allCmps;
|
||||
cmps.forEach(c => {
|
||||
let quant = 1;
|
||||
// TODO: don't special case this
|
||||
if (c.visual === "buttonpair") {
|
||||
quant = 2;
|
||||
}
|
||||
let cmp = mkCmpDiv(c.visual, {
|
||||
left: QUANT_LBL(quant),
|
||||
leftSize: QUANT_LBL_SIZE,
|
||||
cmpScale: PARTS_CMP_SCALE,
|
||||
});
|
||||
addClass(cmp, "partslist-cmp");
|
||||
panel.appendChild(cmp);
|
||||
});
|
||||
|
||||
// wires
|
||||
props.allWireColors.forEach(clr => {
|
||||
let quant = props.colorToWires[clr].length;
|
||||
let cmp = mkCmpDiv("wire", {
|
||||
left: QUANT_LBL(quant),
|
||||
leftSize: WIRE_QUANT_LBL_SIZE,
|
||||
wireClr: clr,
|
||||
cmpScale: PARTS_WIRE_SCALE
|
||||
})
|
||||
addClass(cmp, "partslist-wire");
|
||||
panel.appendChild(cmp);
|
||||
})
|
||||
|
||||
return panel;
|
||||
}
|
||||
function mkStepPanel(step: number, props: BoardProps) {
|
||||
let panel = mkPanel();
|
||||
|
||||
//board
|
||||
let board = mkBlankBoardAndBreadboard(props.boardDef, props.cmpDefs, props.fnArgs, BOARD_WIDTH, true)
|
||||
drawSteps(board, step, props);
|
||||
panel.appendChild(board.getView());
|
||||
|
||||
//number
|
||||
let numDiv = document.createElement("div");
|
||||
addClass(numDiv, "panel-num-outer");
|
||||
addClass(numDiv, "noselect");
|
||||
panel.appendChild(numDiv)
|
||||
let num = document.createElement("div");
|
||||
addClass(num, "panel-num");
|
||||
num.textContent = (step + 1) + "";
|
||||
numDiv.appendChild(num)
|
||||
|
||||
// add requirements
|
||||
let reqsDiv = document.createElement("div");
|
||||
addClass(reqsDiv, "reqs-div")
|
||||
panel.appendChild(reqsDiv);
|
||||
let wires = (props.stepToWires[step] || []);
|
||||
let mkLabel = (loc: Loc) => {
|
||||
if (loc.type === "breadboard") {
|
||||
let [row, col] = (<BBLoc>loc).rowCol;
|
||||
return `(${row},${col})`
|
||||
} else
|
||||
return (<BoardLoc>loc).pin;
|
||||
};
|
||||
wires.forEach(w => {
|
||||
let cmp = mkCmpDiv("wire", {
|
||||
top: mkLabel(w.end),
|
||||
topSize: LOC_LBL_SIZE,
|
||||
bot: mkLabel(w.start),
|
||||
botSize: LOC_LBL_SIZE,
|
||||
wireClr: w.color,
|
||||
cmpHeight: REQ_WIRE_HEIGHT
|
||||
})
|
||||
addClass(cmp, "cmp-div");
|
||||
reqsDiv.appendChild(cmp);
|
||||
});
|
||||
let cmps = (props.stepToCmps[step] || []);
|
||||
cmps.forEach(c => {
|
||||
let l: BBRowCol = [`${c.breadboardStartRow}`, `${c.breadboardStartColumn}`];
|
||||
let locs = [l];
|
||||
if (c.visual === "buttonpair") {
|
||||
//TODO: don't special case this
|
||||
let l2: BBRowCol = [`${c.breadboardStartRow}`, `${c.breadboardStartColumn + 3}`];
|
||||
locs.push(l2);
|
||||
}
|
||||
locs.forEach((l, i) => {
|
||||
let [row, col] = l;
|
||||
let cmp = mkCmpDiv(c.visual, {
|
||||
top: `(${row},${col})`,
|
||||
topSize: LOC_LBL_SIZE,
|
||||
cmpHeight: REQ_CMP_HEIGHT,
|
||||
cmpScale: REQ_CMP_SCALE
|
||||
})
|
||||
addClass(cmp, "cmp-div");
|
||||
reqsDiv.appendChild(cmp);
|
||||
});
|
||||
});
|
||||
|
||||
return panel;
|
||||
}
|
||||
function updateFrontPanel(props: BoardProps): [HTMLElement, BoardProps] {
|
||||
let panel = document.getElementById("front-panel");
|
||||
|
||||
let board = mkBlankBoardAndBreadboard(props.boardDef, props.cmpDefs, props.fnArgs, FRONT_PAGE_BOARD_WIDTH, false);
|
||||
board.addAll(props.allAlloc);
|
||||
panel.appendChild(board.getView());
|
||||
|
||||
return [panel, props];
|
||||
}
|
||||
function mkFinalPanel(props: BoardProps) {
|
||||
const BACK_PAGE_BOARD_WIDTH = PANEL_WIDTH - 20;
|
||||
|
||||
let panel = mkPanel();
|
||||
addClass(panel, "back-panel");
|
||||
let board = mkBlankBoardAndBreadboard(props.boardDef, props.cmpDefs, props.fnArgs, BACK_PAGE_BOARD_WIDTH, false)
|
||||
board.addAll(props.allAlloc);
|
||||
panel.appendChild(board.getView());
|
||||
|
||||
return panel;
|
||||
}
|
||||
export function drawInstructions() {
|
||||
let getQsVal = parseQueryString();
|
||||
|
||||
//project name
|
||||
let name = getQsVal("name") || "Untitled";
|
||||
if (name) {
|
||||
$("#proj-title").text(name);
|
||||
}
|
||||
|
||||
//project code
|
||||
let tsCode = getQsVal("code");
|
||||
let tsPackage = getQsVal("package") || "";
|
||||
let codeSpinnerDiv = document.getElementById("proj-code-spinner");
|
||||
let codeContainerDiv = document.getElementById("proj-code-container");
|
||||
if (tsCode) {
|
||||
//we use the docs renderer to decompile the code to blocks and render it
|
||||
//TODO: render the blocks code directly
|
||||
let md =
|
||||
`\`\`\`blocks
|
||||
${tsCode}
|
||||
\`\`\`
|
||||
\`\`\`package
|
||||
${tsPackage}
|
||||
\`\`\`
|
||||
`
|
||||
|
||||
pxtdocs.requireMarked = function() { return (<any>window).marked; }
|
||||
pxtrunner.renderMarkdownAsync(codeContainerDiv, md)
|
||||
.done(function() {
|
||||
let codeSvg = $("#proj-code-container svg");
|
||||
if (codeSvg.length > 0) {
|
||||
//code rendered successfully as blocks
|
||||
codeSvg.css("width", "inherit");
|
||||
codeSvg.css("height", "inherit");
|
||||
//takes the svg out of the wrapper markdown
|
||||
codeContainerDiv.innerHTML = "";
|
||||
codeContainerDiv.appendChild(codeSvg[0]);
|
||||
} else {
|
||||
//code failed to convert to blocks, display as typescript instead
|
||||
codeContainerDiv.innerText = tsCode;
|
||||
}
|
||||
$(codeContainerDiv).show();
|
||||
$(codeSpinnerDiv).hide();
|
||||
});
|
||||
}
|
||||
|
||||
//parts list
|
||||
let parts = (getQsVal("parts") || "").split(" ");
|
||||
parts.sort();
|
||||
|
||||
//fn args
|
||||
let fnArgs = JSON.parse((getQsVal("fnArgs") || "{}"));
|
||||
|
||||
//init runtime
|
||||
const COMP_CODE = "";
|
||||
if (!pxsim.initCurrentRuntime)
|
||||
pxsim.initCurrentRuntime = initRuntimeWithDalBoard;
|
||||
pxsim.runtime = new Runtime(COMP_CODE);
|
||||
pxsim.runtime.board = null;
|
||||
pxsim.initCurrentRuntime();
|
||||
|
||||
let style = document.createElement("style");
|
||||
document.head.appendChild(style);
|
||||
|
||||
style.textContent += STYLE;
|
||||
|
||||
const boardDef = CURRENT_BOARD;
|
||||
const cmpDefs = PART_DEFINITIONS;
|
||||
|
||||
//props
|
||||
let dummyBreadboard = new visuals.Breadboard({});
|
||||
let onboardCmps = boardDef.onboardComponents || [];
|
||||
let activeComponents = (parts || []).filter(c => onboardCmps.indexOf(c) < 0);
|
||||
activeComponents.sort();
|
||||
let props = mkBoardProps({
|
||||
boardDef: boardDef,
|
||||
cmpDefs: cmpDefs,
|
||||
cmpList: activeComponents,
|
||||
fnArgs: fnArgs,
|
||||
getBBCoord: dummyBreadboard.getCoord.bind(dummyBreadboard)
|
||||
});
|
||||
|
||||
//front page
|
||||
let frontPanel = updateFrontPanel(props);
|
||||
|
||||
//all required parts
|
||||
let partsPanel = mkPartsPanel(props);
|
||||
document.body.appendChild(partsPanel);
|
||||
|
||||
//steps
|
||||
for (let s = 0; s <= props.lastStep; s++) {
|
||||
let p = mkStepPanel(s, props);
|
||||
document.body.appendChild(p);
|
||||
}
|
||||
|
||||
//final
|
||||
let finalPanel = mkFinalPanel(props);
|
||||
document.body.appendChild(finalPanel);
|
||||
}
|
||||
}
|
763
sim/libmbit.ts
@ -1,763 +0,0 @@
|
||||
/// <reference path="../node_modules/pxt-core/typings/bluebird/bluebird.d.ts"/>
|
||||
/// <reference path="../node_modules/pxt-core/built/pxtsim.d.ts"/>
|
||||
/// <reference path="../libs/microbit/dal.d.ts"/>
|
||||
|
||||
namespace pxsim {
|
||||
pxsim.initCurrentRuntime = () => {
|
||||
U.assert(!runtime.board);
|
||||
runtime.board = new Board();
|
||||
}
|
||||
|
||||
export function board() {
|
||||
return runtime.board as Board;
|
||||
}
|
||||
|
||||
export interface AnimationOptions {
|
||||
interval: number;
|
||||
// false means last frame
|
||||
frame: () => boolean;
|
||||
whenDone?: (cancelled: boolean) => void;
|
||||
}
|
||||
|
||||
export class AnimationQueue {
|
||||
private queue: AnimationOptions[] = [];
|
||||
private process: () => void;
|
||||
|
||||
constructor(private runtime: Runtime) {
|
||||
this.process = () => {
|
||||
let top = this.queue[0]
|
||||
if (!top) return
|
||||
if (this.runtime.dead) return
|
||||
runtime = this.runtime
|
||||
let res = top.frame()
|
||||
runtime.queueDisplayUpdate()
|
||||
runtime.maybeUpdateDisplay()
|
||||
if (res === false) {
|
||||
this.queue.shift();
|
||||
// if there is already something in the queue, start processing
|
||||
if (this.queue[0])
|
||||
setTimeout(this.process, this.queue[0].interval)
|
||||
// this may push additional stuff
|
||||
top.whenDone(false);
|
||||
} else {
|
||||
setTimeout(this.process, top.interval)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public cancelAll() {
|
||||
let q = this.queue
|
||||
this.queue = []
|
||||
for (let a of q) {
|
||||
a.whenDone(true)
|
||||
}
|
||||
}
|
||||
|
||||
public cancelCurrent() {
|
||||
let top = this.queue[0]
|
||||
if (top) {
|
||||
this.queue.shift();
|
||||
top.whenDone(true);
|
||||
}
|
||||
}
|
||||
|
||||
public enqueue(anim: AnimationOptions) {
|
||||
if (!anim.whenDone) anim.whenDone = () => { };
|
||||
this.queue.push(anim)
|
||||
// we start processing when the queue goes from 0 to 1
|
||||
if (this.queue.length == 1)
|
||||
this.process()
|
||||
}
|
||||
|
||||
public executeAsync(anim: AnimationOptions) {
|
||||
U.assert(!anim.whenDone)
|
||||
return new Promise<boolean>((resolve, reject) => {
|
||||
anim.whenDone = resolve
|
||||
this.enqueue(anim)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Error codes used in the micro:bit runtime.
|
||||
*/
|
||||
export enum PanicCode {
|
||||
// PANIC Codes. These are not return codes, but are terminal conditions.
|
||||
// These induce a panic operation, where all code stops executing, and a panic state is
|
||||
// entered where the panic code is diplayed.
|
||||
|
||||
// Out out memory error. Heap storage was requested, but is not available.
|
||||
MICROBIT_OOM = 20,
|
||||
|
||||
// Corruption detected in the micro:bit heap space
|
||||
MICROBIT_HEAP_ERROR = 30,
|
||||
|
||||
// Dereference of a NULL pointer through the ManagedType class,
|
||||
MICROBIT_NULL_DEREFERENCE = 40,
|
||||
};
|
||||
|
||||
export function panic(code: number) {
|
||||
console.log("PANIC:", code)
|
||||
led.setBrightness(255);
|
||||
let img = board().image;
|
||||
img.clear();
|
||||
img.set(0, 4, 255);
|
||||
img.set(1, 3, 255);
|
||||
img.set(2, 3, 255);
|
||||
img.set(3, 3, 255);
|
||||
img.set(4, 4, 255);
|
||||
img.set(0, 0, 255);
|
||||
img.set(1, 0, 255);
|
||||
img.set(0, 1, 255);
|
||||
img.set(1, 1, 255);
|
||||
img.set(3, 0, 255);
|
||||
img.set(4, 0, 255);
|
||||
img.set(3, 1, 255);
|
||||
img.set(4, 1, 255);
|
||||
runtime.updateDisplay();
|
||||
|
||||
throw new Error("PANIC " + code)
|
||||
}
|
||||
|
||||
export function getPin(id: number) {
|
||||
return board().pins.filter(p => p && p.id == id)[0] || null
|
||||
}
|
||||
|
||||
|
||||
export namespace AudioContextManager {
|
||||
let _context: any; // AudioContext
|
||||
let _vco: any; // OscillatorNode;
|
||||
let _vca: any; // GainNode;
|
||||
|
||||
function context(): any {
|
||||
if (!_context) _context = freshContext();
|
||||
return _context;
|
||||
}
|
||||
|
||||
function freshContext(): any {
|
||||
(<any>window).AudioContext = (<any>window).AudioContext || (<any>window).webkitAudioContext;
|
||||
if ((<any>window).AudioContext) {
|
||||
try {
|
||||
// this call my crash.
|
||||
// SyntaxError: audio resources unavailable for AudioContext construction
|
||||
return new (<any>window).AudioContext();
|
||||
} catch (e) { }
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function stop() {
|
||||
if (_vca) _vca.gain.value = 0;
|
||||
}
|
||||
|
||||
export function tone(frequency: number, gain: number) {
|
||||
if (frequency <= 0) return;
|
||||
let ctx = context();
|
||||
if (!ctx) return;
|
||||
|
||||
gain = Math.max(0, Math.min(1, gain));
|
||||
if (!_vco) {
|
||||
try {
|
||||
_vco = ctx.createOscillator();
|
||||
_vca = ctx.createGain();
|
||||
_vco.connect(_vca);
|
||||
_vca.connect(ctx.destination);
|
||||
_vca.gain.value = gain;
|
||||
_vco.start(0);
|
||||
} catch (e) {
|
||||
_vco = undefined;
|
||||
_vca = undefined;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
_vco.frequency.value = frequency;
|
||||
_vca.gain.value = gain;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
namespace pxsim.basic {
|
||||
export var pause = thread.pause;
|
||||
export var forever = thread.forever;
|
||||
|
||||
export function showNumber(x: number, interval: number) {
|
||||
if (interval < 0) return;
|
||||
|
||||
let leds = createImageFromString(x.toString());
|
||||
if (x < 0 || x >= 10) ImageMethods.scrollImage(leds, 1, interval);
|
||||
else showLeds(leds, interval * 5);
|
||||
}
|
||||
|
||||
export function showString(s: string, interval: number) {
|
||||
if (interval < 0) return;
|
||||
if (s.length == 0) {
|
||||
clearScreen();
|
||||
pause(interval * 5);
|
||||
} else {
|
||||
if (s.length == 1) showLeds(createImageFromString(s + " "), interval * 5)
|
||||
else ImageMethods.scrollImage(createImageFromString(s + " "), 1, interval);
|
||||
}
|
||||
}
|
||||
|
||||
export function showLeds(leds: Image, delay: number): void {
|
||||
showAnimation(leds, delay);
|
||||
}
|
||||
|
||||
export function clearScreen() {
|
||||
board().image.clear();
|
||||
runtime.queueDisplayUpdate()
|
||||
}
|
||||
|
||||
export function showAnimation(leds: Image, interval: number): void {
|
||||
ImageMethods.scrollImage(leds, 5, interval);
|
||||
}
|
||||
|
||||
export function plotLeds(leds: Image): void {
|
||||
ImageMethods.plotImage(leds, 0);
|
||||
}
|
||||
}
|
||||
|
||||
namespace pxsim.control {
|
||||
export var inBackground = thread.runInBackground;
|
||||
|
||||
export function reset() {
|
||||
U.userError("reset not implemented in simulator yet")
|
||||
}
|
||||
|
||||
export function waitMicros(micros: number) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
export function deviceName(): string {
|
||||
let b = board();
|
||||
return b && b.id
|
||||
? b.id.slice(0, 4)
|
||||
: "abcd";
|
||||
}
|
||||
|
||||
export function deviceSerialNumber(): number {
|
||||
let b = board();
|
||||
return parseInt(b && b.id
|
||||
? b.id.slice(1)
|
||||
: "42");
|
||||
}
|
||||
|
||||
export function onEvent(id: number, evid: number, handler: RefAction) {
|
||||
pxt.registerWithDal(id, evid, handler)
|
||||
}
|
||||
|
||||
export function raiseEvent(id: number, evid: number, mode: number) {
|
||||
// TODO mode?
|
||||
board().bus.queue(id, evid)
|
||||
}
|
||||
}
|
||||
|
||||
namespace pxsim.pxt {
|
||||
export function registerWithDal(id: number, evid: number, handler: RefAction) {
|
||||
board().bus.listen(id, evid, handler);
|
||||
}
|
||||
}
|
||||
|
||||
namespace pxsim.input {
|
||||
export function onButtonPressed(button: number, handler: RefAction): void {
|
||||
let b = board();
|
||||
if (button == DAL.MICROBIT_ID_BUTTON_AB && !board().usesButtonAB) {
|
||||
b.usesButtonAB = true;
|
||||
runtime.queueDisplayUpdate();
|
||||
}
|
||||
pxt.registerWithDal(button, DAL.MICROBIT_BUTTON_EVT_CLICK, handler);
|
||||
}
|
||||
|
||||
export function buttonIsPressed(button: number): boolean {
|
||||
let b = board();
|
||||
if (button == DAL.MICROBIT_ID_BUTTON_AB && !board().usesButtonAB) {
|
||||
b.usesButtonAB = true;
|
||||
runtime.queueDisplayUpdate();
|
||||
}
|
||||
let bts = b.buttons;
|
||||
if (button == DAL.MICROBIT_ID_BUTTON_A) return bts[0].pressed;
|
||||
if (button == DAL.MICROBIT_ID_BUTTON_B) return bts[1].pressed;
|
||||
return bts[2].pressed || (bts[0].pressed && bts[1].pressed);
|
||||
}
|
||||
|
||||
export function onGesture(gesture: number, handler: RefAction) {
|
||||
let b = board();
|
||||
b.accelerometer.activate();
|
||||
|
||||
if (gesture == 11 && !b.useShake) { // SAKE
|
||||
b.useShake = true;
|
||||
runtime.queueDisplayUpdate();
|
||||
}
|
||||
pxt.registerWithDal(DAL.MICROBIT_ID_GESTURE, gesture, handler);
|
||||
}
|
||||
|
||||
export function onPinPressed(pinId: number, handler: RefAction) {
|
||||
let pin = getPin(pinId);
|
||||
if (!pin) return;
|
||||
pin.isTouched();
|
||||
pxt.registerWithDal(pin.id, DAL.MICROBIT_BUTTON_EVT_CLICK, handler);
|
||||
}
|
||||
|
||||
export function onPinReleased(pinId: number, handler: RefAction) {
|
||||
let pin = getPin(pinId);
|
||||
if (!pin) return;
|
||||
pin.isTouched();
|
||||
pxt.registerWithDal(pin.id, DAL.MICROBIT_BUTTON_EVT_UP, handler);
|
||||
}
|
||||
|
||||
export function pinIsPressed(pinId: number): boolean {
|
||||
let pin = getPin(pinId);
|
||||
if (!pin) return false;
|
||||
return pin.isTouched();
|
||||
}
|
||||
|
||||
|
||||
|
||||
export function compassHeading(): number {
|
||||
let b = board();
|
||||
if (!b.usesHeading) {
|
||||
b.usesHeading = true;
|
||||
runtime.queueDisplayUpdate();
|
||||
}
|
||||
return b.heading;
|
||||
}
|
||||
|
||||
export function temperature(): number {
|
||||
let b = board();
|
||||
if (!b.usesTemperature) {
|
||||
b.usesTemperature = true;
|
||||
runtime.queueDisplayUpdate();
|
||||
}
|
||||
return b.temperature;
|
||||
}
|
||||
|
||||
export function acceleration(dimension: number): number {
|
||||
let b = board();
|
||||
let acc = b.accelerometer;
|
||||
acc.activate();
|
||||
switch (dimension) {
|
||||
case 0: return acc.getX();
|
||||
case 1: return acc.getY();
|
||||
case 2: return acc.getZ();
|
||||
default: return Math.floor(Math.sqrt(acc.instantaneousAccelerationSquared()));
|
||||
}
|
||||
}
|
||||
|
||||
export function rotation(kind: number): number {
|
||||
let b = board();
|
||||
let acc = b.accelerometer;
|
||||
acc.activate();
|
||||
let x = acc.getX(MicroBitCoordinateSystem.NORTH_EAST_DOWN);
|
||||
let y = acc.getX(MicroBitCoordinateSystem.NORTH_EAST_DOWN);
|
||||
let z = acc.getX(MicroBitCoordinateSystem.NORTH_EAST_DOWN);
|
||||
|
||||
let roll = Math.atan2(y, z);
|
||||
let pitch = Math.atan(-x / (y * Math.sin(roll) + z * Math.cos(roll)));
|
||||
|
||||
let r = 0;
|
||||
switch (kind) {
|
||||
case 0: r = pitch; break;
|
||||
case 1: r = roll; break;
|
||||
}
|
||||
return Math.floor(r / Math.PI * 180);
|
||||
}
|
||||
|
||||
export function setAccelerometerRange(range: number) {
|
||||
let b = board();
|
||||
b.accelerometer.setSampleRange(range);
|
||||
}
|
||||
|
||||
export function lightLevel(): number {
|
||||
let b = board();
|
||||
if (!b.usesLightLevel) {
|
||||
b.usesLightLevel = true;
|
||||
runtime.queueDisplayUpdate();
|
||||
}
|
||||
return b.lightLevel;
|
||||
}
|
||||
|
||||
export function magneticForce(): number {
|
||||
// TODO
|
||||
return 0;
|
||||
}
|
||||
|
||||
export function runningTime(): number {
|
||||
return runtime.runningTime();
|
||||
}
|
||||
|
||||
export function calibrate() {
|
||||
}
|
||||
}
|
||||
|
||||
namespace pxsim.led {
|
||||
export function plot(x: number, y: number) {
|
||||
board().image.set(x, y, 255);
|
||||
runtime.queueDisplayUpdate()
|
||||
}
|
||||
|
||||
export function unplot(x: number, y: number) {
|
||||
board().image.set(x, y, 0);
|
||||
runtime.queueDisplayUpdate()
|
||||
}
|
||||
|
||||
export function point(x: number, y: number): boolean {
|
||||
return !!board().image.get(x, y);
|
||||
}
|
||||
|
||||
export function brightness(): number {
|
||||
return board().brigthness;
|
||||
}
|
||||
|
||||
export function setBrightness(value: number): void {
|
||||
board().brigthness = value;
|
||||
runtime.queueDisplayUpdate()
|
||||
}
|
||||
|
||||
export function stopAnimation(): void {
|
||||
board().animationQ.cancelAll();
|
||||
}
|
||||
|
||||
export function setDisplayMode(mode: DisplayMode): void {
|
||||
board().displayMode = mode;
|
||||
runtime.queueDisplayUpdate()
|
||||
}
|
||||
|
||||
export function screenshot(): Image {
|
||||
let img = createImage(5)
|
||||
board().image.copyTo(0, 5, img, 0);
|
||||
return img;
|
||||
}
|
||||
}
|
||||
|
||||
namespace pxsim.serial {
|
||||
export function writeString(s: string) {
|
||||
board().writeSerial(s);
|
||||
}
|
||||
|
||||
export function readString(): string {
|
||||
return board().readSerial();
|
||||
}
|
||||
|
||||
export function readLine(): string {
|
||||
return board().readSerial();
|
||||
}
|
||||
|
||||
export function onDataReceived(delimiters: string, handler: RefAction) {
|
||||
let b = board();
|
||||
b.bus.listen(DAL.MICROBIT_ID_SERIAL, DAL.MICROBIT_SERIAL_EVT_DELIM_MATCH, handler);
|
||||
}
|
||||
|
||||
export function redirect(tx: number, rx: number, rate: number) {
|
||||
// TODO?
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
namespace pxsim.radio {
|
||||
export function broadcastMessage(msg: number): void {
|
||||
board().radio.broadcast(msg);
|
||||
}
|
||||
|
||||
export function onBroadcastMessageReceived(msg: number, handler: RefAction): void {
|
||||
pxt.registerWithDal(DAL.MES_BROADCAST_GENERAL_ID, msg, handler);
|
||||
}
|
||||
|
||||
export function setGroup(id: number): void {
|
||||
board().radio.setGroup(id);
|
||||
}
|
||||
|
||||
export function setTransmitPower(power: number): void {
|
||||
board().radio.setTransmitPower(power);
|
||||
}
|
||||
|
||||
export function setTransmitSerialNumber(transmit: boolean): void {
|
||||
board().radio.setTransmitSerialNumber(transmit);
|
||||
}
|
||||
|
||||
export function sendNumber(value: number): void {
|
||||
board().radio.datagram.send([value]);
|
||||
}
|
||||
|
||||
export function sendString(msg: string): void {
|
||||
board().radio.datagram.send(msg);
|
||||
}
|
||||
|
||||
export function writeValueToSerial(): void {
|
||||
let b = board();
|
||||
let v = b.radio.datagram.recv().data[0];
|
||||
b.writeSerial(`{v:${v}}`);
|
||||
}
|
||||
|
||||
export function sendValue(name: string, value: number) {
|
||||
board().radio.datagram.send([value]);
|
||||
}
|
||||
|
||||
export function receiveNumber(): number {
|
||||
let buffer = board().radio.datagram.recv().data;
|
||||
if (buffer instanceof Array) return buffer[0];
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
export function receiveString(): string {
|
||||
let buffer = board().radio.datagram.recv().data;
|
||||
if (typeof buffer === "string") return <string>buffer;
|
||||
return "";
|
||||
}
|
||||
|
||||
export function receivedNumberAt(index: number): number {
|
||||
let buffer = board().radio.datagram.recv().data;
|
||||
if (buffer instanceof Array) return buffer[index] || 0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
export function receivedSignalStrength(): number {
|
||||
return board().radio.datagram.lastReceived.rssi;
|
||||
}
|
||||
|
||||
export function onDataReceived(handler: RefAction): void {
|
||||
pxt.registerWithDal(DAL.MICROBIT_ID_RADIO, DAL.MICROBIT_RADIO_EVT_DATAGRAM, handler);
|
||||
radio.receiveNumber();
|
||||
}
|
||||
}
|
||||
|
||||
namespace pxsim.pins {
|
||||
export function onPulsed(name: number, pulse: number, body: RefAction) {
|
||||
}
|
||||
|
||||
export function pulseDuration(): number {
|
||||
return 0;
|
||||
}
|
||||
|
||||
export function createBuffer(sz: number) {
|
||||
return pxsim.BufferMethods.createBuffer(sz)
|
||||
}
|
||||
|
||||
export function digitalReadPin(pinId: number): number {
|
||||
let pin = getPin(pinId);
|
||||
if (!pin) return;
|
||||
pin.mode = PinFlags.Digital | PinFlags.Input;
|
||||
return pin.value > 100 ? 1 : 0;
|
||||
}
|
||||
|
||||
export function digitalWritePin(pinId: number, value: number) {
|
||||
let pin = getPin(pinId);
|
||||
if (!pin) return;
|
||||
pin.mode = PinFlags.Digital | PinFlags.Output;
|
||||
pin.value = value > 0 ? 1023 : 0;
|
||||
runtime.queueDisplayUpdate();
|
||||
}
|
||||
|
||||
export function setPull(pinId: number, pull: number) {
|
||||
let pin = getPin(pinId);
|
||||
if (!pin) return;
|
||||
pin.pull = pull;
|
||||
}
|
||||
|
||||
export function analogReadPin(pinId: number): number {
|
||||
let pin = getPin(pinId);
|
||||
if (!pin) return;
|
||||
pin.mode = PinFlags.Analog | PinFlags.Input;
|
||||
return pin.value || 0;
|
||||
}
|
||||
|
||||
export function analogWritePin(pinId: number, value: number) {
|
||||
let pin = getPin(pinId);
|
||||
if (!pin) return;
|
||||
pin.mode = PinFlags.Analog | PinFlags.Output;
|
||||
pin.value = value ? 1 : 0;
|
||||
runtime.queueDisplayUpdate();
|
||||
}
|
||||
|
||||
export function analogSetPeriod(pinId: number, micros: number) {
|
||||
let pin = getPin(pinId);
|
||||
if (!pin) return;
|
||||
pin.mode = PinFlags.Analog | PinFlags.Output;
|
||||
pin.period = micros;
|
||||
runtime.queueDisplayUpdate();
|
||||
}
|
||||
|
||||
export function servoWritePin(pinId: number, value: number) {
|
||||
analogSetPeriod(pinId, 20000);
|
||||
// TODO
|
||||
}
|
||||
|
||||
export function servoSetPulse(pinId: number, micros: number) {
|
||||
let pin = getPin(pinId);
|
||||
if (!pin) return;
|
||||
// TODO
|
||||
}
|
||||
|
||||
export function pulseIn(name: number, value: number, maxDuration: number): number {
|
||||
let pin = getPin(name);
|
||||
if (!pin) return 0;
|
||||
|
||||
return 5000;
|
||||
}
|
||||
|
||||
export function spiWrite(value: number): number {
|
||||
// TODO
|
||||
return 0;
|
||||
}
|
||||
|
||||
export function i2cReadBuffer(address: number, size: number, repeat?: boolean): RefBuffer {
|
||||
// fake reading zeros
|
||||
return createBuffer(size)
|
||||
}
|
||||
|
||||
export function i2cWriteBuffer(address: number, buf: RefBuffer, repeat?: boolean): void {
|
||||
// fake - noop
|
||||
}
|
||||
|
||||
export function analogSetPitchPin(pinId: number) {
|
||||
let pin = getPin(pinId);
|
||||
if (!pin) return;
|
||||
board().pins.filter(p => !!p).forEach(p => p.pitch = false);
|
||||
pin.pitch = true;
|
||||
}
|
||||
|
||||
export function analogPitch(frequency: number, ms: number) {
|
||||
// update analog output
|
||||
let pin = board().pins.filter(pin => !!pin && pin.pitch)[0] || board().pins[0];
|
||||
pin.mode = PinFlags.Analog | PinFlags.Output;
|
||||
if (frequency <= 0) {
|
||||
pin.value = 0;
|
||||
pin.period = 0;
|
||||
} else {
|
||||
pin.value = 512;
|
||||
pin.period = 1000000 / frequency;
|
||||
}
|
||||
runtime.queueDisplayUpdate();
|
||||
|
||||
let cb = getResume();
|
||||
AudioContextManager.tone(frequency, 1);
|
||||
if (ms <= 0) cb();
|
||||
else {
|
||||
setTimeout(() => {
|
||||
AudioContextManager.stop();
|
||||
pin.value = 0;
|
||||
pin.period = 0;
|
||||
pin.mode = PinFlags.Unused;
|
||||
runtime.queueDisplayUpdate();
|
||||
cb()
|
||||
}, ms);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
namespace pxsim.bluetooth {
|
||||
export function startIOPinService(): void {
|
||||
// TODO
|
||||
}
|
||||
export function startLEDService(): void {
|
||||
// TODO
|
||||
}
|
||||
export function startTemperatureService(): void {
|
||||
// TODO
|
||||
}
|
||||
export function startMagnetometerService(): void {
|
||||
// TODO
|
||||
}
|
||||
export function startAccelerometerService(): void {
|
||||
// TODO
|
||||
}
|
||||
export function startButtonService(): void {
|
||||
// TODO
|
||||
}
|
||||
}
|
||||
|
||||
namespace pxsim.images {
|
||||
export function createImage(img: Image) { return img }
|
||||
export function createBigImage(img: Image) { return img }
|
||||
}
|
||||
|
||||
namespace pxsim.ImageMethods {
|
||||
export function showImage(leds: Image, offset: number) {
|
||||
if (!leds) panic(PanicCode.MICROBIT_NULL_DEREFERENCE);
|
||||
|
||||
leds.copyTo(offset, 5, board().image, 0)
|
||||
runtime.queueDisplayUpdate()
|
||||
}
|
||||
|
||||
export function plotImage(leds: Image, offset: number): void {
|
||||
if (!leds) panic(PanicCode.MICROBIT_NULL_DEREFERENCE);
|
||||
|
||||
leds.copyTo(offset, 5, board().image, 0)
|
||||
runtime.queueDisplayUpdate()
|
||||
}
|
||||
|
||||
export function height(leds: Image): number {
|
||||
if (!leds) panic(PanicCode.MICROBIT_NULL_DEREFERENCE);
|
||||
return Image.height;
|
||||
}
|
||||
|
||||
export function width(leds: Image): number {
|
||||
if (!leds) panic(PanicCode.MICROBIT_NULL_DEREFERENCE);
|
||||
return leds.width;
|
||||
}
|
||||
|
||||
export function plotFrame(leds: Image, frame: number) {
|
||||
ImageMethods.plotImage(leds, frame * Image.height);
|
||||
}
|
||||
|
||||
export function showFrame(leds: Image, frame: number) {
|
||||
ImageMethods.showImage(leds, frame * Image.height);
|
||||
}
|
||||
|
||||
export function pixel(leds: Image, x: number, y: number): number {
|
||||
if (!leds) panic(PanicCode.MICROBIT_NULL_DEREFERENCE);
|
||||
return leds.get(x, y);
|
||||
}
|
||||
|
||||
export function setPixel(leds: Image, x: number, y: number, v: number) {
|
||||
if (!leds) panic(PanicCode.MICROBIT_NULL_DEREFERENCE);
|
||||
leds.set(x, y, v);
|
||||
}
|
||||
|
||||
export function clear(leds: Image) {
|
||||
if (!leds) panic(PanicCode.MICROBIT_NULL_DEREFERENCE);
|
||||
|
||||
leds.clear();
|
||||
}
|
||||
|
||||
export function setPixelBrightness(i: Image, x: number, y: number, b: number) {
|
||||
if (!i) panic(PanicCode.MICROBIT_NULL_DEREFERENCE);
|
||||
|
||||
i.set(x, y, b);
|
||||
}
|
||||
|
||||
export function pixelBrightness(i: Image, x: number, y: number): number {
|
||||
if (!i) panic(PanicCode.MICROBIT_NULL_DEREFERENCE);
|
||||
|
||||
return i.get(x, y);
|
||||
}
|
||||
|
||||
export function scrollImage(leds: Image, stride: number, interval: number): void {
|
||||
if (!leds) panic(PanicCode.MICROBIT_NULL_DEREFERENCE);
|
||||
if (stride == 0) stride = 1;
|
||||
|
||||
let cb = getResume();
|
||||
let off = stride > 0 ? 0 : leds.width - 1;
|
||||
let display = board().image;
|
||||
|
||||
board().animationQ.enqueue({
|
||||
interval: interval,
|
||||
frame: () => {
|
||||
//TODO: support right to left.
|
||||
if (off >= leds.width || off < 0) return false;
|
||||
stride > 0 ? display.shiftLeft(stride) : display.shiftRight(-stride);
|
||||
let c = Math.min(stride, leds.width - off);
|
||||
leds.copyTo(off, c, display, 5 - stride)
|
||||
off += stride;
|
||||
return true;
|
||||
},
|
||||
whenDone: cb
|
||||
})
|
||||
}
|
||||
}
|
@ -1,6 +1,183 @@
|
||||
namespace pxsim.micro_bit {
|
||||
const svg = pxsim.svg;
|
||||
namespace pxsim.visuals {
|
||||
const MB_STYLE = `
|
||||
svg.sim {
|
||||
margin-bottom:1em;
|
||||
}
|
||||
svg.sim.grayscale {
|
||||
-moz-filter: grayscale(1);
|
||||
-webkit-filter: grayscale(1);
|
||||
filter: grayscale(1);
|
||||
}
|
||||
.sim-button {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.sim-button-outer:hover {
|
||||
stroke:grey;
|
||||
stroke-width: 3px;
|
||||
}
|
||||
.sim-button-nut {
|
||||
fill:#704A4A;
|
||||
pointer-events:none;
|
||||
}
|
||||
.sim-button-nut:hover {
|
||||
stroke:1px solid #704A4A;
|
||||
}
|
||||
.sim-pin:hover {
|
||||
stroke:#D4AF37;
|
||||
stroke-width:2px;
|
||||
}
|
||||
|
||||
.sim-pin-touch.touched:hover {
|
||||
stroke:darkorange;
|
||||
}
|
||||
|
||||
.sim-led-back:hover {
|
||||
stroke:#a0a0a0;
|
||||
stroke-width:3px;
|
||||
}
|
||||
.sim-led:hover {
|
||||
stroke:#ff7f7f;
|
||||
stroke-width:3px;
|
||||
}
|
||||
|
||||
.sim-systemled {
|
||||
fill:#333;
|
||||
stroke:#555;
|
||||
stroke-width: 1px;
|
||||
}
|
||||
|
||||
.sim-light-level-button {
|
||||
stroke:#fff;
|
||||
stroke-width: 3px;
|
||||
}
|
||||
|
||||
.sim-antenna {
|
||||
stroke:#555;
|
||||
stroke-width: 2px;
|
||||
}
|
||||
|
||||
.sim-text {
|
||||
font-family:"Lucida Console", Monaco, monospace;
|
||||
font-size:25px;
|
||||
fill:#fff;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.sim-text-pin {
|
||||
font-family:"Lucida Console", Monaco, monospace;
|
||||
font-size:20px;
|
||||
fill:#fff;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.sim-thermometer {
|
||||
stroke:#aaa;
|
||||
stroke-width: 3px;
|
||||
}
|
||||
|
||||
/* animations */
|
||||
.sim-theme-glow {
|
||||
animation-name: sim-theme-glow-animation;
|
||||
animation-timing-function: ease-in-out;
|
||||
animation-direction: alternate;
|
||||
animation-iteration-count: infinite;
|
||||
animation-duration: 1.25s;
|
||||
}
|
||||
@keyframes sim-theme-glow-animation {
|
||||
from { opacity: 1; }
|
||||
to { opacity: 0.75; }
|
||||
}
|
||||
|
||||
.sim-flash {
|
||||
animation-name: sim-flash-animation;
|
||||
animation-duration: 0.1s;
|
||||
}
|
||||
|
||||
@keyframes sim-flash-animation {
|
||||
from { fill: yellow; }
|
||||
to { fill: default; }
|
||||
}
|
||||
|
||||
.sim-flash-stroke {
|
||||
animation-name: sim-flash-stroke-animation;
|
||||
animation-duration: 0.4s;
|
||||
animation-timing-function: ease-in;
|
||||
}
|
||||
|
||||
@keyframes sim-flash-stroke-animation {
|
||||
from { stroke: yellow; }
|
||||
to { stroke: default; }
|
||||
}
|
||||
|
||||
/* wireframe */
|
||||
.sim-wireframe * {
|
||||
fill: none;
|
||||
stroke: black;
|
||||
}
|
||||
.sim-wireframe .sim-display,
|
||||
.sim-wireframe .sim-led,
|
||||
.sim-wireframe .sim-led-back,
|
||||
.sim-wireframe .sim-head,
|
||||
.sim-wireframe .sim-theme,
|
||||
.sim-wireframe .sim-button-group,
|
||||
.sim-wireframe .sim-button-label,
|
||||
.sim-wireframe .sim-button,
|
||||
.sim-wireframe .sim-text-pin
|
||||
{
|
||||
visibility: hidden;
|
||||
}
|
||||
.sim-wireframe .sim-label
|
||||
{
|
||||
stroke: none;
|
||||
fill: #777;
|
||||
}
|
||||
.sim-wireframe .sim-board {
|
||||
stroke-width: 2px;
|
||||
}
|
||||
`;
|
||||
const pins4onXs = [66.7, 79.1, 91.4, 103.7, 164.3, 176.6, 188.9, 201.3, 213.6, 275.2, 287.5, 299.8, 312.1, 324.5, 385.1, 397.4, 409.7, 422];
|
||||
const pins4onMids = pins4onXs.map(x => x + 5);
|
||||
const littlePinDist = pins4onMids[1] - pins4onMids[0];
|
||||
const bigPinWidth = pins4onMids[4] - pins4onMids[3];
|
||||
const pin0mid = pins4onXs[0] - bigPinWidth / 2.0;
|
||||
const pin3mid = pin0mid - bigPinWidth / 2.0;
|
||||
const pin1mid = pins4onMids[3] + bigPinWidth / 2.0;
|
||||
const pin2mid = pins4onMids[8] + bigPinWidth / 2.0;
|
||||
const pin3Vmid = pins4onMids[13] + bigPinWidth / 2.0;
|
||||
const pinGNDmid = pins4onMids[pins4onMids.length - 1] + bigPinWidth / 2.0;
|
||||
const pinGND2mid = pinGNDmid + bigPinWidth / 2.0;
|
||||
const pinMids = [pin0mid, pin1mid, pin2mid, pin3mid].concat(pins4onXs).concat([pinGNDmid, pin3Vmid, pinGND2mid]);
|
||||
const pinNames = [
|
||||
"P0", "P1", "P2", "P3", "P4", "P5", "P6", "P7", "P8", "P9", "P10",
|
||||
"P11", "P12", "P13", "P14", "P15", "P16", "P17", "P18", "P19", "P20",
|
||||
"GND0", "GND", "+3v3", "GND1"];
|
||||
const pinTitles = [
|
||||
"P0, ANALOG IN",
|
||||
"P1, ANALOG IN",
|
||||
"P2, ANALOG IN",
|
||||
"P3, ANALOG IN, LED Col 1",
|
||||
"P4, ANALOG IN, LED Col 2",
|
||||
"P5, BUTTON A",
|
||||
"P6, LED Col 9",
|
||||
"P7, LED Col 8",
|
||||
"P8",
|
||||
"P9, LED Col 7",
|
||||
"P10, ANALOG IN, LED Col 3",
|
||||
"P11, BUTTON B",
|
||||
"P12, RESERVED ACCESSIBILITY",
|
||||
"P13, SPI - SCK",
|
||||
"P14, SPI - MISO",
|
||||
"P15, SPI - MOSI",
|
||||
"P16, SPI - Chip Select",
|
||||
"P17, +3v3",
|
||||
"P18, +3v3",
|
||||
"P19, I2C - SCL",
|
||||
"P20, I2C - SDA",
|
||||
"GND", "GND", "+3v3", "GND"
|
||||
];
|
||||
const MB_WIDTH = 500;
|
||||
const MB_HEIGHT = 408;
|
||||
export interface IBoardTheme {
|
||||
accent?: string;
|
||||
display?: string;
|
||||
@ -43,9 +220,10 @@ namespace pxsim.micro_bit {
|
||||
}
|
||||
|
||||
export interface IBoardProps {
|
||||
runtime: pxsim.Runtime;
|
||||
runtime?: pxsim.Runtime;
|
||||
theme?: IBoardTheme;
|
||||
disableTilt?: boolean;
|
||||
wireframe?: boolean;
|
||||
}
|
||||
|
||||
const pointerEvents = !!(window as any).PointerEvent ? {
|
||||
@ -60,11 +238,11 @@ namespace pxsim.micro_bit {
|
||||
leave: "mouseleave"
|
||||
};
|
||||
|
||||
export class MicrobitBoardSvg {
|
||||
export class MicrobitBoardSvg implements BoardView {
|
||||
public element: SVGSVGElement;
|
||||
private style: SVGStyleElement;
|
||||
private defs: SVGDefsElement;
|
||||
private g: SVGElement;
|
||||
private g: SVGGElement;
|
||||
|
||||
private logos: SVGElement[];
|
||||
private head: SVGGElement; private headInitialized = false;
|
||||
@ -88,15 +266,54 @@ namespace pxsim.micro_bit {
|
||||
private thermometerText: SVGTextElement;
|
||||
private shakeButton: SVGCircleElement;
|
||||
private shakeText: SVGTextElement;
|
||||
public board: pxsim.Board;
|
||||
public board: pxsim.DalBoard;
|
||||
private pinNmToCoord: Map<Coord> = {};
|
||||
|
||||
constructor(public props: IBoardProps) {
|
||||
this.board = this.props.runtime.board as pxsim.Board;
|
||||
this.board.updateView = () => this.updateState();
|
||||
this.recordPinCoords();
|
||||
this.buildDom();
|
||||
this.updateTheme();
|
||||
this.updateState();
|
||||
this.attachEvents();
|
||||
if (props && props.wireframe)
|
||||
svg.addClass(this.element, "sim-wireframe");
|
||||
|
||||
if (props && props.theme)
|
||||
this.updateTheme();
|
||||
|
||||
if (props && props.runtime) {
|
||||
this.board = this.props.runtime.board as pxsim.DalBoard;
|
||||
this.board.updateSubscribers.push(() => this.updateState());
|
||||
this.updateState();
|
||||
this.attachEvents();
|
||||
}
|
||||
}
|
||||
|
||||
public getView(): SVGAndSize<SVGSVGElement> {
|
||||
return {
|
||||
el: this.element,
|
||||
y: 0,
|
||||
x: 0,
|
||||
w: MB_WIDTH,
|
||||
h: MB_HEIGHT
|
||||
};
|
||||
}
|
||||
|
||||
public getCoord(pinNm: string): Coord {
|
||||
return this.pinNmToCoord[pinNm];
|
||||
}
|
||||
|
||||
public highlightPin(pinNm: string): void {
|
||||
//TODO: for instructions
|
||||
}
|
||||
|
||||
public getPinDist(): number {
|
||||
return littlePinDist * 1.7;
|
||||
}
|
||||
|
||||
public recordPinCoords() {
|
||||
const pinsY = 356.7 + 40;
|
||||
pinNames.forEach((nm, i) => {
|
||||
let x = pinMids[i];
|
||||
this.pinNmToCoord[nm] = [x, pinsY];
|
||||
});
|
||||
}
|
||||
|
||||
private updateTheme() {
|
||||
@ -123,12 +340,14 @@ namespace pxsim.micro_bit {
|
||||
if (!state) return;
|
||||
let theme = this.props.theme;
|
||||
|
||||
state.buttons.forEach((btn, index) => {
|
||||
let bpState = state.buttonPairState;
|
||||
let buttons = [bpState.aBtn, bpState.bBtn, bpState.abBtn];
|
||||
buttons.forEach((btn, index) => {
|
||||
svg.fill(this.buttons[index], btn.pressed ? theme.buttonDown : theme.buttonUp);
|
||||
});
|
||||
|
||||
let bw = state.displayMode == pxsim.DisplayMode.bw
|
||||
let img = state.image;
|
||||
let bw = state.ledMatrixState.displayMode == pxsim.DisplayMode.bw
|
||||
let img = state.ledMatrixState.image;
|
||||
this.leds.forEach((led, i) => {
|
||||
let sel = (<SVGStylable><any>led)
|
||||
sel.style.opacity = ((bw ? img.data[i] > 0 ? 255 : 0 : img.data[i]) / 255.0) + "";
|
||||
@ -147,7 +366,7 @@ namespace pxsim.micro_bit {
|
||||
|
||||
private updateGestures() {
|
||||
let state = this.board;
|
||||
if (state.useShake && !this.shakeButton) {
|
||||
if (state.accelerometerState.useShake && !this.shakeButton) {
|
||||
this.shakeButton = svg.child(this.g, "circle", { cx: 380, cy: 100, r: 16.5 }) as SVGCircleElement;
|
||||
svg.fill(this.shakeButton, this.props.theme.virtualButtonUp)
|
||||
this.shakeButton.addEventListener(pointerEvents.down, ev => {
|
||||
@ -170,7 +389,7 @@ namespace pxsim.micro_bit {
|
||||
|
||||
private updateButtonAB() {
|
||||
let state = this.board;
|
||||
if (state.usesButtonAB && !this.buttonABText) {
|
||||
if (state.buttonPairState.usesButtonAB && !this.buttonABText) {
|
||||
(<any>this.buttonsOuter[2]).style.visibility = "visible";
|
||||
(<any>this.buttons[2]).style.visibility = "visible";
|
||||
this.buttonABText = svg.child(this.g, "text", { class: "sim-text", x: 370, y: 272 }) as SVGTextElement;
|
||||
@ -203,7 +422,7 @@ namespace pxsim.micro_bit {
|
||||
|
||||
private updateTemperature() {
|
||||
let state = this.board;
|
||||
if (!state || !state.usesTemperature) return;
|
||||
if (!state || !state.thermometerState.usesTemperature) return;
|
||||
|
||||
let tmin = -5;
|
||||
let tmax = 50;
|
||||
@ -227,13 +446,13 @@ namespace pxsim.micro_bit {
|
||||
(ev) => {
|
||||
let cur = svg.cursorPoint(pt, this.element, ev);
|
||||
let t = Math.max(0, Math.min(1, (260 - cur.y) / 140))
|
||||
state.temperature = Math.floor(tmin + t * (tmax - tmin));
|
||||
state.thermometerState.temperature = Math.floor(tmin + t * (tmax - tmin));
|
||||
this.updateTemperature();
|
||||
}, ev => { }, ev => { })
|
||||
}
|
||||
|
||||
let t = Math.max(tmin, Math.min(tmax, state.temperature))
|
||||
let per = Math.floor((state.temperature - tmin) / (tmax - tmin) * 100)
|
||||
let t = Math.max(tmin, Math.min(tmax, state.thermometerState.temperature))
|
||||
let per = Math.floor((state.thermometerState.temperature - tmin) / (tmax - tmin) * 100)
|
||||
svg.setGradientValue(this.thermometerGradient, 100 - per + "%");
|
||||
this.thermometerText.textContent = t + "°C";
|
||||
}
|
||||
@ -242,7 +461,7 @@ namespace pxsim.micro_bit {
|
||||
let xc = 258;
|
||||
let yc = 75;
|
||||
let state = this.board;
|
||||
if (!state || !state.usesHeading) return;
|
||||
if (!state || !state.compassState.usesHeading) return;
|
||||
if (!this.headInitialized) {
|
||||
let p = this.head.firstChild.nextSibling as SVGPathElement;
|
||||
p.setAttribute("d", "m269.9,50.134647l0,0l-39.5,0l0,0c-14.1,0.1 -24.6,10.7 -24.6,24.8c0,13.9 10.4,24.4 24.3,24.7l0,0l39.6,0c14.2,0 40.36034,-22.97069 40.36034,-24.85394c0,-1.88326 -26.06034,-24.54606 -40.16034,-24.64606m-0.2,39l0,0l-39.3,0c-7.7,-0.1 -14,-6.4 -14,-14.2c0,-7.8 6.4,-14.2 14.2,-14.2l39.1,0c7.8,0 14.2,6.4 14.2,14.2c0,7.9 -6.4,14.2 -14.2,14.2l0,0l0,0z");
|
||||
@ -252,16 +471,16 @@ namespace pxsim.micro_bit {
|
||||
this.head,
|
||||
(ev: MouseEvent) => {
|
||||
let cur = svg.cursorPoint(pt, this.element, ev);
|
||||
state.heading = Math.floor(Math.atan2(cur.y - yc, cur.x - xc) * 180 / Math.PI + 90);
|
||||
if (state.heading < 0) state.heading += 360;
|
||||
state.compassState.heading = Math.floor(Math.atan2(cur.y - yc, cur.x - xc) * 180 / Math.PI + 90);
|
||||
if (state.compassState.heading < 0) state.compassState.heading += 360;
|
||||
this.updateHeading();
|
||||
});
|
||||
this.headInitialized = true;
|
||||
}
|
||||
|
||||
let txt = state.heading.toString() + "°";
|
||||
let txt = state.compassState.heading.toString() + "°";
|
||||
if (txt != this.headText.textContent) {
|
||||
svg.rotateElement(this.head, xc, yc, state.heading + 180);
|
||||
svg.rotateElement(this.head, xc, yc, state.compassState.heading + 180);
|
||||
this.headText.textContent = txt;
|
||||
}
|
||||
}
|
||||
@ -297,12 +516,12 @@ namespace pxsim.micro_bit {
|
||||
let state = this.board;
|
||||
if (!state) return;
|
||||
|
||||
state.pins.forEach((pin, i) => this.updatePin(pin, i));
|
||||
state.edgeConnectorState.pins.forEach((pin, i) => this.updatePin(pin, i));
|
||||
}
|
||||
|
||||
private updateLightLevel() {
|
||||
let state = this.board;
|
||||
if (!state || !state.usesLightLevel) return;
|
||||
if (!state || !state.lightSensorState.usesLightLevel) return;
|
||||
|
||||
if (!this.lightLevelButton) {
|
||||
let gid = "gradient-light-level";
|
||||
@ -320,8 +539,8 @@ namespace pxsim.micro_bit {
|
||||
let pos = svg.cursorPoint(pt, this.element, ev);
|
||||
let rs = r / 2;
|
||||
let level = Math.max(0, Math.min(255, Math.floor((pos.y - (cy - rs)) / (2 * rs) * 255)));
|
||||
if (level != this.board.lightLevel) {
|
||||
this.board.lightLevel = level;
|
||||
if (level != this.board.lightSensorState.lightLevel) {
|
||||
this.board.lightSensorState.lightLevel = level;
|
||||
this.applyLightLevel();
|
||||
}
|
||||
}, ev => { },
|
||||
@ -330,12 +549,12 @@ namespace pxsim.micro_bit {
|
||||
this.updateTheme();
|
||||
}
|
||||
|
||||
svg.setGradientValue(this.lightLevelGradient, Math.min(100, Math.max(0, Math.floor(state.lightLevel * 100 / 255))) + '%')
|
||||
this.lightLevelText.textContent = state.lightLevel.toString();
|
||||
svg.setGradientValue(this.lightLevelGradient, Math.min(100, Math.max(0, Math.floor(state.lightSensorState.lightLevel * 100 / 255))) + '%')
|
||||
this.lightLevelText.textContent = state.lightSensorState.lightLevel.toString();
|
||||
}
|
||||
|
||||
private applyLightLevel() {
|
||||
let lv = this.board.lightLevel;
|
||||
let lv = this.board.lightSensorState.lightLevel;
|
||||
svg.setGradientValue(this.lightLevelGradient, Math.min(100, Math.max(0, Math.floor(lv * 100 / 255))) + '%')
|
||||
this.lightLevelText.textContent = lv.toString();
|
||||
}
|
||||
@ -343,10 +562,10 @@ namespace pxsim.micro_bit {
|
||||
private updateTilt() {
|
||||
if (this.props.disableTilt) return;
|
||||
let state = this.board;
|
||||
if (!state || !state.accelerometer.isActive) return;
|
||||
if (!state || !state.accelerometerState.accelerometer.isActive) return;
|
||||
|
||||
let x = state.accelerometer.getX();
|
||||
let y = state.accelerometer.getY();
|
||||
let x = state.accelerometerState.accelerometer.getX();
|
||||
let y = state.accelerometerState.accelerometer.getY();
|
||||
let af = 8 / 1023;
|
||||
|
||||
this.element.style.transform = "perspective(30em) rotateX(" + y * af + "deg) rotateY(" + x * af + "deg)"
|
||||
@ -358,129 +577,18 @@ namespace pxsim.micro_bit {
|
||||
this.element = <SVGSVGElement>svg.elt("svg")
|
||||
svg.hydrate(this.element, {
|
||||
"version": "1.0",
|
||||
"viewBox": "0 0 498 406",
|
||||
"enable-background": "new 0 0 498 406",
|
||||
"viewBox": `0 0 ${MB_WIDTH} ${MB_HEIGHT}`,
|
||||
"class": "sim",
|
||||
"x": "0px",
|
||||
"y": "0px"
|
||||
"y": "0px",
|
||||
"width": MB_WIDTH + "px",
|
||||
"height": MB_HEIGHT + "px",
|
||||
});
|
||||
this.style = <SVGStyleElement>svg.child(this.element, "style", {});
|
||||
this.style.textContent = `
|
||||
svg.sim {
|
||||
margin-bottom:1em;
|
||||
}
|
||||
svg.sim.grayscale {
|
||||
-moz-filter: grayscale(1);
|
||||
-webkit-filter: grayscale(1);
|
||||
filter: grayscale(1);
|
||||
}
|
||||
.sim-button {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.sim-button-outer:hover {
|
||||
stroke:grey;
|
||||
stroke-width: 3px;
|
||||
}
|
||||
.sim-button-nut {
|
||||
fill:#704A4A;
|
||||
pointer-events:none;
|
||||
}
|
||||
.sim-button-nut:hover {
|
||||
stroke:1px solid #704A4A;
|
||||
}
|
||||
.sim-pin:hover {
|
||||
stroke:#D4AF37;
|
||||
stroke-width:2px;
|
||||
}
|
||||
|
||||
.sim-pin-touch.touched:hover {
|
||||
stroke:darkorange;
|
||||
}
|
||||
|
||||
.sim-led-back:hover {
|
||||
stroke:#a0a0a0;
|
||||
stroke-width:3px;
|
||||
}
|
||||
.sim-led:hover {
|
||||
stroke:#ff7f7f;
|
||||
stroke-width:3px;
|
||||
}
|
||||
|
||||
.sim-systemled {
|
||||
fill:#333;
|
||||
stroke:#555;
|
||||
stroke-width: 1px;
|
||||
}
|
||||
|
||||
.sim-light-level-button {
|
||||
stroke:#fff;
|
||||
stroke-width: 3px;
|
||||
}
|
||||
|
||||
.sim-antenna {
|
||||
stroke:#555;
|
||||
stroke-width: 2px;
|
||||
}
|
||||
|
||||
.sim-text {
|
||||
font-family:"Lucida Console", Monaco, monospace;
|
||||
font-size:25px;
|
||||
fill:#fff;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.sim-text-pin {
|
||||
font-family:"Lucida Console", Monaco, monospace;
|
||||
font-size:20px;
|
||||
fill:#fff;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.sim-thermometer {
|
||||
stroke:#aaa;
|
||||
stroke-width: 3px;
|
||||
}
|
||||
|
||||
/* animations */
|
||||
.sim-theme-glow {
|
||||
animation-name: sim-theme-glow-animation;
|
||||
animation-timing-function: ease-in-out;
|
||||
animation-direction: alternate;
|
||||
animation-iteration-count: infinite;
|
||||
animation-duration: 1.25s;
|
||||
}
|
||||
@keyframes sim-theme-glow-animation {
|
||||
from { opacity: 1; }
|
||||
to { opacity: 0.75; }
|
||||
}
|
||||
|
||||
.sim-flash {
|
||||
animation-name: sim-flash-animation;
|
||||
animation-duration: 0.1s;
|
||||
}
|
||||
|
||||
@keyframes sim-flash-animation {
|
||||
from { fill: yellow; }
|
||||
to { fill: default; }
|
||||
}
|
||||
|
||||
.sim-flash-stroke {
|
||||
animation-name: sim-flash-stroke-animation;
|
||||
animation-duration: 0.4s;
|
||||
animation-timing-function: ease-in;
|
||||
}
|
||||
|
||||
@keyframes sim-flash-stroke-animation {
|
||||
from { stroke: yellow; }
|
||||
to { stroke: default; }
|
||||
}
|
||||
|
||||
`;
|
||||
|
||||
this.style.textContent = MB_STYLE;
|
||||
|
||||
this.defs = <SVGDefsElement>svg.child(this.element, "defs", {});
|
||||
this.g = svg.elt("g");
|
||||
this.g = <SVGGElement>svg.elt("g");
|
||||
this.element.appendChild(this.g);
|
||||
|
||||
// filters
|
||||
@ -517,7 +625,7 @@ svg.sim.grayscale {
|
||||
}
|
||||
|
||||
// head
|
||||
this.head = <SVGGElement>svg.child(this.g, "g", {});
|
||||
this.head = <SVGGElement>svg.child(this.g, "g", {class: "sim-head"});
|
||||
svg.child(this.head, "circle", { cx: 258, cy: 75, r: 100, fill: "transparent" })
|
||||
this.logos.push(svg.path(this.head, "sim-theme sim-theme-glow", "M269.9,50.2L269.9,50.2l-39.5,0v0c-14.1,0.1-24.6,10.7-24.6,24.8c0,13.9,10.4,24.4,24.3,24.7v0h39.6c14.2,0,24.8-10.6,24.8-24.7C294.5,61,284,50.3,269.9,50.2 M269.7,89.2L269.7,89.2l-39.3,0c-7.7-0.1-14-6.4-14-14.2c0-7.8,6.4-14.2,14.2-14.2h39.1c7.8,0,14.2,6.4,14.2,14.2C283.9,82.9,277.5,89.2,269.7,89.2"));
|
||||
this.logos.push(svg.path(this.head, "sim-theme sim-theme-glow", "M230.6,69.7c-2.9,0-5.3,2.4-5.3,5.3c0,2.9,2.4,5.3,5.3,5.3c2.9,0,5.3-2.4,5.3-5.3C235.9,72.1,233.5,69.7,230.6,69.7"));
|
||||
@ -530,38 +638,19 @@ svg.sim.grayscale {
|
||||
"M16.5,341.2c0,0.4-0.1,0.9-0.1,1.3v60.7c4.1,1.7,8.6,2.7,12.9,2.7h34.4v-64.7h0.3c0,0,0-0.1,0-0.1c0-13-10.6-23.6-23.7-23.6C27.2,317.6,16.5,328.1,16.5,341.2z M21.2,341.6c0-10.7,8.7-19.3,19.3-19.3c10.7,0,19.3,8.7,19.3,19.3c0,10.7-8.6,19.3-19.3,19.3C29.9,360.9,21.2,352.2,21.2,341.6z",
|
||||
"M139.1,317.3c-12.8,0-22.1,10.3-23.1,23.1V406h46.2v-65.6C162.2,327.7,151.9,317.3,139.1,317.3zM139.3,360.1c-10.7,0-19.3-8.6-19.3-19.3c0-10.7,8.6-19.3,19.3-19.3c10.7,0,19.3,8.7,19.3,19.3C158.6,351.5,150,360.1,139.3,360.1z",
|
||||
"M249,317.3c-12.8,0-22.1,10.3-23.1,23.1V406h46.2v-65.6C272.1,327.7,261.8,317.3,249,317.3z M249.4,360.1c-10.7,0-19.3-8.6-19.3-19.3c0-10.7,8.6-19.3,19.3-19.3c10.7,0,19.3,8.7,19.3,19.3C268.7,351.5,260.1,360.1,249.4,360.1z"
|
||||
].map((p, pi) => svg.path(this.g, "sim-pin sim-pin-touch", p, `P${pi}, ANALOG IN`));
|
||||
].map((p, pi) => svg.path(this.g, "sim-pin sim-pin-touch", p));
|
||||
|
||||
// P3
|
||||
this.pins.push(svg.path(this.g, "sim-pin", "M0,357.7v19.2c0,10.8,6.2,20.2,14.4,25.2v-44.4H0z", "P3, ANALOG IN, LED Col 1"));
|
||||
this.pins.push(svg.path(this.g, "sim-pin", "M0,357.7v19.2c0,10.8,6.2,20.2,14.4,25.2v-44.4H0z"));
|
||||
|
||||
[66.7, 79.1, 91.4, 103.7, 164.3, 176.6, 188.9, 201.3, 213.6, 275.2, 287.5, 299.8, 312.1, 324.5, 385.1, 397.4, 409.7, 422].forEach(x => {
|
||||
pins4onXs.forEach(x => {
|
||||
this.pins.push(svg.child(this.g, "rect", { x: x, y: 356.7, width: 10, height: 50, class: "sim-pin" }));
|
||||
})
|
||||
svg.title(this.pins[4], "P4, ANALOG IN, LED Col 2")
|
||||
svg.title(this.pins[5], "P5, BUTTON A")
|
||||
svg.title(this.pins[6], "P6, LED Col 9")
|
||||
svg.title(this.pins[7], "P7, LED Col 8")
|
||||
svg.title(this.pins[8], "P8")
|
||||
svg.title(this.pins[9], "P9, LED Col 7")
|
||||
svg.title(this.pins[10], "P10, ANALOG IN, LED Col 3")
|
||||
svg.title(this.pins[11], "P11, BUTTON B")
|
||||
svg.title(this.pins[12], "P12, RESERVED ACCESSIBILITY")
|
||||
svg.title(this.pins[13], "P13, SPI - SCK")
|
||||
svg.title(this.pins[14], "P14, SPI - MISO")
|
||||
svg.title(this.pins[15], "P15, SPI - MOSI")
|
||||
svg.title(this.pins[16], "P16, SPI - Chip Select")
|
||||
svg.title(this.pins[17], "P17, +3v3")
|
||||
svg.title(this.pins[18], "P18, +3v3")
|
||||
svg.title(this.pins[19], "P19, I2C - SCL")
|
||||
svg.title(this.pins[20], "P20, I2C - SDA")
|
||||
svg.title(this.pins[21], "GND")
|
||||
|
||||
this.pins.push(svg.path(this.g, "sim-pin", "M483.6,402c8.2-5,14.4-14.4,14.4-25.1v-19.2h-14.4V402z", "GND"));
|
||||
|
||||
this.pins.push(svg.path(this.g, "sim-pin", "M359.9,317.3c-12.8,0-22.1,10.3-23.1,23.1V406H383v-65.6C383,327.7,372.7,317.3,359.9,317.3z M360,360.1c-10.7,0-19.3-8.6-19.3-19.3c0-10.7,8.6-19.3,19.3-19.3c10.7,0,19.3,8.7,19.3,19.3C379.3,351.5,370.7,360.1,360,360.1z", "+3v3"));
|
||||
this.pins.push(svg.path(this.g, "sim-pin", "M458,317.6c-13,0-23.6,10.6-23.6,23.6c0,0,0,0.1,0,0.1h0V406H469c4.3,0,8.4-1,12.6-2.7v-60.7c0-0.4,0-0.9,0-1.3C481.6,328.1,471,317.6,458,317.6z M457.8,360.9c-10.7,0-19.3-8.6-19.3-19.3c0-10.7,8.6-19.3,19.3-19.3c10.7,0,19.3,8.7,19.3,19.3C477.1,352.2,468.4,360.9,457.8,360.9z", "GND"));
|
||||
this.pins.push(svg.path(this.g, "sim-pin", "M483.6,402c8.2-5,14.4-14.4,14.4-25.1v-19.2h-14.4V402z"));
|
||||
this.pins.push(svg.path(this.g, "sim-pin", "M359.9,317.3c-12.8,0-22.1,10.3-23.1,23.1V406H383v-65.6C383,327.7,372.7,317.3,359.9,317.3z M360,360.1c-10.7,0-19.3-8.6-19.3-19.3c0-10.7,8.6-19.3,19.3-19.3c10.7,0,19.3,8.7,19.3,19.3C379.3,351.5,370.7,360.1,360,360.1z"));
|
||||
this.pins.push(svg.path(this.g, "sim-pin", "M458,317.6c-13,0-23.6,10.6-23.6,23.6c0,0,0,0.1,0,0.1h0V406H469c4.3,0,8.4-1,12.6-2.7v-60.7c0-0.4,0-0.9,0-1.3C481.6,328.1,471,317.6,458,317.6z M457.8,360.9c-10.7,0-19.3-8.6-19.3-19.3c0-10.7,8.6-19.3,19.3-19.3c10.7,0,19.3,8.7,19.3,19.3C477.1,352.2,468.4,360.9,457.8,360.9z"));
|
||||
|
||||
this.pins.forEach((p, i) => svg.hydrate(p, {title: pinTitles[i]}));
|
||||
|
||||
this.pinGradients = this.pins.map((pin, i) => {
|
||||
let gid = "gradient-pin-" + i
|
||||
@ -571,6 +660,7 @@ svg.sim.grayscale {
|
||||
})
|
||||
|
||||
this.pinTexts = [67, 165, 275].map(x => <SVGTextElement>svg.child(this.g, "text", { class: "sim-text-pin", x: x, y: 345 }));
|
||||
|
||||
this.buttonsOuter = []; this.buttons = [];
|
||||
|
||||
const outerBtn = (left: number, top: number) => {
|
||||
@ -578,7 +668,7 @@ svg.sim.grayscale {
|
||||
const btnw = 56.2;
|
||||
const btnn = 6;
|
||||
const btnnm = 10
|
||||
let btng = svg.child(this.g, "g");
|
||||
let btng = svg.child(this.g, "g", {class: "sim-button-group"});
|
||||
this.buttonsOuter.push(btng);
|
||||
svg.child(btng, "rect", { class: "sim-button-outer", x: left, y: top, rx: btnr, ry: btnr, width: btnw, height: btnw });
|
||||
svg.child(btng, "circle", { class: "sim-button-nut", cx: left + btnnm, cy: top + btnnm, r: btnn });
|
||||
@ -620,7 +710,7 @@ svg.sim.grayscale {
|
||||
let tiltDecayer = 0;
|
||||
this.element.addEventListener(pointerEvents.move, (ev: MouseEvent) => {
|
||||
let state = this.board;
|
||||
if (!state.accelerometer.isActive) return;
|
||||
if (!state.accelerometerState.accelerometer.isActive) return;
|
||||
|
||||
if (tiltDecayer) {
|
||||
clearInterval(tiltDecayer);
|
||||
@ -635,18 +725,18 @@ svg.sim.grayscale {
|
||||
let z2 = 1023 * 1023 - x * x - y * y;
|
||||
let z = Math.floor((z2 > 0 ? -1 : 1) * Math.sqrt(Math.abs(z2)));
|
||||
|
||||
state.accelerometer.update(x, y, z);
|
||||
state.accelerometerState.accelerometer.update(x, y, z);
|
||||
this.updateTilt();
|
||||
}, false);
|
||||
this.element.addEventListener(pointerEvents.leave, (ev: MouseEvent) => {
|
||||
let state = this.board;
|
||||
if (!state.accelerometer.isActive) return;
|
||||
if (!state.accelerometerState.accelerometer.isActive) return;
|
||||
|
||||
if (!tiltDecayer) {
|
||||
tiltDecayer = setInterval(() => {
|
||||
let accx = state.accelerometer.getX(MicroBitCoordinateSystem.RAW);
|
||||
let accx = state.accelerometerState.accelerometer.getX(MicroBitCoordinateSystem.RAW);
|
||||
accx = Math.floor(Math.abs(accx) * 0.85) * (accx > 0 ? 1 : -1);
|
||||
let accy = state.accelerometer.getY(MicroBitCoordinateSystem.RAW);
|
||||
let accy = state.accelerometerState.accelerometer.getY(MicroBitCoordinateSystem.RAW);
|
||||
accy = Math.floor(Math.abs(accy) * 0.85) * (accy > 0 ? 1 : -1);
|
||||
let accz = -Math.sqrt(Math.max(0, 1023 * 1023 - accx * accx - accy * accy));
|
||||
if (Math.abs(accx) <= 24 && Math.abs(accy) <= 24) {
|
||||
@ -656,20 +746,20 @@ svg.sim.grayscale {
|
||||
accy = 0;
|
||||
accz = -1023;
|
||||
}
|
||||
state.accelerometer.update(accx, accy, accz);
|
||||
state.accelerometerState.accelerometer.update(accx, accy, accz);
|
||||
this.updateTilt();
|
||||
}, 50)
|
||||
}
|
||||
}, false);
|
||||
|
||||
this.pins.forEach((pin, index) => {
|
||||
if (!this.board.pins[index]) return;
|
||||
if (!this.board.edgeConnectorState.pins[index]) return;
|
||||
let pt = this.element.createSVGPoint();
|
||||
svg.buttonEvents(pin,
|
||||
// move
|
||||
ev => {
|
||||
let state = this.board;
|
||||
let pin = state.pins[index];
|
||||
let pin = state.edgeConnectorState.pins[index];
|
||||
let svgpin = this.pins[index];
|
||||
if (pin.mode & PinFlags.Input) {
|
||||
let cursor = svg.cursorPoint(pt, this.element, ev);
|
||||
@ -681,7 +771,7 @@ svg.sim.grayscale {
|
||||
// start
|
||||
ev => {
|
||||
let state = this.board;
|
||||
let pin = state.pins[index];
|
||||
let pin = state.edgeConnectorState.pins[index];
|
||||
let svgpin = this.pins[index];
|
||||
svg.addClass(svgpin, "touched");
|
||||
if (pin.mode & PinFlags.Input) {
|
||||
@ -694,7 +784,7 @@ svg.sim.grayscale {
|
||||
// stop
|
||||
(ev: MouseEvent) => {
|
||||
let state = this.board;
|
||||
let pin = state.pins[index];
|
||||
let pin = state.edgeConnectorState.pins[index];
|
||||
let svgpin = this.pins[index];
|
||||
svg.removeClass(svgpin, "touched");
|
||||
this.updatePin(pin, index);
|
||||
@ -704,70 +794,73 @@ svg.sim.grayscale {
|
||||
this.pins.slice(0, 3).forEach((btn, index) => {
|
||||
btn.addEventListener(pointerEvents.down, ev => {
|
||||
let state = this.board;
|
||||
state.pins[index].touched = true;
|
||||
this.updatePin(state.pins[index], index);
|
||||
state.edgeConnectorState.pins[index].touched = true;
|
||||
this.updatePin(state.edgeConnectorState.pins[index], index);
|
||||
})
|
||||
btn.addEventListener(pointerEvents.leave, ev => {
|
||||
let state = this.board;
|
||||
state.pins[index].touched = false;
|
||||
this.updatePin(state.pins[index], index);
|
||||
state.edgeConnectorState.pins[index].touched = false;
|
||||
this.updatePin(state.edgeConnectorState.pins[index], index);
|
||||
})
|
||||
btn.addEventListener(pointerEvents.up, ev => {
|
||||
let state = this.board;
|
||||
state.pins[index].touched = false;
|
||||
this.updatePin(state.pins[index], index);
|
||||
this.board.bus.queue(state.pins[index].id, DAL.MICROBIT_BUTTON_EVT_UP);
|
||||
this.board.bus.queue(state.pins[index].id, DAL.MICROBIT_BUTTON_EVT_CLICK);
|
||||
state.edgeConnectorState.pins[index].touched = false;
|
||||
this.updatePin(state.edgeConnectorState.pins[index], index);
|
||||
this.board.bus.queue(state.edgeConnectorState.pins[index].id, DAL.MICROBIT_BUTTON_EVT_UP);
|
||||
this.board.bus.queue(state.edgeConnectorState.pins[index].id, DAL.MICROBIT_BUTTON_EVT_CLICK);
|
||||
})
|
||||
})
|
||||
|
||||
let bpState = this.board.buttonPairState;
|
||||
let stateButtons = [bpState.aBtn, bpState.bBtn, bpState.abBtn];
|
||||
this.buttonsOuter.slice(0, 2).forEach((btn, index) => {
|
||||
btn.addEventListener(pointerEvents.down, ev => {
|
||||
let state = this.board;
|
||||
state.buttons[index].pressed = true;
|
||||
stateButtons[index].pressed = true;
|
||||
svg.fill(this.buttons[index], this.props.theme.buttonDown);
|
||||
})
|
||||
btn.addEventListener(pointerEvents.leave, ev => {
|
||||
let state = this.board;
|
||||
state.buttons[index].pressed = false;
|
||||
stateButtons[index].pressed = false;
|
||||
svg.fill(this.buttons[index], this.props.theme.buttonUp);
|
||||
})
|
||||
btn.addEventListener(pointerEvents.up, ev => {
|
||||
let state = this.board;
|
||||
state.buttons[index].pressed = false;
|
||||
stateButtons[index].pressed = false;
|
||||
svg.fill(this.buttons[index], this.props.theme.buttonUp);
|
||||
this.board.bus.queue(state.buttons[index].id, DAL.MICROBIT_BUTTON_EVT_UP);
|
||||
this.board.bus.queue(state.buttons[index].id, DAL.MICROBIT_BUTTON_EVT_CLICK);
|
||||
this.board.bus.queue(stateButtons[index].id, DAL.MICROBIT_BUTTON_EVT_UP);
|
||||
this.board.bus.queue(stateButtons[index].id, DAL.MICROBIT_BUTTON_EVT_CLICK);
|
||||
})
|
||||
})
|
||||
this.buttonsOuter[2].addEventListener(pointerEvents.down, ev => {
|
||||
let state = this.board;
|
||||
state.buttons[0].pressed = true;
|
||||
state.buttons[1].pressed = true;
|
||||
state.buttons[2].pressed = true;
|
||||
stateButtons[0].pressed = true;
|
||||
stateButtons[1].pressed = true;
|
||||
stateButtons[2].pressed = true;
|
||||
svg.fill(this.buttons[0], this.props.theme.buttonDown);
|
||||
svg.fill(this.buttons[1], this.props.theme.buttonDown);
|
||||
svg.fill(this.buttons[2], this.props.theme.buttonDown);
|
||||
})
|
||||
this.buttonsOuter[2].addEventListener(pointerEvents.leave, ev => {
|
||||
let state = this.board;
|
||||
state.buttons[0].pressed = false;
|
||||
state.buttons[1].pressed = false;
|
||||
state.buttons[2].pressed = false;
|
||||
stateButtons[0].pressed = false;
|
||||
stateButtons[1].pressed = false;
|
||||
stateButtons[2].pressed = false;
|
||||
svg.fill(this.buttons[0], this.props.theme.buttonUp);
|
||||
svg.fill(this.buttons[1], this.props.theme.buttonUp);
|
||||
svg.fill(this.buttons[2], this.props.theme.virtualButtonUp);
|
||||
})
|
||||
this.buttonsOuter[2].addEventListener(pointerEvents.up, ev => {
|
||||
let state = this.board;
|
||||
state.buttons[0].pressed = false;
|
||||
state.buttons[1].pressed = false;
|
||||
state.buttons[2].pressed = false;
|
||||
stateButtons[0].pressed = false;
|
||||
stateButtons[1].pressed = false;
|
||||
stateButtons[2].pressed = false;
|
||||
svg.fill(this.buttons[0], this.props.theme.buttonUp);
|
||||
svg.fill(this.buttons[1], this.props.theme.buttonUp);
|
||||
svg.fill(this.buttons[2], this.props.theme.virtualButtonUp);
|
||||
|
||||
this.board.bus.queue(state.buttons[2].id, DAL.MICROBIT_BUTTON_EVT_UP);
|
||||
this.board.bus.queue(state.buttons[2].id, DAL.MICROBIT_BUTTON_EVT_CLICK);
|
||||
this.board.bus.queue(stateButtons[2].id, DAL.MICROBIT_BUTTON_EVT_UP);
|
||||
this.board.bus.queue(stateButtons[2].id, DAL.MICROBIT_BUTTON_EVT_CLICK);
|
||||
})
|
||||
}
|
||||
}
|
203
sim/public/instructions.html
Normal file
@ -0,0 +1,203 @@
|
||||
<!doctype html>
|
||||
<html lang="en" data-framework="typescript">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Assembly Instructions</title>
|
||||
<style>
|
||||
|
||||
svg {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.blocklyText, .ace_editor {
|
||||
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', monospace !important;
|
||||
}
|
||||
|
||||
.blocklyText, .ace_editor {
|
||||
font-size: 1rem !important;
|
||||
}
|
||||
|
||||
.blocklyTreeLabel {
|
||||
font-size: 1.25rem !important;
|
||||
}
|
||||
|
||||
.blocklyCheckbox {
|
||||
fill: #ff3030 !important;
|
||||
text-shadow: 0px 0px 6px #f00;
|
||||
font-size: 17pt !important;
|
||||
}
|
||||
|
||||
.ui.card .blocklyPreview {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: calc(100% - 1em);
|
||||
max-height: calc(100% - 1em);
|
||||
}
|
||||
|
||||
code {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
code.lang-config, code.lang-package { display:none; }
|
||||
|
||||
code.lang-blocks::before,
|
||||
code.lang-sig::before,
|
||||
code.lang-block::before,
|
||||
code.lang-shuffle::before,
|
||||
code.lang-sim::before,
|
||||
code.lang-cards::before,
|
||||
code.lang-namespaces::before,
|
||||
code.lang-codecard::before {
|
||||
content: "...";
|
||||
position: absolute;
|
||||
top: calc(50% - 0.5em);
|
||||
left: calc(50% - 5em);
|
||||
}
|
||||
|
||||
code.lang-blocks,
|
||||
code.lang-sig,
|
||||
code.lang-block,
|
||||
code.lang-shuffle,
|
||||
code.lang-sim,
|
||||
code.lang-cards,
|
||||
code.lang-namespaces,
|
||||
code.lang-codecard {
|
||||
color: transparent;
|
||||
}
|
||||
</style>
|
||||
<style type="text/css">
|
||||
@import "/cdn/semantic.css";
|
||||
@import "/cdn/icons.css";
|
||||
</style>
|
||||
<style>
|
||||
html {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
font-family: "Lucida Console", Monaco, monospace;
|
||||
}
|
||||
|
||||
div {
|
||||
/*undo semantic UI*/
|
||||
box-sizing: content-box;
|
||||
line-height: normal;
|
||||
}
|
||||
|
||||
img {
|
||||
border: 0;
|
||||
}
|
||||
/*TODO: Share CSS with main webpage*/
|
||||
|
||||
.organization {
|
||||
position: absolute;
|
||||
bottom: 1rem;
|
||||
right: 1rem;
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 2em;
|
||||
font-weight: normal;
|
||||
color: rgba(0, 0, 0, 0.87);
|
||||
font-family: 'Segoe UI', 'Helvetica Neue', Arial, Helvetica, sans-serif;
|
||||
display: block;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#front-panel .board-svg {
|
||||
position: absolute;
|
||||
left: 1rem;
|
||||
width: 140px;
|
||||
top: 8rem;
|
||||
}
|
||||
|
||||
#proj-title {
|
||||
top: 20px;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#proj-code {
|
||||
width: 140px;
|
||||
height: 200px;
|
||||
position: absolute;
|
||||
right: 1rem;
|
||||
top: 8rem;
|
||||
}
|
||||
#proj-code-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
font-size: 4px;
|
||||
overflow: hidden;
|
||||
display: none;
|
||||
}
|
||||
#proj-code-spinner {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.back-panel svg {
|
||||
position: relative;
|
||||
margin: 0 auto;
|
||||
left: inherit;
|
||||
bottom: -7px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id='loading' class="ui active inverted dimmer">
|
||||
<div class="ui large loader"></div>
|
||||
</div>
|
||||
<!-- start Mixpanel --><script type="text/javascript">(function(e,b){if(!b.__SV){var a,f,i,g;window.mixpanel=b;b._i=[];b.init=function(a,e,d){function f(b,h){var a=h.split(".");2==a.length&&(b=b[a[0]],h=a[1]);b[h]=function(){b.push([h].concat(Array.prototype.slice.call(arguments,0)))}}var c=b;"undefined"!==typeof d?c=b[d]=[]:d="mixpanel";c.people=c.people||[];c.toString=function(b){var a="mixpanel";"mixpanel"!==d&&(a+="."+d);b||(a+=" (stub)");return a};c.people.toString=function(){return c.toString(1)+".people (stub)"};i="disable time_event track track_pageview track_links track_forms register register_once alias unregister identify name_tag set_config reset people.set people.set_once people.increment people.append people.union people.track_charge people.clear_charges people.delete_user".split(" ");
|
||||
for(g=0;g<i.length;g++)f(c,i[g]);b._i.push([a,e,d])};b.__SV=1.2;a=e.createElement("script");a.type="text/javascript";a.async=!0;a.src="undefined"!==typeof MIXPANEL_CUSTOM_LIB_URL?MIXPANEL_CUSTOM_LIB_URL:"file:"===e.location.protocol&&"//cdn.mxpnl.com/libs/mixpanel-2-latest.min.js".match(/^\/\//)?"https://cdn.mxpnl.com/libs/mixpanel-2-latest.min.js":"//cdn.mxpnl.com/libs/mixpanel-2-latest.min.js";f=e.getElementsByTagName("script")[0];f.parentNode.insertBefore(a,f)}})(document,window.mixpanel||[]);
|
||||
mixpanel.init("762fef19c053a0ea4cec43d2fecae76e");</script><!-- end Mixpanel -->
|
||||
<script>
|
||||
// This line gets patched up by the cloud
|
||||
var pxtConfig = null;
|
||||
</script>
|
||||
<script type="text/javascript" src="/cdn/lzma/lzma_worker-min.js"></script>
|
||||
<script type="text/javascript" src="/cdn/marked/marked.min.js"></script>
|
||||
<script type="text/javascript" src="/cdn/jquery.js"></script>
|
||||
<script type="text/javascript" src="/cdn/typescript.js"></script>
|
||||
<script type="text/javascript" src="/cdn/blockly/blockly_compressed.js"></script>
|
||||
<script type="text/javascript" src="/cdn/blockly/blocks_compressed.js"></script>
|
||||
<script type="text/javascript" src="/cdn/blockly/msg/js/en.js"></script>
|
||||
<script type="text/javascript" src="/cdn/pxtlib.js"></script>
|
||||
<script type="text/javascript" src="/cdn/pxtblocks.js"></script>
|
||||
<script type="text/javascript" src="/cdn/pxtsim.js"></script>
|
||||
<script type="text/javascript" src="/cdn/pxtrunner.js"></script>
|
||||
<script type="text/javascript" src="/cdn/semantic.js"></script>
|
||||
<script type="text/javascript" src="/embed.js"></script>
|
||||
<script type="text/javascript" src="/sim/sim.js"></script>
|
||||
<script type="text/javascript">
|
||||
(function () {
|
||||
ksRunnerReady(function() {
|
||||
var orgLogo = pxt.appTarget.appTheme.organizationLogo;
|
||||
if (orgLogo)
|
||||
$('#front-panel').append(
|
||||
$('<img/>').attr('class', 'organization').attr('src', orgLogo)
|
||||
);
|
||||
var loading = document.getElementById('loading');
|
||||
pxsim.instructions.drawInstructions();
|
||||
$(loading).hide();
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
|
||||
<div id="front-panel" class="instr-panel">
|
||||
<h1 id="proj-title"></h1>
|
||||
|
||||
<!--TODO: extract real code snapshot from PXT -->
|
||||
<div id="proj-code">
|
||||
<i id="proj-code-spinner" class="spinner loading icon"></i>
|
||||
<div id="proj-code-container">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
240
sim/simlib.ts
Normal file
@ -0,0 +1,240 @@
|
||||
/// <reference path="../node_modules/pxt-core/typings/bluebird/bluebird.d.ts"/>
|
||||
/// <reference path="../node_modules/pxt-core/built/pxtsim.d.ts"/>
|
||||
/// <reference path="../libs/microbit/dal.d.ts"/>
|
||||
|
||||
namespace pxsim {
|
||||
export type BBRowCol = [/*row*/string, /*column*/string];
|
||||
export type BoardPin = string;
|
||||
export interface BBLoc {type: "breadboard", rowCol: BBRowCol};
|
||||
export interface BoardLoc {type: "dalboard", pin: BoardPin};
|
||||
export type Loc = BBLoc | BoardLoc;
|
||||
|
||||
export function initRuntimeWithDalBoard() {
|
||||
U.assert(!runtime.board);
|
||||
let b = new DalBoard();
|
||||
runtime.board = b;
|
||||
}
|
||||
if (!pxsim.initCurrentRuntime) {
|
||||
pxsim.initCurrentRuntime = initRuntimeWithDalBoard;
|
||||
}
|
||||
|
||||
export function board() {
|
||||
return runtime.board as DalBoard;
|
||||
}
|
||||
|
||||
export function mkRange(a: number, b: number): number[] {
|
||||
let res: number[] = [];
|
||||
for (; a < b; a++)
|
||||
res.push(a);
|
||||
return res;
|
||||
}
|
||||
|
||||
export function parseQueryString(): (key: string) => string {
|
||||
let qs = window.location.search.substring(1);
|
||||
let getQsVal = (key: string) => decodeURIComponent((qs.split(`${key}=`)[1] || "").split("&")[0] || "").replace(/\+/g, " ");
|
||||
return getQsVal;
|
||||
}
|
||||
}
|
||||
|
||||
namespace pxsim.visuals {
|
||||
export interface IPointerEvents {
|
||||
up: string,
|
||||
down: string,
|
||||
move: string,
|
||||
leave: string
|
||||
}
|
||||
|
||||
export const pointerEvents: IPointerEvents = !!(window as any).PointerEvent ? {
|
||||
up: "pointerup",
|
||||
down: "pointerdown",
|
||||
move: "pointermove",
|
||||
leave: "pointerleave"
|
||||
} : {
|
||||
up: "mouseup",
|
||||
down: "mousedown",
|
||||
move: "mousemove",
|
||||
leave: "mouseleave"
|
||||
};
|
||||
|
||||
export function translateEl(el: SVGElement, xy: [number, number]) {
|
||||
//TODO append translation instead of replacing the full transform
|
||||
svg.hydrate(el, {transform: `translate(${xy[0]} ${xy[1]})`});
|
||||
}
|
||||
|
||||
export interface ComposeOpts {
|
||||
el1: SVGAndSize<SVGSVGElement>,
|
||||
scaleUnit1: number,
|
||||
el2: SVGAndSize<SVGSVGElement>,
|
||||
scaleUnit2: number,
|
||||
margin: [number, number, number, number],
|
||||
middleMargin: number,
|
||||
maxWidth?: string,
|
||||
maxHeight?: string,
|
||||
}
|
||||
export interface ComposeResult {
|
||||
host: SVGSVGElement,
|
||||
scaleUnit: number,
|
||||
under: SVGGElement,
|
||||
over: SVGGElement,
|
||||
edges: number[],
|
||||
toHostCoord1: (xy: Coord) => Coord,
|
||||
toHostCoord2: (xy: Coord) => Coord,
|
||||
}
|
||||
export function composeSVG(opts: ComposeOpts): ComposeResult {
|
||||
let [a, b] = [opts.el1, opts.el2];
|
||||
U.assert(a.x == 0 && a.y == 0 && b.x == 0 && b.y == 0, "el1 and el2 x,y offsets not supported");
|
||||
let setXY = (e: SVGSVGElement, x: number, y: number) => svg.hydrate(e, {x: x, y: y});
|
||||
let setWH = (e: SVGSVGElement, w: string, h: string) => {
|
||||
if (w)
|
||||
svg.hydrate(e, {width: w});
|
||||
if (h)
|
||||
svg.hydrate(e, {height: h});
|
||||
}
|
||||
let setWHpx = (e: SVGSVGElement, w: number, h: number) => svg.hydrate(e, {width: `${w}px`, height: `${h}px`});
|
||||
let scaleUnit = opts.scaleUnit2;
|
||||
let aScalar = opts.scaleUnit2 / opts.scaleUnit1;
|
||||
let bScalar = 1.0;
|
||||
let aw = a.w * aScalar;
|
||||
let ah = a.h * aScalar;
|
||||
setWHpx(a.el, aw, ah);
|
||||
let bw = b.w * bScalar;
|
||||
let bh = b.h * bScalar;
|
||||
setWHpx(b.el, bw, bh);
|
||||
let [mt, mr, mb, ml] = opts.margin;
|
||||
let mm = opts.middleMargin;
|
||||
let innerW = Math.max(aw, bw);
|
||||
let ax = mr + (innerW - aw) / 2.0;
|
||||
let ay = mt;
|
||||
setXY(a.el, ax, ay);
|
||||
let bx = mr + (innerW - bw) / 2.0;
|
||||
let by = ay + ah + mm;
|
||||
setXY(b.el, bx, by);
|
||||
let edges = [ay, ay + ah, by, by + bh];
|
||||
let w = mr + innerW + ml;
|
||||
let h = mt + ah + mm + bh + mb;
|
||||
let host = <SVGSVGElement>svg.elt("svg", {
|
||||
"version": "1.0",
|
||||
"viewBox": `0 0 ${w} ${h}`,
|
||||
"class": `sim-bb`,
|
||||
});
|
||||
setWH(host, opts.maxWidth, opts.maxHeight);
|
||||
setXY(host, 0, 0);
|
||||
let under = <SVGGElement>svg.child(host, "g");
|
||||
host.appendChild(a.el);
|
||||
host.appendChild(b.el);
|
||||
let over = <SVGGElement>svg.child(host, "g");
|
||||
let toHostCoord1 = (xy: Coord): Coord => {
|
||||
let [x, y] = xy;
|
||||
return [x * aScalar + ax, y * aScalar + ay];
|
||||
};
|
||||
let toHostCoord2 = (xy: Coord): Coord => {
|
||||
let [x, y] = xy;
|
||||
return [x * bScalar + bx, y * bScalar + by];
|
||||
};
|
||||
return {
|
||||
under: under,
|
||||
over: over,
|
||||
host: host,
|
||||
edges: edges,
|
||||
scaleUnit: scaleUnit,
|
||||
toHostCoord1: toHostCoord1,
|
||||
toHostCoord2: toHostCoord2,
|
||||
};
|
||||
}
|
||||
|
||||
export function mkScaleFn(originUnit: number, targetUnit: number): (n: number) => number {
|
||||
return (n: number) => n * (targetUnit / originUnit);
|
||||
}
|
||||
export interface MkImageOpts {
|
||||
image: string,
|
||||
width: number,
|
||||
height: number,
|
||||
imageUnitDist: number,
|
||||
targetUnitDist: number
|
||||
}
|
||||
export function mkImageSVG(opts: MkImageOpts): SVGAndSize<SVGImageElement> {
|
||||
let scaleFn = mkScaleFn(opts.imageUnitDist, opts.targetUnitDist);
|
||||
let w = scaleFn(opts.width);
|
||||
let h = scaleFn(opts.height);
|
||||
let img = <SVGImageElement>svg.elt("image", {
|
||||
width: w,
|
||||
height: h,
|
||||
"href": `${opts.image}`
|
||||
});
|
||||
return {el: img, w: w, h: h, x: 0, y: 0};
|
||||
}
|
||||
|
||||
export type Coord = [number, number];
|
||||
export function findDistSqrd(a: Coord, b: Coord): number {
|
||||
let x = a[0] - b[0];
|
||||
let y = a[1] - b[1];
|
||||
return x * x + y * y;
|
||||
}
|
||||
export function findClosestCoordIdx(a: Coord, bs: Coord[]): number {
|
||||
let dists = bs.map(b => findDistSqrd(a, b));
|
||||
let minIdx = dists.reduce((prevIdx, currDist, currIdx, arr) => {
|
||||
return currDist < arr[prevIdx] ? currIdx : prevIdx;
|
||||
}, 0);
|
||||
return minIdx;
|
||||
}
|
||||
|
||||
export interface IBoardComponent<T> {
|
||||
style: string,
|
||||
element: SVGElement,
|
||||
defs: SVGElement[],
|
||||
init(bus: EventBus, state: T, svgEl: SVGSVGElement, gpioPins: string[], otherArgs: string[]): void, //NOTE: constructors not supported in interfaces
|
||||
moveToCoord(xy: Coord): void,
|
||||
updateState(): void,
|
||||
updateTheme(): void,
|
||||
}
|
||||
|
||||
export function mkTxt(cx: number, cy: number, size: number, rot: number, txt: string, txtXOffFactor?: number, txtYOffFactor?: number): SVGTextElement {
|
||||
let el = <SVGTextElement>svg.elt("text")
|
||||
//HACK: these constants (txtXOffFactor, txtYOffFactor) tweak the way this algorithm knows how to center the text
|
||||
txtXOffFactor = txtXOffFactor || -0.33333;
|
||||
txtYOffFactor = txtYOffFactor || 0.3;
|
||||
const xOff = txtXOffFactor * size * txt.length;
|
||||
const yOff = txtYOffFactor * size;
|
||||
svg.hydrate(el, {style: `font-size:${size}px;`,
|
||||
transform: `translate(${cx} ${cy}) rotate(${rot}) translate(${xOff} ${yOff})` });
|
||||
svg.addClass(el, "noselect");
|
||||
el.textContent = txt;
|
||||
return el;
|
||||
}
|
||||
|
||||
export type WireColor =
|
||||
"black" | "white" | "gray" | "purple" | "blue" | "green" | "yellow" | "orange" | "red" | "brown";
|
||||
export const WIRE_COLOR_MAP: Map<string> = {
|
||||
black: "#514f4d",
|
||||
white: "#fcfdfc",
|
||||
gray: "#acabab",
|
||||
purple: "#a772a1",
|
||||
blue: "#01a6e8",
|
||||
green: "#3cce73",
|
||||
yellow: "#ece600",
|
||||
orange: "#fdb262",
|
||||
red: "#f44f43",
|
||||
brown: "#c89764",
|
||||
}
|
||||
export function mapWireColor(clr: WireColor | string): string {
|
||||
return WIRE_COLOR_MAP[clr] || clr;
|
||||
}
|
||||
|
||||
export interface SVGAndSize<T extends SVGElement> {
|
||||
el: T,
|
||||
y: number,
|
||||
x: number,
|
||||
w: number,
|
||||
h: number
|
||||
};
|
||||
export type SVGElAndSize = SVGAndSize<SVGElement>;
|
||||
|
||||
export const PIN_DIST = 15;
|
||||
|
||||
export interface BoardView {
|
||||
getView(): SVGAndSize<SVGSVGElement>;
|
||||
getCoord(pinNm: string): Coord;
|
||||
getPinDist(): number;
|
||||
highlightPin(pinNm: string): void;
|
||||
}
|
||||
}
|
711
sim/state.ts
@ -1,711 +0,0 @@
|
||||
namespace pxsim {
|
||||
export interface RuntimeOptions {
|
||||
theme: string;
|
||||
}
|
||||
|
||||
export enum DisplayMode {
|
||||
bw,
|
||||
greyscale
|
||||
}
|
||||
|
||||
export enum PinFlags {
|
||||
Unused = 0,
|
||||
Digital = 0x0001,
|
||||
Analog = 0x0002,
|
||||
Input = 0x0004,
|
||||
Output = 0x0008,
|
||||
Touch = 0x0010
|
||||
}
|
||||
|
||||
export class Pin {
|
||||
constructor(public id: number) { }
|
||||
touched = false;
|
||||
value = 0;
|
||||
period = 0;
|
||||
mode = PinFlags.Unused;
|
||||
pitch = false;
|
||||
pull = 0; // PullDown
|
||||
|
||||
isTouched(): boolean {
|
||||
this.mode = PinFlags.Touch;
|
||||
return this.touched;
|
||||
}
|
||||
}
|
||||
|
||||
export class Button {
|
||||
constructor(public id: number) { }
|
||||
pressed: boolean;
|
||||
}
|
||||
|
||||
export class EventBus {
|
||||
private queues: Map<EventQueue<number>> = {};
|
||||
|
||||
constructor(private runtime: Runtime) { }
|
||||
|
||||
listen(id: number, evid: number, handler: RefAction) {
|
||||
let k = id + ":" + evid;
|
||||
let queue = this.queues[k];
|
||||
if (!queue) queue = this.queues[k] = new EventQueue<number>(this.runtime);
|
||||
queue.handler = handler;
|
||||
}
|
||||
|
||||
queue(id: number, evid: number, value: number = 0) {
|
||||
let k = id + ":" + evid;
|
||||
let queue = this.queues[k];
|
||||
if (queue) queue.push(value);
|
||||
}
|
||||
}
|
||||
|
||||
export interface PacketBuffer {
|
||||
data: number[] | string;
|
||||
rssi?: number;
|
||||
}
|
||||
|
||||
export class RadioDatagram {
|
||||
datagram: PacketBuffer[] = [];
|
||||
lastReceived: PacketBuffer = {
|
||||
data: [0, 0, 0, 0],
|
||||
rssi: -1
|
||||
};
|
||||
|
||||
constructor(private runtime: Runtime) {
|
||||
}
|
||||
|
||||
queue(packet: PacketBuffer) {
|
||||
if (this.datagram.length < 4)
|
||||
this.datagram.push(packet);
|
||||
(<Board>runtime.board).bus.queue(DAL.MICROBIT_ID_RADIO, DAL.MICROBIT_RADIO_EVT_DATAGRAM);
|
||||
}
|
||||
|
||||
send(buffer: number[] | string) {
|
||||
if (buffer instanceof String) buffer = buffer.slice(0, 32);
|
||||
else buffer = buffer.slice(0, 8);
|
||||
|
||||
Runtime.postMessage(<SimulatorRadioPacketMessage>{
|
||||
type: "radiopacket",
|
||||
data: buffer
|
||||
})
|
||||
}
|
||||
|
||||
recv(): PacketBuffer {
|
||||
let r = this.datagram.shift();
|
||||
if (!r) r = {
|
||||
data: [0, 0, 0, 0],
|
||||
rssi: -1
|
||||
};
|
||||
return this.lastReceived = r;
|
||||
}
|
||||
}
|
||||
|
||||
export class RadioBus {
|
||||
// uint8_t radioDefaultGroup = MICROBIT_RADIO_DEFAULT_GROUP;
|
||||
groupId = 0; // todo
|
||||
power = 0;
|
||||
transmitSerialNumber = false;
|
||||
datagram: RadioDatagram;
|
||||
|
||||
constructor(private runtime: Runtime) {
|
||||
this.datagram = new RadioDatagram(runtime);
|
||||
}
|
||||
|
||||
setGroup(id: number) {
|
||||
this.groupId = id & 0xff; // byte only
|
||||
}
|
||||
|
||||
setTransmitPower(power: number) {
|
||||
this.power = Math.max(0, Math.min(7, power));
|
||||
}
|
||||
|
||||
setTransmitSerialNumber(sn: boolean) {
|
||||
this.transmitSerialNumber = !!sn;
|
||||
}
|
||||
|
||||
broadcast(msg: number) {
|
||||
Runtime.postMessage(<SimulatorEventBusMessage>{
|
||||
type: "eventbus",
|
||||
id: DAL.MES_BROADCAST_GENERAL_ID,
|
||||
eventid: msg,
|
||||
power: this.power,
|
||||
group: this.groupId
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
interface AccelerometerSample {
|
||||
x: number;
|
||||
y: number;
|
||||
z: number;
|
||||
}
|
||||
|
||||
interface ShakeHistory {
|
||||
x: boolean;
|
||||
y: boolean;
|
||||
z: boolean;
|
||||
count: number;
|
||||
shaken: number;
|
||||
timer: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Co-ordinate systems that can be used.
|
||||
* RAW: Unaltered data. Data will be returned directly from the accelerometer.
|
||||
*
|
||||
* SIMPLE_CARTESIAN: Data will be returned based on an easy to understand alignment, consistent with the cartesian system taught in schools.
|
||||
* When held upright, facing the user:
|
||||
*
|
||||
* /
|
||||
* +--------------------+ z
|
||||
* | |
|
||||
* | ..... |
|
||||
* | * ..... * |
|
||||
* ^ | ..... |
|
||||
* | | |
|
||||
* y +--------------------+ x-->
|
||||
*
|
||||
*
|
||||
* NORTH_EAST_DOWN: Data will be returned based on the industry convention of the North East Down (NED) system.
|
||||
* When held upright, facing the user:
|
||||
*
|
||||
* z
|
||||
* +--------------------+ /
|
||||
* | |
|
||||
* | ..... |
|
||||
* | * ..... * |
|
||||
* ^ | ..... |
|
||||
* | | |
|
||||
* x +--------------------+ y-->
|
||||
*
|
||||
*/
|
||||
export enum MicroBitCoordinateSystem {
|
||||
RAW,
|
||||
SIMPLE_CARTESIAN,
|
||||
NORTH_EAST_DOWN
|
||||
}
|
||||
|
||||
export class Accelerometer {
|
||||
private sigma: number = 0; // the number of ticks that the instantaneous gesture has been stable.
|
||||
private lastGesture: number = 0; // the last, stable gesture recorded.
|
||||
private currentGesture: number = 0 // the instantaneous, unfiltered gesture detected.
|
||||
private sample: AccelerometerSample = { x: 0, y: 0, z: -1023 }
|
||||
private shake: ShakeHistory = { x: false, y: false, z: false, count: 0, shaken: 0, timer: 0 }; // State information needed to detect shake events.
|
||||
private pitch: number;
|
||||
private roll: number;
|
||||
private id: number;
|
||||
public isActive = false;
|
||||
public sampleRange = 2;
|
||||
|
||||
constructor(public runtime: Runtime) {
|
||||
this.id = DAL.MICROBIT_ID_ACCELEROMETER;
|
||||
}
|
||||
|
||||
public setSampleRange(range: number) {
|
||||
this.activate();
|
||||
this.sampleRange = Math.max(1, Math.min(8, range));
|
||||
}
|
||||
|
||||
public activate() {
|
||||
if (!this.isActive) {
|
||||
this.isActive = true;
|
||||
this.runtime.queueDisplayUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the acceleration data from the accelerometer, and stores it in our buffer.
|
||||
* This is called by the tick() member function, if the interrupt is set!
|
||||
*/
|
||||
public update(x: number, y: number, z: number) {
|
||||
// read MSB values...
|
||||
this.sample.x = Math.floor(x);
|
||||
this.sample.y = Math.floor(y);
|
||||
this.sample.z = Math.floor(z);
|
||||
|
||||
// Update gesture tracking
|
||||
this.updateGesture();
|
||||
|
||||
// Indicate that a new sample is available
|
||||
board().bus.queue(this.id, DAL.MICROBIT_ACCELEROMETER_EVT_DATA_UPDATE)
|
||||
}
|
||||
|
||||
public instantaneousAccelerationSquared() {
|
||||
// Use pythagoras theorem to determine the combined force acting on the device.
|
||||
return this.sample.x * this.sample.x + this.sample.y * this.sample.y + this.sample.z * this.sample.z;
|
||||
}
|
||||
|
||||
/**
|
||||
* Service function. Determines the best guess posture of the device based on instantaneous data.
|
||||
* This makes no use of historic data (except for shake), and forms this input to the filter implemented in updateGesture().
|
||||
*
|
||||
* @return A best guess of the current posture of the device, based on instantaneous data.
|
||||
*/
|
||||
private instantaneousPosture(): number {
|
||||
let force = this.instantaneousAccelerationSquared();
|
||||
let shakeDetected = false;
|
||||
|
||||
// Test for shake events.
|
||||
// We detect a shake by measuring zero crossings in each axis. In other words, if we see a strong acceleration to the left followed by
|
||||
// a string acceleration to the right, then we can infer a shake. Similarly, we can do this for each acxis (left/right, up/down, in/out).
|
||||
//
|
||||
// If we see enough zero crossings in succession (MICROBIT_ACCELEROMETER_SHAKE_COUNT_THRESHOLD), then we decide that the device
|
||||
// has been shaken.
|
||||
if ((this.getX() < -DAL.MICROBIT_ACCELEROMETER_SHAKE_TOLERANCE && this.shake.x) || (this.getX() > DAL.MICROBIT_ACCELEROMETER_SHAKE_TOLERANCE && !this.shake.x)) {
|
||||
shakeDetected = true;
|
||||
this.shake.x = !this.shake.x;
|
||||
}
|
||||
|
||||
if ((this.getY() < -DAL.MICROBIT_ACCELEROMETER_SHAKE_TOLERANCE && this.shake.y) || (this.getY() > DAL.MICROBIT_ACCELEROMETER_SHAKE_TOLERANCE && !this.shake.y)) {
|
||||
shakeDetected = true;
|
||||
this.shake.y = !this.shake.y;
|
||||
}
|
||||
|
||||
if ((this.getZ() < -DAL.MICROBIT_ACCELEROMETER_SHAKE_TOLERANCE && this.shake.z) || (this.getZ() > DAL.MICROBIT_ACCELEROMETER_SHAKE_TOLERANCE && !this.shake.z)) {
|
||||
shakeDetected = true;
|
||||
this.shake.z = !this.shake.z;
|
||||
}
|
||||
|
||||
if (shakeDetected && this.shake.count < DAL.MICROBIT_ACCELEROMETER_SHAKE_COUNT_THRESHOLD && ++this.shake.count == DAL.MICROBIT_ACCELEROMETER_SHAKE_COUNT_THRESHOLD)
|
||||
this.shake.shaken = 1;
|
||||
|
||||
if (++this.shake.timer >= DAL.MICROBIT_ACCELEROMETER_SHAKE_DAMPING) {
|
||||
this.shake.timer = 0;
|
||||
if (this.shake.count > 0) {
|
||||
if (--this.shake.count == 0)
|
||||
this.shake.shaken = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.shake.shaken)
|
||||
return DAL.MICROBIT_ACCELEROMETER_EVT_SHAKE;
|
||||
|
||||
let sq = (n: number) => n * n
|
||||
|
||||
if (force < sq(DAL.MICROBIT_ACCELEROMETER_FREEFALL_TOLERANCE))
|
||||
return DAL.MICROBIT_ACCELEROMETER_EVT_FREEFALL;
|
||||
|
||||
if (force > sq(DAL.MICROBIT_ACCELEROMETER_3G_TOLERANCE))
|
||||
return DAL.MICROBIT_ACCELEROMETER_EVT_3G;
|
||||
|
||||
if (force > sq(DAL.MICROBIT_ACCELEROMETER_6G_TOLERANCE))
|
||||
return DAL.MICROBIT_ACCELEROMETER_EVT_6G;
|
||||
|
||||
if (force > sq(DAL.MICROBIT_ACCELEROMETER_8G_TOLERANCE))
|
||||
return DAL.MICROBIT_ACCELEROMETER_EVT_8G;
|
||||
|
||||
// Determine our posture.
|
||||
if (this.getX() < (-1000 + DAL.MICROBIT_ACCELEROMETER_TILT_TOLERANCE))
|
||||
return DAL.MICROBIT_ACCELEROMETER_EVT_TILT_LEFT;
|
||||
|
||||
if (this.getX() > (1000 - DAL.MICROBIT_ACCELEROMETER_TILT_TOLERANCE))
|
||||
return DAL.MICROBIT_ACCELEROMETER_EVT_TILT_RIGHT;
|
||||
|
||||
if (this.getY() < (-1000 + DAL.MICROBIT_ACCELEROMETER_TILT_TOLERANCE))
|
||||
return DAL.MICROBIT_ACCELEROMETER_EVT_TILT_DOWN;
|
||||
|
||||
if (this.getY() > (1000 - DAL.MICROBIT_ACCELEROMETER_TILT_TOLERANCE))
|
||||
return DAL.MICROBIT_ACCELEROMETER_EVT_TILT_UP;
|
||||
|
||||
if (this.getZ() < (-1000 + DAL.MICROBIT_ACCELEROMETER_TILT_TOLERANCE))
|
||||
return DAL.MICROBIT_ACCELEROMETER_EVT_FACE_UP;
|
||||
|
||||
if (this.getZ() > (1000 - DAL.MICROBIT_ACCELEROMETER_TILT_TOLERANCE))
|
||||
return DAL.MICROBIT_ACCELEROMETER_EVT_FACE_DOWN;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
updateGesture() {
|
||||
// Determine what it looks like we're doing based on the latest sample...
|
||||
let g = this.instantaneousPosture();
|
||||
|
||||
// Perform some low pass filtering to reduce jitter from any detected effects
|
||||
if (g == this.currentGesture) {
|
||||
if (this.sigma < DAL.MICROBIT_ACCELEROMETER_GESTURE_DAMPING)
|
||||
this.sigma++;
|
||||
}
|
||||
else {
|
||||
this.currentGesture = g;
|
||||
this.sigma = 0;
|
||||
}
|
||||
|
||||
// If we've reached threshold, update our record and raise the relevant event...
|
||||
if (this.currentGesture != this.lastGesture && this.sigma >= DAL.MICROBIT_ACCELEROMETER_GESTURE_DAMPING) {
|
||||
this.lastGesture = this.currentGesture;
|
||||
board().bus.queue(DAL.MICROBIT_ID_GESTURE, this.lastGesture);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the X axis value of the latest update from the accelerometer.
|
||||
* @param system The coordinate system to use. By default, a simple cartesian system is provided.
|
||||
* @return The force measured in the X axis, in milli-g.
|
||||
*
|
||||
* Example:
|
||||
* @code
|
||||
* uBit.accelerometer.getX();
|
||||
* uBit.accelerometer.getX(RAW);
|
||||
* @endcode
|
||||
*/
|
||||
public getX(system: MicroBitCoordinateSystem = MicroBitCoordinateSystem.SIMPLE_CARTESIAN): number {
|
||||
this.activate();
|
||||
switch (system) {
|
||||
case MicroBitCoordinateSystem.SIMPLE_CARTESIAN:
|
||||
return -this.sample.x;
|
||||
|
||||
case MicroBitCoordinateSystem.NORTH_EAST_DOWN:
|
||||
return this.sample.y;
|
||||
//case MicroBitCoordinateSystem.SIMPLE_CARTESIAN.RAW:
|
||||
default:
|
||||
return this.sample.x;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the Y axis value of the latest update from the accelerometer.
|
||||
* @param system The coordinate system to use. By default, a simple cartesian system is provided.
|
||||
* @return The force measured in the Y axis, in milli-g.
|
||||
*
|
||||
* Example:
|
||||
* @code
|
||||
* uBit.accelerometer.getY();
|
||||
* uBit.accelerometer.getY(RAW);
|
||||
* @endcode
|
||||
*/
|
||||
public getY(system: MicroBitCoordinateSystem = MicroBitCoordinateSystem.SIMPLE_CARTESIAN): number {
|
||||
this.activate();
|
||||
switch (system) {
|
||||
case MicroBitCoordinateSystem.SIMPLE_CARTESIAN:
|
||||
return -this.sample.y;
|
||||
|
||||
case MicroBitCoordinateSystem.NORTH_EAST_DOWN:
|
||||
return -this.sample.x;
|
||||
//case RAW:
|
||||
default:
|
||||
return this.sample.y;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the Z axis value of the latest update from the accelerometer.
|
||||
* @param system The coordinate system to use. By default, a simple cartesian system is provided.
|
||||
* @return The force measured in the Z axis, in milli-g.
|
||||
*
|
||||
* Example:
|
||||
* @code
|
||||
* uBit.accelerometer.getZ();
|
||||
* uBit.accelerometer.getZ(RAW);
|
||||
* @endcode
|
||||
*/
|
||||
public getZ(system: MicroBitCoordinateSystem = MicroBitCoordinateSystem.SIMPLE_CARTESIAN): number {
|
||||
this.activate();
|
||||
switch (system) {
|
||||
case MicroBitCoordinateSystem.NORTH_EAST_DOWN:
|
||||
return -this.sample.z;
|
||||
//case MicroBitCoordinateSystem.SIMPLE_CARTESIAN:
|
||||
//case MicroBitCoordinateSystem.RAW:
|
||||
default:
|
||||
return this.sample.z;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a rotation compensated pitch of the device, based on the latest update from the accelerometer.
|
||||
* @return The pitch of the device, in degrees.
|
||||
*
|
||||
* Example:
|
||||
* @code
|
||||
* uBit.accelerometer.getPitch();
|
||||
* @endcode
|
||||
*/
|
||||
public getPitch(): number {
|
||||
this.activate();
|
||||
return Math.floor((360 * this.getPitchRadians()) / (2 * Math.PI));
|
||||
}
|
||||
|
||||
getPitchRadians(): number {
|
||||
this.recalculatePitchRoll();
|
||||
return this.pitch;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a rotation compensated roll of the device, based on the latest update from the accelerometer.
|
||||
* @return The roll of the device, in degrees.
|
||||
*
|
||||
* Example:
|
||||
* @code
|
||||
* uBit.accelerometer.getRoll();
|
||||
* @endcode
|
||||
*/
|
||||
public getRoll(): number {
|
||||
this.activate();
|
||||
return Math.floor((360 * this.getRollRadians()) / (2 * Math.PI));
|
||||
}
|
||||
|
||||
getRollRadians(): number {
|
||||
this.recalculatePitchRoll();
|
||||
return this.roll;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recalculate roll and pitch values for the current sample.
|
||||
* We only do this at most once per sample, as the necessary trigonemteric functions are rather
|
||||
* heavyweight for a CPU without a floating point unit...
|
||||
*/
|
||||
recalculatePitchRoll() {
|
||||
let x = this.getX(MicroBitCoordinateSystem.NORTH_EAST_DOWN);
|
||||
let y = this.getY(MicroBitCoordinateSystem.NORTH_EAST_DOWN);
|
||||
let z = this.getZ(MicroBitCoordinateSystem.NORTH_EAST_DOWN);
|
||||
|
||||
this.roll = Math.atan2(y, z);
|
||||
this.pitch = Math.atan(-x / (y * Math.sin(this.roll) + z * Math.cos(this.roll)));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
export class Board extends BaseBoard {
|
||||
id: string;
|
||||
|
||||
// the bus
|
||||
bus: EventBus;
|
||||
radio: RadioBus;
|
||||
|
||||
// display
|
||||
image = createInternalImage(5);
|
||||
brigthness = 255;
|
||||
displayMode = DisplayMode.bw;
|
||||
font: Image = createFont();
|
||||
|
||||
// buttons
|
||||
usesButtonAB: boolean = false;
|
||||
buttons: Button[];
|
||||
|
||||
// pins
|
||||
pins: Pin[];
|
||||
|
||||
// serial
|
||||
serialIn: string[] = [];
|
||||
|
||||
// sensors
|
||||
accelerometer: Accelerometer;
|
||||
|
||||
// gestures
|
||||
useShake = false;
|
||||
|
||||
usesHeading = false;
|
||||
heading = 90;
|
||||
|
||||
usesTemperature = false;
|
||||
temperature = 21;
|
||||
|
||||
usesLightLevel = false;
|
||||
lightLevel = 128;
|
||||
|
||||
animationQ: AnimationQueue;
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
this.id = "b" + Math_.random(2147483647);
|
||||
this.animationQ = new AnimationQueue(runtime);
|
||||
this.bus = new EventBus(runtime);
|
||||
this.radio = new RadioBus(runtime);
|
||||
this.accelerometer = new Accelerometer(runtime);
|
||||
this.buttons = [
|
||||
new Button(DAL.MICROBIT_ID_BUTTON_A),
|
||||
new Button(DAL.MICROBIT_ID_BUTTON_B),
|
||||
new Button(DAL.MICROBIT_ID_BUTTON_AB)
|
||||
];
|
||||
this.pins = [
|
||||
new Pin(DAL.MICROBIT_ID_IO_P0),
|
||||
new Pin(DAL.MICROBIT_ID_IO_P1),
|
||||
new Pin(DAL.MICROBIT_ID_IO_P2),
|
||||
new Pin(DAL.MICROBIT_ID_IO_P3),
|
||||
new Pin(DAL.MICROBIT_ID_IO_P4),
|
||||
new Pin(DAL.MICROBIT_ID_IO_P5),
|
||||
new Pin(DAL.MICROBIT_ID_IO_P6),
|
||||
new Pin(DAL.MICROBIT_ID_IO_P7),
|
||||
new Pin(DAL.MICROBIT_ID_IO_P8),
|
||||
new Pin(DAL.MICROBIT_ID_IO_P9),
|
||||
new Pin(DAL.MICROBIT_ID_IO_P10),
|
||||
new Pin(DAL.MICROBIT_ID_IO_P11),
|
||||
new Pin(DAL.MICROBIT_ID_IO_P12),
|
||||
new Pin(DAL.MICROBIT_ID_IO_P13),
|
||||
new Pin(DAL.MICROBIT_ID_IO_P14),
|
||||
new Pin(DAL.MICROBIT_ID_IO_P15),
|
||||
new Pin(DAL.MICROBIT_ID_IO_P16),
|
||||
null,
|
||||
null,
|
||||
new Pin(DAL.MICROBIT_ID_IO_P19),
|
||||
new Pin(DAL.MICROBIT_ID_IO_P20)
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
initAsync(msg: SimulatorRunMessage): Promise<void> {
|
||||
let options = (msg.options || {}) as RuntimeOptions;
|
||||
let theme: micro_bit.IBoardTheme;
|
||||
switch (options.theme) {
|
||||
case 'blue': theme = micro_bit.themes[0]; break;
|
||||
case 'yellow': theme = micro_bit.themes[1]; break;
|
||||
case 'green': theme = micro_bit.themes[2]; break;
|
||||
case 'red': theme = micro_bit.themes[3]; break;
|
||||
default: theme = pxsim.micro_bit.randomTheme();
|
||||
}
|
||||
|
||||
let view = new pxsim.micro_bit.MicrobitBoardSvg({
|
||||
theme: theme,
|
||||
runtime: runtime
|
||||
})
|
||||
document.body.innerHTML = ""; // clear children
|
||||
document.body.appendChild(view.element);
|
||||
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
receiveMessage(msg: SimulatorMessage) {
|
||||
if (!runtime || runtime.dead) return;
|
||||
|
||||
switch (msg.type || "") {
|
||||
case "eventbus":
|
||||
let ev = <SimulatorEventBusMessage>msg;
|
||||
this.bus.queue(ev.id, ev.eventid, ev.value);
|
||||
break;
|
||||
case "serial":
|
||||
this.serialIn.push((<SimulatorSerialMessage>msg).data || "");
|
||||
break;
|
||||
case "radiopacket":
|
||||
let packet = <SimulatorRadioPacketMessage>msg;
|
||||
this.radio.datagram.queue({ data: packet.data, rssi: packet.rssi || 0 })
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
readSerial() {
|
||||
let v = this.serialIn.shift() || "";
|
||||
return v;
|
||||
}
|
||||
|
||||
kill() {
|
||||
super.kill();
|
||||
AudioContextManager.stop();
|
||||
}
|
||||
|
||||
serialOutBuffer: string = "";
|
||||
writeSerial(s: string) {
|
||||
for (let i = 0; i < s.length; ++i) {
|
||||
let c = s[i];
|
||||
this.serialOutBuffer += c;
|
||||
if (c == "\n") {
|
||||
Runtime.postMessage(<SimulatorSerialMessage>{
|
||||
type: "serial",
|
||||
data: this.serialOutBuffer,
|
||||
id: runtime.id,
|
||||
sim: true
|
||||
})
|
||||
this.serialOutBuffer = ""
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class Image extends RefObject {
|
||||
public static height: number = 5;
|
||||
public width: number;
|
||||
public data: number[];
|
||||
constructor(width: number, data: number[]) {
|
||||
super()
|
||||
this.width = width;
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public print() {
|
||||
console.log(`Image id:${this.id} refs:${this.refcnt} size:${this.width}x${Image.height}`)
|
||||
}
|
||||
public get(x: number, y: number): number {
|
||||
if (x < 0 || x >= this.width || y < 0 || y >= 5) return 0;
|
||||
return this.data[y * this.width + x];
|
||||
}
|
||||
public set(x: number, y: number, v: number) {
|
||||
if (x < 0 || x >= this.width || y < 0 || y >= 5) return;
|
||||
this.data[y * this.width + x] = Math.max(0, Math.min(255, v));
|
||||
}
|
||||
public copyTo(xSrcIndex: number, length: number, target: Image, xTargetIndex: number): void {
|
||||
for (let x = 0; x < length; x++) {
|
||||
for (let y = 0; y < 5; y++) {
|
||||
let value = this.get(xSrcIndex + x, y);
|
||||
target.set(xTargetIndex + x, y, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
public shiftLeft(cols: number) {
|
||||
for (let x = 0; x < this.width; ++x)
|
||||
for (let y = 0; y < 5; ++y)
|
||||
this.set(x, y, x < this.width - cols ? this.get(x + cols, y) : 0);
|
||||
}
|
||||
|
||||
public shiftRight(cols: number) {
|
||||
for (let x = this.width - 1; x <= 0; --x)
|
||||
for (let y = 0; y < 5; ++y)
|
||||
this.set(x, y, x > cols ? this.get(x - cols, y) : 0);
|
||||
}
|
||||
|
||||
public clear(): void {
|
||||
for (let i = 0; i < this.data.length; ++i)
|
||||
this.data[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
export function createInternalImage(width: number): Image {
|
||||
let img = createImage(width)
|
||||
pxsim.noLeakTracking(img)
|
||||
return img
|
||||
}
|
||||
|
||||
export function createImage(width: number): Image {
|
||||
return new Image(width, new Array(width * 5));
|
||||
}
|
||||
|
||||
export function createImageFromBuffer(data: number[]): Image {
|
||||
return new Image(data.length / 5, data);
|
||||
}
|
||||
|
||||
export function createImageFromString(text: string): Image {
|
||||
let font = board().font;
|
||||
let w = font.width;
|
||||
let sprite = createInternalImage(6 * text.length - 1);
|
||||
let k = 0;
|
||||
for (let i = 0; i < text.length; i++) {
|
||||
let charCode = text.charCodeAt(i);
|
||||
let charStart = (charCode - 32) * 5;
|
||||
if (charStart < 0 || charStart + 5 > w) {
|
||||
charCode = " ".charCodeAt(0);
|
||||
charStart = (charCode - 32) * 5;
|
||||
}
|
||||
|
||||
font.copyTo(charStart, 5, sprite, k);
|
||||
k = k + 5;
|
||||
if (i < text.length - 1) {
|
||||
k = k + 1;
|
||||
}
|
||||
}
|
||||
return sprite;
|
||||
}
|
||||
|
||||
export function createFont(): Image {
|
||||
const data = [0x0, 0x0, 0x0, 0x0, 0x0, 0x8, 0x8, 0x8, 0x0, 0x8, 0xa, 0x4a, 0x40, 0x0, 0x0, 0xa, 0x5f, 0xea, 0x5f, 0xea, 0xe, 0xd9, 0x2e, 0xd3, 0x6e, 0x19, 0x32, 0x44, 0x89, 0x33, 0xc, 0x92, 0x4c, 0x92, 0x4d, 0x8, 0x8, 0x0, 0x0, 0x0, 0x4, 0x88, 0x8, 0x8, 0x4, 0x8, 0x4, 0x84, 0x84, 0x88, 0x0, 0xa, 0x44, 0x8a, 0x40, 0x0, 0x4, 0x8e, 0xc4, 0x80, 0x0, 0x0, 0x0, 0x4, 0x88, 0x0, 0x0, 0xe, 0xc0, 0x0, 0x0, 0x0, 0x0, 0x8, 0x0, 0x1, 0x22, 0x44, 0x88, 0x10, 0xc, 0x92, 0x52, 0x52, 0x4c, 0x4, 0x8c, 0x84, 0x84, 0x8e, 0x1c, 0x82, 0x4c, 0x90, 0x1e, 0x1e, 0xc2, 0x44, 0x92, 0x4c, 0x6, 0xca, 0x52, 0x5f, 0xe2, 0x1f, 0xf0, 0x1e, 0xc1, 0x3e, 0x2, 0x44, 0x8e, 0xd1, 0x2e, 0x1f, 0xe2, 0x44, 0x88, 0x10, 0xe, 0xd1, 0x2e, 0xd1, 0x2e, 0xe, 0xd1, 0x2e, 0xc4, 0x88, 0x0, 0x8, 0x0, 0x8, 0x0, 0x0, 0x4, 0x80, 0x4, 0x88, 0x2, 0x44, 0x88, 0x4, 0x82, 0x0, 0xe, 0xc0, 0xe, 0xc0, 0x8, 0x4, 0x82, 0x44, 0x88, 0xe, 0xd1, 0x26, 0xc0, 0x4, 0xe, 0xd1, 0x35, 0xb3, 0x6c, 0xc, 0x92, 0x5e, 0xd2, 0x52, 0x1c, 0x92, 0x5c, 0x92, 0x5c, 0xe, 0xd0, 0x10, 0x10, 0xe, 0x1c, 0x92, 0x52, 0x52, 0x5c, 0x1e, 0xd0, 0x1c, 0x90, 0x1e, 0x1e, 0xd0, 0x1c, 0x90, 0x10, 0xe, 0xd0, 0x13, 0x71, 0x2e, 0x12, 0x52, 0x5e, 0xd2, 0x52, 0x1c, 0x88, 0x8, 0x8, 0x1c, 0x1f, 0xe2, 0x42, 0x52, 0x4c, 0x12, 0x54, 0x98, 0x14, 0x92, 0x10, 0x10, 0x10, 0x10, 0x1e, 0x11, 0x3b, 0x75, 0xb1, 0x31, 0x11, 0x39, 0x35, 0xb3, 0x71, 0xc, 0x92, 0x52, 0x52, 0x4c, 0x1c, 0x92, 0x5c, 0x90, 0x10, 0xc, 0x92, 0x52, 0x4c, 0x86, 0x1c, 0x92, 0x5c, 0x92, 0x51, 0xe, 0xd0, 0xc, 0x82, 0x5c, 0x1f, 0xe4, 0x84, 0x84, 0x84, 0x12, 0x52, 0x52, 0x52, 0x4c, 0x11, 0x31, 0x31, 0x2a, 0x44, 0x11, 0x31, 0x35, 0xbb, 0x71, 0x12, 0x52, 0x4c, 0x92, 0x52, 0x11, 0x2a, 0x44, 0x84, 0x84, 0x1e, 0xc4, 0x88, 0x10, 0x1e, 0xe, 0xc8, 0x8, 0x8, 0xe, 0x10, 0x8, 0x4, 0x82, 0x41, 0xe, 0xc2, 0x42, 0x42, 0x4e, 0x4, 0x8a, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1f, 0x8, 0x4, 0x80, 0x0, 0x0, 0x0, 0xe, 0xd2, 0x52, 0x4f, 0x10, 0x10, 0x1c, 0x92, 0x5c, 0x0, 0xe, 0xd0, 0x10, 0xe, 0x2, 0x42, 0x4e, 0xd2, 0x4e, 0xc, 0x92, 0x5c, 0x90, 0xe, 0x6, 0xc8, 0x1c, 0x88, 0x8, 0xe, 0xd2, 0x4e, 0xc2, 0x4c, 0x10, 0x10, 0x1c, 0x92, 0x52, 0x8, 0x0, 0x8, 0x8, 0x8, 0x2, 0x40, 0x2, 0x42, 0x4c, 0x10, 0x14, 0x98, 0x14, 0x92, 0x8, 0x8, 0x8, 0x8, 0x6, 0x0, 0x1b, 0x75, 0xb1, 0x31, 0x0, 0x1c, 0x92, 0x52, 0x52, 0x0, 0xc, 0x92, 0x52, 0x4c, 0x0, 0x1c, 0x92, 0x5c, 0x90, 0x0, 0xe, 0xd2, 0x4e, 0xc2, 0x0, 0xe, 0xd0, 0x10, 0x10, 0x0, 0x6, 0xc8, 0x4, 0x98, 0x8, 0x8, 0xe, 0xc8, 0x7, 0x0, 0x12, 0x52, 0x52, 0x4f, 0x0, 0x11, 0x31, 0x2a, 0x44, 0x0, 0x11, 0x31, 0x35, 0xbb, 0x0, 0x12, 0x4c, 0x8c, 0x92, 0x0, 0x11, 0x2a, 0x44, 0x98, 0x0, 0x1e, 0xc4, 0x88, 0x1e, 0x6, 0xc4, 0x8c, 0x84, 0x86, 0x8, 0x8, 0x8, 0x8, 0x8, 0x18, 0x8, 0xc, 0x88, 0x18, 0x0, 0x0, 0xc, 0x83, 0x60];
|
||||
|
||||
let nb = data.length;
|
||||
let n = nb / 5;
|
||||
let font = createInternalImage(nb);
|
||||
for (let c = 0; c < n; c++) {
|
||||
for (let row = 0; row < 5; row++) {
|
||||
let char = data[c * 5 + row];
|
||||
for (let col = 0; col < 5; col++) {
|
||||
if ((char & (1 << col)) != 0)
|
||||
font.set((c * 5 + 4) - col, row, 255);
|
||||
}
|
||||
}
|
||||
}
|
||||
return font;
|
||||
}
|
||||
}
|
389
sim/state/accelerometer.ts
Normal file
@ -0,0 +1,389 @@
|
||||
namespace pxsim.input {
|
||||
export function onGesture(gesture: number, handler: RefAction) {
|
||||
let b = board().accelerometerState;
|
||||
b.accelerometer.activate();
|
||||
|
||||
if (gesture == 11 && !b.useShake) { // SAKE
|
||||
b.useShake = true;
|
||||
runtime.queueDisplayUpdate();
|
||||
}
|
||||
pxtcore.registerWithDal(DAL.MICROBIT_ID_GESTURE, gesture, handler);
|
||||
}
|
||||
|
||||
export function acceleration(dimension: number): number {
|
||||
let b = board().accelerometerState;
|
||||
let acc = b.accelerometer;
|
||||
acc.activate();
|
||||
switch (dimension) {
|
||||
case 0: return acc.getX();
|
||||
case 1: return acc.getY();
|
||||
case 2: return acc.getZ();
|
||||
default: return Math.floor(Math.sqrt(acc.instantaneousAccelerationSquared()));
|
||||
}
|
||||
}
|
||||
|
||||
export function rotation(kind: number): number {
|
||||
let b = board().accelerometerState;
|
||||
let acc = b.accelerometer;
|
||||
acc.activate();
|
||||
let x = acc.getX(MicroBitCoordinateSystem.NORTH_EAST_DOWN);
|
||||
let y = acc.getX(MicroBitCoordinateSystem.NORTH_EAST_DOWN);
|
||||
let z = acc.getX(MicroBitCoordinateSystem.NORTH_EAST_DOWN);
|
||||
|
||||
let roll = Math.atan2(y, z);
|
||||
let pitch = Math.atan(-x / (y * Math.sin(roll) + z * Math.cos(roll)));
|
||||
|
||||
let r = 0;
|
||||
switch (kind) {
|
||||
case 0: r = pitch; break;
|
||||
case 1: r = roll; break;
|
||||
}
|
||||
return Math.floor(r / Math.PI * 180);
|
||||
}
|
||||
|
||||
export function setAccelerometerRange(range: number) {
|
||||
let b = board().accelerometerState;
|
||||
b.accelerometer.setSampleRange(range);
|
||||
}
|
||||
}
|
||||
|
||||
namespace pxsim {
|
||||
interface AccelerometerSample {
|
||||
x: number;
|
||||
y: number;
|
||||
z: number;
|
||||
}
|
||||
|
||||
interface ShakeHistory {
|
||||
x: boolean;
|
||||
y: boolean;
|
||||
z: boolean;
|
||||
count: number;
|
||||
shaken: number;
|
||||
timer: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Co-ordinate systems that can be used.
|
||||
* RAW: Unaltered data. Data will be returned directly from the accelerometer.
|
||||
*
|
||||
* SIMPLE_CARTESIAN: Data will be returned based on an easy to understand alignment, consistent with the cartesian system taught in schools.
|
||||
* When held upright, facing the user:
|
||||
*
|
||||
* /
|
||||
* +--------------------+ z
|
||||
* | |
|
||||
* | ..... |
|
||||
* | * ..... * |
|
||||
* ^ | ..... |
|
||||
* | | |
|
||||
* y +--------------------+ x-->
|
||||
*
|
||||
*
|
||||
* NORTH_EAST_DOWN: Data will be returned based on the industry convention of the North East Down (NED) system.
|
||||
* When held upright, facing the user:
|
||||
*
|
||||
* z
|
||||
* +--------------------+ /
|
||||
* | |
|
||||
* | ..... |
|
||||
* | * ..... * |
|
||||
* ^ | ..... |
|
||||
* | | |
|
||||
* x +--------------------+ y-->
|
||||
*
|
||||
*/
|
||||
export enum MicroBitCoordinateSystem {
|
||||
RAW,
|
||||
SIMPLE_CARTESIAN,
|
||||
NORTH_EAST_DOWN
|
||||
}
|
||||
|
||||
export class Accelerometer {
|
||||
private sigma: number = 0; // the number of ticks that the instantaneous gesture has been stable.
|
||||
private lastGesture: number = 0; // the last, stable gesture recorded.
|
||||
private currentGesture: number = 0 // the instantaneous, unfiltered gesture detected.
|
||||
private sample: AccelerometerSample = { x: 0, y: 0, z: -1023 }
|
||||
private shake: ShakeHistory = { x: false, y: false, z: false, count: 0, shaken: 0, timer: 0 }; // State information needed to detect shake events.
|
||||
private pitch: number;
|
||||
private roll: number;
|
||||
private id: number;
|
||||
public isActive = false;
|
||||
public sampleRange = 2;
|
||||
|
||||
constructor(public runtime: Runtime) {
|
||||
this.id = DAL.MICROBIT_ID_ACCELEROMETER;
|
||||
}
|
||||
|
||||
public setSampleRange(range: number) {
|
||||
this.activate();
|
||||
this.sampleRange = Math.max(1, Math.min(8, range));
|
||||
}
|
||||
|
||||
public activate() {
|
||||
if (!this.isActive) {
|
||||
this.isActive = true;
|
||||
this.runtime.queueDisplayUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the acceleration data from the accelerometer, and stores it in our buffer.
|
||||
* This is called by the tick() member function, if the interrupt is set!
|
||||
*/
|
||||
public update(x: number, y: number, z: number) {
|
||||
// read MSB values...
|
||||
this.sample.x = Math.floor(x);
|
||||
this.sample.y = Math.floor(y);
|
||||
this.sample.z = Math.floor(z);
|
||||
|
||||
// Update gesture tracking
|
||||
this.updateGesture();
|
||||
|
||||
// Indicate that a new sample is available
|
||||
board().bus.queue(this.id, DAL.MICROBIT_ACCELEROMETER_EVT_DATA_UPDATE)
|
||||
}
|
||||
|
||||
public instantaneousAccelerationSquared() {
|
||||
// Use pythagoras theorem to determine the combined force acting on the device.
|
||||
return this.sample.x * this.sample.x + this.sample.y * this.sample.y + this.sample.z * this.sample.z;
|
||||
}
|
||||
|
||||
/**
|
||||
* Service function. Determines the best guess posture of the device based on instantaneous data.
|
||||
* This makes no use of historic data (except for shake), and forms this input to the filter implemented in updateGesture().
|
||||
*
|
||||
* @return A best guess of the current posture of the device, based on instantaneous data.
|
||||
*/
|
||||
private instantaneousPosture(): number {
|
||||
let force = this.instantaneousAccelerationSquared();
|
||||
let shakeDetected = false;
|
||||
|
||||
// Test for shake events.
|
||||
// We detect a shake by measuring zero crossings in each axis. In other words, if we see a strong acceleration to the left followed by
|
||||
// a string acceleration to the right, then we can infer a shake. Similarly, we can do this for each acxis (left/right, up/down, in/out).
|
||||
//
|
||||
// If we see enough zero crossings in succession (MICROBIT_ACCELEROMETER_SHAKE_COUNT_THRESHOLD), then we decide that the device
|
||||
// has been shaken.
|
||||
if ((this.getX() < -DAL.MICROBIT_ACCELEROMETER_SHAKE_TOLERANCE && this.shake.x) || (this.getX() > DAL.MICROBIT_ACCELEROMETER_SHAKE_TOLERANCE && !this.shake.x)) {
|
||||
shakeDetected = true;
|
||||
this.shake.x = !this.shake.x;
|
||||
}
|
||||
|
||||
if ((this.getY() < -DAL.MICROBIT_ACCELEROMETER_SHAKE_TOLERANCE && this.shake.y) || (this.getY() > DAL.MICROBIT_ACCELEROMETER_SHAKE_TOLERANCE && !this.shake.y)) {
|
||||
shakeDetected = true;
|
||||
this.shake.y = !this.shake.y;
|
||||
}
|
||||
|
||||
if ((this.getZ() < -DAL.MICROBIT_ACCELEROMETER_SHAKE_TOLERANCE && this.shake.z) || (this.getZ() > DAL.MICROBIT_ACCELEROMETER_SHAKE_TOLERANCE && !this.shake.z)) {
|
||||
shakeDetected = true;
|
||||
this.shake.z = !this.shake.z;
|
||||
}
|
||||
|
||||
if (shakeDetected && this.shake.count < DAL.MICROBIT_ACCELEROMETER_SHAKE_COUNT_THRESHOLD && ++this.shake.count == DAL.MICROBIT_ACCELEROMETER_SHAKE_COUNT_THRESHOLD)
|
||||
this.shake.shaken = 1;
|
||||
|
||||
if (++this.shake.timer >= DAL.MICROBIT_ACCELEROMETER_SHAKE_DAMPING) {
|
||||
this.shake.timer = 0;
|
||||
if (this.shake.count > 0) {
|
||||
if (--this.shake.count == 0)
|
||||
this.shake.shaken = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.shake.shaken)
|
||||
return DAL.MICROBIT_ACCELEROMETER_EVT_SHAKE;
|
||||
|
||||
let sq = (n: number) => n * n
|
||||
|
||||
if (force < sq(DAL.MICROBIT_ACCELEROMETER_FREEFALL_TOLERANCE))
|
||||
return DAL.MICROBIT_ACCELEROMETER_EVT_FREEFALL;
|
||||
|
||||
if (force > sq(DAL.MICROBIT_ACCELEROMETER_3G_TOLERANCE))
|
||||
return DAL.MICROBIT_ACCELEROMETER_EVT_3G;
|
||||
|
||||
if (force > sq(DAL.MICROBIT_ACCELEROMETER_6G_TOLERANCE))
|
||||
return DAL.MICROBIT_ACCELEROMETER_EVT_6G;
|
||||
|
||||
if (force > sq(DAL.MICROBIT_ACCELEROMETER_8G_TOLERANCE))
|
||||
return DAL.MICROBIT_ACCELEROMETER_EVT_8G;
|
||||
|
||||
// Determine our posture.
|
||||
if (this.getX() < (-1000 + DAL.MICROBIT_ACCELEROMETER_TILT_TOLERANCE))
|
||||
return DAL.MICROBIT_ACCELEROMETER_EVT_TILT_LEFT;
|
||||
|
||||
if (this.getX() > (1000 - DAL.MICROBIT_ACCELEROMETER_TILT_TOLERANCE))
|
||||
return DAL.MICROBIT_ACCELEROMETER_EVT_TILT_RIGHT;
|
||||
|
||||
if (this.getY() < (-1000 + DAL.MICROBIT_ACCELEROMETER_TILT_TOLERANCE))
|
||||
return DAL.MICROBIT_ACCELEROMETER_EVT_TILT_DOWN;
|
||||
|
||||
if (this.getY() > (1000 - DAL.MICROBIT_ACCELEROMETER_TILT_TOLERANCE))
|
||||
return DAL.MICROBIT_ACCELEROMETER_EVT_TILT_UP;
|
||||
|
||||
if (this.getZ() < (-1000 + DAL.MICROBIT_ACCELEROMETER_TILT_TOLERANCE))
|
||||
return DAL.MICROBIT_ACCELEROMETER_EVT_FACE_UP;
|
||||
|
||||
if (this.getZ() > (1000 - DAL.MICROBIT_ACCELEROMETER_TILT_TOLERANCE))
|
||||
return DAL.MICROBIT_ACCELEROMETER_EVT_FACE_DOWN;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
updateGesture() {
|
||||
// Determine what it looks like we're doing based on the latest sample...
|
||||
let g = this.instantaneousPosture();
|
||||
|
||||
// Perform some low pass filtering to reduce jitter from any detected effects
|
||||
if (g == this.currentGesture) {
|
||||
if (this.sigma < DAL.MICROBIT_ACCELEROMETER_GESTURE_DAMPING)
|
||||
this.sigma++;
|
||||
}
|
||||
else {
|
||||
this.currentGesture = g;
|
||||
this.sigma = 0;
|
||||
}
|
||||
|
||||
// If we've reached threshold, update our record and raise the relevant event...
|
||||
if (this.currentGesture != this.lastGesture && this.sigma >= DAL.MICROBIT_ACCELEROMETER_GESTURE_DAMPING) {
|
||||
this.lastGesture = this.currentGesture;
|
||||
board().bus.queue(DAL.MICROBIT_ID_GESTURE, this.lastGesture);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the X axis value of the latest update from the accelerometer.
|
||||
* @param system The coordinate system to use. By default, a simple cartesian system is provided.
|
||||
* @return The force measured in the X axis, in milli-g.
|
||||
*
|
||||
* Example:
|
||||
* @code
|
||||
* uBit.accelerometer.getX();
|
||||
* uBit.accelerometer.getX(RAW);
|
||||
* @endcode
|
||||
*/
|
||||
public getX(system: MicroBitCoordinateSystem = MicroBitCoordinateSystem.SIMPLE_CARTESIAN): number {
|
||||
this.activate();
|
||||
switch (system) {
|
||||
case MicroBitCoordinateSystem.SIMPLE_CARTESIAN:
|
||||
return -this.sample.x;
|
||||
|
||||
case MicroBitCoordinateSystem.NORTH_EAST_DOWN:
|
||||
return this.sample.y;
|
||||
//case MicroBitCoordinateSystem.SIMPLE_CARTESIAN.RAW:
|
||||
default:
|
||||
return this.sample.x;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the Y axis value of the latest update from the accelerometer.
|
||||
* @param system The coordinate system to use. By default, a simple cartesian system is provided.
|
||||
* @return The force measured in the Y axis, in milli-g.
|
||||
*
|
||||
* Example:
|
||||
* @code
|
||||
* uBit.accelerometer.getY();
|
||||
* uBit.accelerometer.getY(RAW);
|
||||
* @endcode
|
||||
*/
|
||||
public getY(system: MicroBitCoordinateSystem = MicroBitCoordinateSystem.SIMPLE_CARTESIAN): number {
|
||||
this.activate();
|
||||
switch (system) {
|
||||
case MicroBitCoordinateSystem.SIMPLE_CARTESIAN:
|
||||
return -this.sample.y;
|
||||
|
||||
case MicroBitCoordinateSystem.NORTH_EAST_DOWN:
|
||||
return -this.sample.x;
|
||||
//case RAW:
|
||||
default:
|
||||
return this.sample.y;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the Z axis value of the latest update from the accelerometer.
|
||||
* @param system The coordinate system to use. By default, a simple cartesian system is provided.
|
||||
* @return The force measured in the Z axis, in milli-g.
|
||||
*
|
||||
* Example:
|
||||
* @code
|
||||
* uBit.accelerometer.getZ();
|
||||
* uBit.accelerometer.getZ(RAW);
|
||||
* @endcode
|
||||
*/
|
||||
public getZ(system: MicroBitCoordinateSystem = MicroBitCoordinateSystem.SIMPLE_CARTESIAN): number {
|
||||
this.activate();
|
||||
switch (system) {
|
||||
case MicroBitCoordinateSystem.NORTH_EAST_DOWN:
|
||||
return -this.sample.z;
|
||||
//case MicroBitCoordinateSystem.SIMPLE_CARTESIAN:
|
||||
//case MicroBitCoordinateSystem.RAW:
|
||||
default:
|
||||
return this.sample.z;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a rotation compensated pitch of the device, based on the latest update from the accelerometer.
|
||||
* @return The pitch of the device, in degrees.
|
||||
*
|
||||
* Example:
|
||||
* @code
|
||||
* uBit.accelerometer.getPitch();
|
||||
* @endcode
|
||||
*/
|
||||
public getPitch(): number {
|
||||
this.activate();
|
||||
return Math.floor((360 * this.getPitchRadians()) / (2 * Math.PI));
|
||||
}
|
||||
|
||||
getPitchRadians(): number {
|
||||
this.recalculatePitchRoll();
|
||||
return this.pitch;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a rotation compensated roll of the device, based on the latest update from the accelerometer.
|
||||
* @return The roll of the device, in degrees.
|
||||
*
|
||||
* Example:
|
||||
* @code
|
||||
* uBit.accelerometer.getRoll();
|
||||
* @endcode
|
||||
*/
|
||||
public getRoll(): number {
|
||||
this.activate();
|
||||
return Math.floor((360 * this.getRollRadians()) / (2 * Math.PI));
|
||||
}
|
||||
|
||||
getRollRadians(): number {
|
||||
this.recalculatePitchRoll();
|
||||
return this.roll;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recalculate roll and pitch values for the current sample.
|
||||
* We only do this at most once per sample, as the necessary trigonemteric functions are rather
|
||||
* heavyweight for a CPU without a floating point unit...
|
||||
*/
|
||||
recalculatePitchRoll() {
|
||||
let x = this.getX(MicroBitCoordinateSystem.NORTH_EAST_DOWN);
|
||||
let y = this.getY(MicroBitCoordinateSystem.NORTH_EAST_DOWN);
|
||||
let z = this.getZ(MicroBitCoordinateSystem.NORTH_EAST_DOWN);
|
||||
|
||||
this.roll = Math.atan2(y, z);
|
||||
this.pitch = Math.atan(-x / (y * Math.sin(this.roll) + z * Math.cos(this.roll)));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class AccelerometerState {
|
||||
accelerometer: Accelerometer;
|
||||
useShake = false;
|
||||
|
||||
constructor(runtime: Runtime) {
|
||||
this.accelerometer = new Accelerometer(runtime);
|
||||
}
|
||||
}
|
||||
}
|
41
sim/state/buttonpair.ts
Normal file
@ -0,0 +1,41 @@
|
||||
namespace pxsim.input {
|
||||
export function onButtonPressed(button: number, handler: RefAction): void {
|
||||
let b = board().buttonPairState;
|
||||
if (button == DAL.MICROBIT_ID_BUTTON_AB && !b.usesButtonAB) {
|
||||
b.usesButtonAB = true;
|
||||
runtime.queueDisplayUpdate();
|
||||
}
|
||||
pxtcore.registerWithDal(button, DAL.MICROBIT_BUTTON_EVT_CLICK, handler);
|
||||
}
|
||||
|
||||
export function buttonIsPressed(button: number): boolean {
|
||||
let b = board().buttonPairState;
|
||||
if (button == DAL.MICROBIT_ID_BUTTON_AB && !b.usesButtonAB) {
|
||||
b.usesButtonAB = true;
|
||||
runtime.queueDisplayUpdate();
|
||||
}
|
||||
if (button == DAL.MICROBIT_ID_BUTTON_A) return b.aBtn.pressed;
|
||||
if (button == DAL.MICROBIT_ID_BUTTON_B) return b.bBtn.pressed;
|
||||
return b.abBtn.pressed || (b.aBtn.pressed && b.bBtn.pressed);
|
||||
}
|
||||
}
|
||||
|
||||
namespace pxsim {
|
||||
export class Button {
|
||||
constructor(public id: number) { }
|
||||
pressed: boolean;
|
||||
}
|
||||
|
||||
export class ButtonPairState {
|
||||
usesButtonAB: boolean = false;
|
||||
aBtn: Button;
|
||||
bBtn: Button;
|
||||
abBtn: Button;
|
||||
|
||||
constructor() {
|
||||
this.aBtn = new Button(DAL.MICROBIT_ID_BUTTON_A);
|
||||
this.bBtn = new Button(DAL.MICROBIT_ID_BUTTON_B);
|
||||
this.abBtn = new Button(DAL.MICROBIT_ID_BUTTON_AB);
|
||||
}
|
||||
}
|
||||
}
|
22
sim/state/compass.ts
Normal file
@ -0,0 +1,22 @@
|
||||
namespace pxsim.input {
|
||||
export function compassHeading(): number {
|
||||
let b = board().compassState;
|
||||
if (!b.usesHeading) {
|
||||
b.usesHeading = true;
|
||||
runtime.queueDisplayUpdate();
|
||||
}
|
||||
return b.heading;
|
||||
}
|
||||
|
||||
export function magneticForce(): number {
|
||||
// TODO
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
namespace pxsim {
|
||||
export class CompassState {
|
||||
usesHeading = false;
|
||||
heading = 90;
|
||||
}
|
||||
}
|
178
sim/state/edgeconnector.ts
Normal file
@ -0,0 +1,178 @@
|
||||
namespace pxsim.input {
|
||||
export function onPinPressed(pinId: number, handler: RefAction) {
|
||||
let pin = getPin(pinId);
|
||||
if (!pin) return;
|
||||
pin.isTouched();
|
||||
pxtcore.registerWithDal(pin.id, DAL.MICROBIT_BUTTON_EVT_CLICK, handler);
|
||||
}
|
||||
|
||||
export function onPinReleased(pinId: number, handler: RefAction) {
|
||||
let pin = getPin(pinId);
|
||||
if (!pin) return;
|
||||
pin.isTouched();
|
||||
pxtcore.registerWithDal(pin.id, DAL.MICROBIT_BUTTON_EVT_UP, handler);
|
||||
}
|
||||
|
||||
export function pinIsPressed(pinId: number): boolean {
|
||||
let pin = getPin(pinId);
|
||||
if (!pin) return false;
|
||||
return pin.isTouched();
|
||||
}
|
||||
}
|
||||
|
||||
namespace pxsim {
|
||||
export function getPin(id: number) {
|
||||
return board().edgeConnectorState.getPin(id);
|
||||
}
|
||||
|
||||
export enum PinFlags {
|
||||
Unused = 0,
|
||||
Digital = 0x0001,
|
||||
Analog = 0x0002,
|
||||
Input = 0x0004,
|
||||
Output = 0x0008,
|
||||
Touch = 0x0010
|
||||
}
|
||||
|
||||
export class Pin {
|
||||
constructor(public id: number) { }
|
||||
touched = false;
|
||||
value = 0;
|
||||
period = 0;
|
||||
mode = PinFlags.Unused;
|
||||
pitch = false;
|
||||
pull = 0; // PullDown
|
||||
|
||||
isTouched(): boolean {
|
||||
this.mode = PinFlags.Touch;
|
||||
return this.touched;
|
||||
}
|
||||
}
|
||||
|
||||
export class EdgeConnectorState {
|
||||
pins: Pin[];
|
||||
|
||||
constructor() {
|
||||
this.pins = [
|
||||
new Pin(DAL.MICROBIT_ID_IO_P0),
|
||||
new Pin(DAL.MICROBIT_ID_IO_P1),
|
||||
new Pin(DAL.MICROBIT_ID_IO_P2),
|
||||
new Pin(DAL.MICROBIT_ID_IO_P3),
|
||||
new Pin(DAL.MICROBIT_ID_IO_P4),
|
||||
new Pin(DAL.MICROBIT_ID_IO_P5),
|
||||
new Pin(DAL.MICROBIT_ID_IO_P6),
|
||||
new Pin(DAL.MICROBIT_ID_IO_P7),
|
||||
new Pin(DAL.MICROBIT_ID_IO_P8),
|
||||
new Pin(DAL.MICROBIT_ID_IO_P9),
|
||||
new Pin(DAL.MICROBIT_ID_IO_P10),
|
||||
new Pin(DAL.MICROBIT_ID_IO_P11),
|
||||
new Pin(DAL.MICROBIT_ID_IO_P12),
|
||||
new Pin(DAL.MICROBIT_ID_IO_P13),
|
||||
new Pin(DAL.MICROBIT_ID_IO_P14),
|
||||
new Pin(DAL.MICROBIT_ID_IO_P15),
|
||||
new Pin(DAL.MICROBIT_ID_IO_P16),
|
||||
null,
|
||||
null,
|
||||
new Pin(DAL.MICROBIT_ID_IO_P19),
|
||||
new Pin(DAL.MICROBIT_ID_IO_P20)
|
||||
];
|
||||
}
|
||||
|
||||
public getPin(id: number) {
|
||||
return this.pins.filter(p => p && p.id == id)[0] || null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace pxsim.pins {
|
||||
export function digitalReadPin(pinId: number): number {
|
||||
let pin = getPin(pinId);
|
||||
if (!pin) return;
|
||||
pin.mode = PinFlags.Digital | PinFlags.Input;
|
||||
return pin.value > 100 ? 1 : 0;
|
||||
}
|
||||
|
||||
export function digitalWritePin(pinId: number, value: number) {
|
||||
let pin = getPin(pinId);
|
||||
if (!pin) return;
|
||||
pin.mode = PinFlags.Digital | PinFlags.Output;
|
||||
pin.value = value > 0 ? 1023 : 0;
|
||||
runtime.queueDisplayUpdate();
|
||||
}
|
||||
|
||||
export function setPull(pinId: number, pull: number) {
|
||||
let pin = getPin(pinId);
|
||||
if (!pin) return;
|
||||
pin.pull = pull;
|
||||
}
|
||||
|
||||
export function analogReadPin(pinId: number): number {
|
||||
let pin = getPin(pinId);
|
||||
if (!pin) return;
|
||||
pin.mode = PinFlags.Analog | PinFlags.Input;
|
||||
return pin.value || 0;
|
||||
}
|
||||
|
||||
export function analogWritePin(pinId: number, value: number) {
|
||||
let pin = getPin(pinId);
|
||||
if (!pin) return;
|
||||
pin.mode = PinFlags.Analog | PinFlags.Output;
|
||||
pin.value = value ? 1 : 0;
|
||||
runtime.queueDisplayUpdate();
|
||||
}
|
||||
|
||||
export function analogSetPeriod(pinId: number, micros: number) {
|
||||
let pin = getPin(pinId);
|
||||
if (!pin) return;
|
||||
pin.mode = PinFlags.Analog | PinFlags.Output;
|
||||
pin.period = micros;
|
||||
runtime.queueDisplayUpdate();
|
||||
}
|
||||
|
||||
export function servoWritePin(pinId: number, value: number) {
|
||||
analogSetPeriod(pinId, 20000);
|
||||
// TODO
|
||||
}
|
||||
|
||||
export function servoSetPulse(pinId: number, micros: number) {
|
||||
let pin = getPin(pinId);
|
||||
if (!pin) return;
|
||||
// TODO
|
||||
}
|
||||
|
||||
export function analogSetPitchPin(pinId: number) {
|
||||
let pin = getPin(pinId);
|
||||
if (!pin) return;
|
||||
board().edgeConnectorState.pins.filter(p => !!p).forEach(p => p.pitch = false);
|
||||
pin.pitch = true;
|
||||
}
|
||||
|
||||
export function analogPitch(frequency: number, ms: number) {
|
||||
// update analog output
|
||||
let pins = board().edgeConnectorState.pins;
|
||||
let pin = pins.filter(pin => !!pin && pin.pitch)[0] || pins[0];
|
||||
pin.mode = PinFlags.Analog | PinFlags.Output;
|
||||
if (frequency <= 0) {
|
||||
pin.value = 0;
|
||||
pin.period = 0;
|
||||
} else {
|
||||
pin.value = 512;
|
||||
pin.period = 1000000 / frequency;
|
||||
}
|
||||
runtime.queueDisplayUpdate();
|
||||
|
||||
let cb = getResume();
|
||||
AudioContextManager.tone(frequency, 1);
|
||||
if (ms <= 0) cb();
|
||||
else {
|
||||
setTimeout(() => {
|
||||
AudioContextManager.stop();
|
||||
pin.value = 0;
|
||||
pin.period = 0;
|
||||
pin.mode = PinFlags.Unused;
|
||||
runtime.queueDisplayUpdate();
|
||||
cb()
|
||||
}, ms);
|
||||
}
|
||||
}
|
||||
}
|
357
sim/state/ledmatrix.ts
Normal file
@ -0,0 +1,357 @@
|
||||
namespace pxsim {
|
||||
export enum DisplayMode {
|
||||
bw,
|
||||
greyscale
|
||||
}
|
||||
|
||||
export class LedMatrixState {
|
||||
image = createInternalImage(5);
|
||||
brigthness = 255;
|
||||
displayMode = DisplayMode.bw;
|
||||
font: Image = createFont();
|
||||
|
||||
animationQ: AnimationQueue;
|
||||
|
||||
constructor(runtime: Runtime) {
|
||||
this.animationQ = new AnimationQueue(runtime);
|
||||
}
|
||||
}
|
||||
|
||||
export class Image extends RefObject {
|
||||
public static height: number = 5;
|
||||
public width: number;
|
||||
public data: number[];
|
||||
constructor(width: number, data: number[]) {
|
||||
super();
|
||||
this.width = width;
|
||||
this.data = data;
|
||||
}
|
||||
public print() {
|
||||
console.log(`Image id:${this.id} refs:${this.refcnt} size:${this.width}x${Image.height}`)
|
||||
}
|
||||
public get(x: number, y: number): number {
|
||||
if (x < 0 || x >= this.width || y < 0 || y >= 5) return 0;
|
||||
return this.data[y * this.width + x];
|
||||
}
|
||||
public set(x: number, y: number, v: number) {
|
||||
if (x < 0 || x >= this.width || y < 0 || y >= 5) return;
|
||||
this.data[y * this.width + x] = Math.max(0, Math.min(255, v));
|
||||
}
|
||||
public copyTo(xSrcIndex: number, length: number, target: Image, xTargetIndex: number): void {
|
||||
for (let x = 0; x < length; x++) {
|
||||
for (let y = 0; y < 5; y++) {
|
||||
let value = this.get(xSrcIndex + x, y);
|
||||
target.set(xTargetIndex + x, y, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
public shiftLeft(cols: number) {
|
||||
for (let x = 0; x < this.width; ++x)
|
||||
for (let y = 0; y < 5; ++y)
|
||||
this.set(x, y, x < this.width - cols ? this.get(x + cols, y) : 0);
|
||||
}
|
||||
|
||||
public shiftRight(cols: number) {
|
||||
for (let x = this.width - 1; x <= 0; --x)
|
||||
for (let y = 0; y < 5; ++y)
|
||||
this.set(x, y, x > cols ? this.get(x - cols, y) : 0);
|
||||
}
|
||||
|
||||
public clear(): void {
|
||||
for (let i = 0; i < this.data.length; ++i)
|
||||
this.data[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
export function createInternalImage(width: number): Image {
|
||||
let img = createImage(width)
|
||||
pxsim.noLeakTracking(img)
|
||||
return img
|
||||
}
|
||||
|
||||
export function createImage(width: number): Image {
|
||||
return new Image(width, new Array(width * 5));
|
||||
}
|
||||
|
||||
export function createImageFromBuffer(data: number[]): Image {
|
||||
return new Image(data.length / 5, data);
|
||||
}
|
||||
|
||||
export function createImageFromString(text: string): Image {
|
||||
let font = board().ledMatrixState.font;
|
||||
let w = font.width;
|
||||
let sprite = createInternalImage(6 * text.length - 1);
|
||||
let k = 0;
|
||||
for (let i = 0; i < text.length; i++) {
|
||||
let charCode = text.charCodeAt(i);
|
||||
let charStart = (charCode - 32) * 5;
|
||||
if (charStart < 0 || charStart + 5 > w) {
|
||||
charCode = " ".charCodeAt(0);
|
||||
charStart = (charCode - 32) * 5;
|
||||
}
|
||||
|
||||
font.copyTo(charStart, 5, sprite, k);
|
||||
k = k + 5;
|
||||
if (i < text.length - 1) {
|
||||
k = k + 1;
|
||||
}
|
||||
}
|
||||
return sprite;
|
||||
}
|
||||
|
||||
export function createFont(): Image {
|
||||
const data = [0x0, 0x0, 0x0, 0x0, 0x0, 0x8, 0x8, 0x8, 0x0, 0x8, 0xa, 0x4a, 0x40, 0x0, 0x0, 0xa, 0x5f, 0xea, 0x5f, 0xea, 0xe, 0xd9, 0x2e, 0xd3, 0x6e, 0x19, 0x32, 0x44, 0x89, 0x33, 0xc, 0x92, 0x4c, 0x92, 0x4d, 0x8, 0x8, 0x0, 0x0, 0x0, 0x4, 0x88, 0x8, 0x8, 0x4, 0x8, 0x4, 0x84, 0x84, 0x88, 0x0, 0xa, 0x44, 0x8a, 0x40, 0x0, 0x4, 0x8e, 0xc4, 0x80, 0x0, 0x0, 0x0, 0x4, 0x88, 0x0, 0x0, 0xe, 0xc0, 0x0, 0x0, 0x0, 0x0, 0x8, 0x0, 0x1, 0x22, 0x44, 0x88, 0x10, 0xc, 0x92, 0x52, 0x52, 0x4c, 0x4, 0x8c, 0x84, 0x84, 0x8e, 0x1c, 0x82, 0x4c, 0x90, 0x1e, 0x1e, 0xc2, 0x44, 0x92, 0x4c, 0x6, 0xca, 0x52, 0x5f, 0xe2, 0x1f, 0xf0, 0x1e, 0xc1, 0x3e, 0x2, 0x44, 0x8e, 0xd1, 0x2e, 0x1f, 0xe2, 0x44, 0x88, 0x10, 0xe, 0xd1, 0x2e, 0xd1, 0x2e, 0xe, 0xd1, 0x2e, 0xc4, 0x88, 0x0, 0x8, 0x0, 0x8, 0x0, 0x0, 0x4, 0x80, 0x4, 0x88, 0x2, 0x44, 0x88, 0x4, 0x82, 0x0, 0xe, 0xc0, 0xe, 0xc0, 0x8, 0x4, 0x82, 0x44, 0x88, 0xe, 0xd1, 0x26, 0xc0, 0x4, 0xe, 0xd1, 0x35, 0xb3, 0x6c, 0xc, 0x92, 0x5e, 0xd2, 0x52, 0x1c, 0x92, 0x5c, 0x92, 0x5c, 0xe, 0xd0, 0x10, 0x10, 0xe, 0x1c, 0x92, 0x52, 0x52, 0x5c, 0x1e, 0xd0, 0x1c, 0x90, 0x1e, 0x1e, 0xd0, 0x1c, 0x90, 0x10, 0xe, 0xd0, 0x13, 0x71, 0x2e, 0x12, 0x52, 0x5e, 0xd2, 0x52, 0x1c, 0x88, 0x8, 0x8, 0x1c, 0x1f, 0xe2, 0x42, 0x52, 0x4c, 0x12, 0x54, 0x98, 0x14, 0x92, 0x10, 0x10, 0x10, 0x10, 0x1e, 0x11, 0x3b, 0x75, 0xb1, 0x31, 0x11, 0x39, 0x35, 0xb3, 0x71, 0xc, 0x92, 0x52, 0x52, 0x4c, 0x1c, 0x92, 0x5c, 0x90, 0x10, 0xc, 0x92, 0x52, 0x4c, 0x86, 0x1c, 0x92, 0x5c, 0x92, 0x51, 0xe, 0xd0, 0xc, 0x82, 0x5c, 0x1f, 0xe4, 0x84, 0x84, 0x84, 0x12, 0x52, 0x52, 0x52, 0x4c, 0x11, 0x31, 0x31, 0x2a, 0x44, 0x11, 0x31, 0x35, 0xbb, 0x71, 0x12, 0x52, 0x4c, 0x92, 0x52, 0x11, 0x2a, 0x44, 0x84, 0x84, 0x1e, 0xc4, 0x88, 0x10, 0x1e, 0xe, 0xc8, 0x8, 0x8, 0xe, 0x10, 0x8, 0x4, 0x82, 0x41, 0xe, 0xc2, 0x42, 0x42, 0x4e, 0x4, 0x8a, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1f, 0x8, 0x4, 0x80, 0x0, 0x0, 0x0, 0xe, 0xd2, 0x52, 0x4f, 0x10, 0x10, 0x1c, 0x92, 0x5c, 0x0, 0xe, 0xd0, 0x10, 0xe, 0x2, 0x42, 0x4e, 0xd2, 0x4e, 0xc, 0x92, 0x5c, 0x90, 0xe, 0x6, 0xc8, 0x1c, 0x88, 0x8, 0xe, 0xd2, 0x4e, 0xc2, 0x4c, 0x10, 0x10, 0x1c, 0x92, 0x52, 0x8, 0x0, 0x8, 0x8, 0x8, 0x2, 0x40, 0x2, 0x42, 0x4c, 0x10, 0x14, 0x98, 0x14, 0x92, 0x8, 0x8, 0x8, 0x8, 0x6, 0x0, 0x1b, 0x75, 0xb1, 0x31, 0x0, 0x1c, 0x92, 0x52, 0x52, 0x0, 0xc, 0x92, 0x52, 0x4c, 0x0, 0x1c, 0x92, 0x5c, 0x90, 0x0, 0xe, 0xd2, 0x4e, 0xc2, 0x0, 0xe, 0xd0, 0x10, 0x10, 0x0, 0x6, 0xc8, 0x4, 0x98, 0x8, 0x8, 0xe, 0xc8, 0x7, 0x0, 0x12, 0x52, 0x52, 0x4f, 0x0, 0x11, 0x31, 0x2a, 0x44, 0x0, 0x11, 0x31, 0x35, 0xbb, 0x0, 0x12, 0x4c, 0x8c, 0x92, 0x0, 0x11, 0x2a, 0x44, 0x98, 0x0, 0x1e, 0xc4, 0x88, 0x1e, 0x6, 0xc4, 0x8c, 0x84, 0x86, 0x8, 0x8, 0x8, 0x8, 0x8, 0x18, 0x8, 0xc, 0x88, 0x18, 0x0, 0x0, 0xc, 0x83, 0x60];
|
||||
|
||||
let nb = data.length;
|
||||
let n = nb / 5;
|
||||
let font = createInternalImage(nb);
|
||||
for (let c = 0; c < n; c++) {
|
||||
for (let row = 0; row < 5; row++) {
|
||||
let char = data[c * 5 + row];
|
||||
for (let col = 0; col < 5; col++) {
|
||||
if ((char & (1 << col)) != 0)
|
||||
font.set((c * 5 + 4) - col, row, 255);
|
||||
}
|
||||
}
|
||||
}
|
||||
return font;
|
||||
}
|
||||
|
||||
export interface AnimationOptions {
|
||||
interval: number;
|
||||
// false means last frame
|
||||
frame: () => boolean;
|
||||
whenDone?: (cancelled: boolean) => void;
|
||||
}
|
||||
|
||||
export class AnimationQueue {
|
||||
private queue: AnimationOptions[] = [];
|
||||
private process: () => void;
|
||||
|
||||
constructor(private runtime: Runtime) {
|
||||
this.process = () => {
|
||||
let top = this.queue[0]
|
||||
if (!top) return
|
||||
if (this.runtime.dead) return
|
||||
runtime = this.runtime
|
||||
let res = top.frame()
|
||||
runtime.queueDisplayUpdate()
|
||||
runtime.maybeUpdateDisplay()
|
||||
if (res === false) {
|
||||
this.queue.shift();
|
||||
// if there is already something in the queue, start processing
|
||||
if (this.queue[0])
|
||||
setTimeout(this.process, this.queue[0].interval)
|
||||
// this may push additional stuff
|
||||
top.whenDone(false);
|
||||
} else {
|
||||
setTimeout(this.process, top.interval)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public cancelAll() {
|
||||
let q = this.queue
|
||||
this.queue = []
|
||||
for (let a of q) {
|
||||
a.whenDone(true)
|
||||
}
|
||||
}
|
||||
|
||||
public cancelCurrent() {
|
||||
let top = this.queue[0]
|
||||
if (top) {
|
||||
this.queue.shift();
|
||||
top.whenDone(true);
|
||||
}
|
||||
}
|
||||
|
||||
public enqueue(anim: AnimationOptions) {
|
||||
if (!anim.whenDone) anim.whenDone = () => { };
|
||||
this.queue.push(anim)
|
||||
// we start processing when the queue goes from 0 to 1
|
||||
if (this.queue.length == 1)
|
||||
this.process()
|
||||
}
|
||||
|
||||
public executeAsync(anim: AnimationOptions) {
|
||||
U.assert(!anim.whenDone)
|
||||
return new Promise<boolean>((resolve, reject) => {
|
||||
anim.whenDone = resolve
|
||||
this.enqueue(anim)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace pxsim.images {
|
||||
export function createImage(img: Image) {
|
||||
return img
|
||||
}
|
||||
export function createBigImage(img: Image) {
|
||||
return img
|
||||
}
|
||||
}
|
||||
|
||||
namespace pxsim.ImageMethods {
|
||||
export function showImage(leds: Image, offset: number) {
|
||||
if (!leds) panic(PanicCode.MICROBIT_NULL_DEREFERENCE);
|
||||
|
||||
leds.copyTo(offset, 5, board().ledMatrixState.image, 0)
|
||||
runtime.queueDisplayUpdate()
|
||||
}
|
||||
|
||||
export function plotImage(leds: Image, offset: number): void {
|
||||
if (!leds) panic(PanicCode.MICROBIT_NULL_DEREFERENCE);
|
||||
|
||||
leds.copyTo(offset, 5, board().ledMatrixState.image, 0)
|
||||
runtime.queueDisplayUpdate()
|
||||
}
|
||||
|
||||
export function height(leds: Image): number {
|
||||
if (!leds) panic(PanicCode.MICROBIT_NULL_DEREFERENCE);
|
||||
return Image.height;
|
||||
}
|
||||
|
||||
export function width(leds: Image): number {
|
||||
if (!leds) panic(PanicCode.MICROBIT_NULL_DEREFERENCE);
|
||||
return leds.width;
|
||||
}
|
||||
|
||||
export function plotFrame(leds: Image, frame: number) {
|
||||
ImageMethods.plotImage(leds, frame * Image.height);
|
||||
}
|
||||
|
||||
export function showFrame(leds: Image, frame: number) {
|
||||
ImageMethods.showImage(leds, frame * Image.height);
|
||||
}
|
||||
|
||||
export function pixel(leds: Image, x: number, y: number): number {
|
||||
if (!leds) panic(PanicCode.MICROBIT_NULL_DEREFERENCE);
|
||||
return leds.get(x, y);
|
||||
}
|
||||
|
||||
export function setPixel(leds: Image, x: number, y: number, v: number) {
|
||||
if (!leds) panic(PanicCode.MICROBIT_NULL_DEREFERENCE);
|
||||
leds.set(x, y, v);
|
||||
}
|
||||
|
||||
export function clear(leds: Image) {
|
||||
if (!leds) panic(PanicCode.MICROBIT_NULL_DEREFERENCE);
|
||||
|
||||
leds.clear();
|
||||
}
|
||||
|
||||
export function setPixelBrightness(i: Image, x: number, y: number, b: number) {
|
||||
if (!i) panic(PanicCode.MICROBIT_NULL_DEREFERENCE);
|
||||
|
||||
i.set(x, y, b);
|
||||
}
|
||||
|
||||
export function pixelBrightness(i: Image, x: number, y: number): number {
|
||||
if (!i) panic(PanicCode.MICROBIT_NULL_DEREFERENCE);
|
||||
|
||||
return i.get(x, y);
|
||||
}
|
||||
|
||||
export function scrollImage(leds: Image, stride: number, interval: number): void {
|
||||
if (!leds) panic(PanicCode.MICROBIT_NULL_DEREFERENCE);
|
||||
if (stride == 0) stride = 1;
|
||||
|
||||
let cb = getResume();
|
||||
let off = stride > 0 ? 0 : leds.width - 1;
|
||||
let display = board().ledMatrixState.image;
|
||||
|
||||
board().ledMatrixState.animationQ.enqueue({
|
||||
interval: interval,
|
||||
frame: () => {
|
||||
//TODO: support right to left.
|
||||
if (off >= leds.width || off < 0) return false;
|
||||
stride > 0 ? display.shiftLeft(stride) : display.shiftRight(-stride);
|
||||
let c = Math.min(stride, leds.width - off);
|
||||
leds.copyTo(off, c, display, 5 - stride)
|
||||
off += stride;
|
||||
return true;
|
||||
},
|
||||
whenDone: cb
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
namespace pxsim.basic {
|
||||
export function showNumber(x: number, interval: number) {
|
||||
if (interval < 0) return;
|
||||
|
||||
let leds = createImageFromString(x.toString());
|
||||
if (x < 0 || x >= 10) ImageMethods.scrollImage(leds, 1, interval);
|
||||
else showLeds(leds, interval * 5);
|
||||
}
|
||||
|
||||
export function showString(s: string, interval: number) {
|
||||
if (interval < 0) return;
|
||||
if (s.length == 0) {
|
||||
clearScreen();
|
||||
pause(interval * 5);
|
||||
} else {
|
||||
if (s.length == 1) showLeds(createImageFromString(s + " "), interval * 5)
|
||||
else ImageMethods.scrollImage(createImageFromString(s + " "), 1, interval);
|
||||
}
|
||||
}
|
||||
|
||||
export function showLeds(leds: Image, delay: number): void {
|
||||
showAnimation(leds, delay);
|
||||
}
|
||||
|
||||
export function clearScreen() {
|
||||
board().ledMatrixState.image.clear();
|
||||
runtime.queueDisplayUpdate()
|
||||
}
|
||||
|
||||
export function showAnimation(leds: Image, interval: number): void {
|
||||
ImageMethods.scrollImage(leds, 5, interval);
|
||||
}
|
||||
|
||||
export function plotLeds(leds: Image): void {
|
||||
ImageMethods.plotImage(leds, 0);
|
||||
}
|
||||
}
|
||||
|
||||
namespace pxsim.led {
|
||||
export function plot(x: number, y: number) {
|
||||
board().ledMatrixState.image.set(x, y, 255);
|
||||
runtime.queueDisplayUpdate()
|
||||
}
|
||||
|
||||
export function unplot(x: number, y: number) {
|
||||
board().ledMatrixState.image.set(x, y, 0);
|
||||
runtime.queueDisplayUpdate()
|
||||
}
|
||||
|
||||
export function point(x: number, y: number): boolean {
|
||||
return !!board().ledMatrixState.image.get(x, y);
|
||||
}
|
||||
|
||||
export function brightness(): number {
|
||||
return board().ledMatrixState.brigthness;
|
||||
}
|
||||
|
||||
export function setBrightness(value: number): void {
|
||||
board().ledMatrixState.brigthness = value;
|
||||
runtime.queueDisplayUpdate()
|
||||
}
|
||||
|
||||
export function stopAnimation(): void {
|
||||
board().ledMatrixState.animationQ.cancelAll();
|
||||
}
|
||||
|
||||
export function setDisplayMode(mode: DisplayMode): void {
|
||||
board().ledMatrixState.displayMode = mode;
|
||||
runtime.queueDisplayUpdate()
|
||||
}
|
||||
|
||||
export function screenshot(): Image {
|
||||
let img = createImage(5)
|
||||
board().ledMatrixState.image.copyTo(0, 5, img, 0);
|
||||
return img;
|
||||
}
|
||||
}
|