Compare commits

...

67 Commits

Author SHA1 Message Date
473950e491 0.3.64 2016-09-01 05:50:27 -07:00
fd33610511 Bump pxt-core to 0.3.74 2016-09-01 05:50:25 -07:00
47ea4e01d2 more docs cleanup 2016-09-01 05:28:27 -07:00
16199cfcea Merge branch 'master' of https://github.com/Microsoft/pxt-microbit 2016-09-01 05:11:58 -07:00
8a4556e70c Merge branch 'master' of https://github.com/Microsoft/pxt-microbit 2016-09-01 05:08:35 -07:00
bfc2641637 various docs cleanup 2016-09-01 05:08:27 -07:00
ef7fdc2ef7 Rename pxsim.pxt to pxsim.pxtcore 2016-09-01 13:06:03 +01:00
a8b427fd89 Merge branch 'master' of github.com:Microsoft/pxt-microbit 2016-09-01 12:13:54 +01:00
79c23e2e2a Bump pxt-microbit-core to 0.4.2 with class support 2016-09-01 12:13:40 +01:00
c11acce579 0.3.63 2016-08-31 22:30:24 -07:00
f79551073e Bump pxt-core to 0.3.72 2016-08-31 22:30:23 -07:00
c6c133ef9e Merge pull request #225 from Microsoft/generic-part
Adds support for generic parts; fixes generic boards to utilize BoardHost
2016-08-31 20:49:03 -07:00
817e86f5aa Merge branch 'master' into generic-part 2016-08-31 20:44:03 -07:00
19e33d029d removes dead code 2016-08-31 20:15:59 -07:00
e3671ca809 Merge branch 'arduino' into generic-part 2016-08-31 19:37:57 -07:00
1f3a2ab6fe adds generic parts to instructions parts list and requirements panels 2016-08-31 19:37:20 -07:00
dad3e89577 adds "extraColumnOffset" for generic parts 2016-08-31 19:28:28 -07:00
df9d3c4444 0.3.62 2016-08-31 19:22:39 -07:00
edc489c83d Merge pull request #223 from Microsoft/breadboarding
wires, breadboarding, hardware components and Arduino support
2016-08-31 19:17:23 -07:00
fb29af8011 Merge branch 'master' into breadboarding 2016-08-31 19:11:03 -07:00
516def7a3f 0.3.61 2016-08-31 19:10:31 -07:00
b5cb8deb93 Bump pxt-core to 0.3.71 2016-08-31 19:10:29 -07:00
658083b4eb removes all experimental boards 2016-08-31 19:03:11 -07:00
1441129355 adds support for generic components 2016-08-31 19:01:59 -07:00
2b87b26f00 adds support for arduino zero 2016-08-31 18:03:34 -07:00
741facc769 Merge branch 'master' into breadboarding 2016-08-31 16:47:40 -07:00
41a5bc72a1 0.3.60 2016-08-31 16:47:19 -07:00
9d5e93b879 Bump pxt-core to 0.3.69 2016-08-31 16:47:17 -07:00
32e0cb0fe8 Merge branch 'master' into breadboarding 2016-08-31 16:37:14 -07:00
a224259e74 0.3.59 2016-08-31 16:36:35 -07:00
c144f3a15d Bump pxt-core to 0.3.68 2016-08-31 16:36:34 -07:00
a3fa07463f Merge branch 'master' into breadboarding 2016-08-31 16:24:31 -07:00
aa20f2fa4d 0.3.58 2016-08-31 16:24:07 -07:00
4a1399de59 Bump pxt-core to 0.3.67 2016-08-31 16:24:06 -07:00
0b8e5c12c3 merging vnumber 2016-08-31 15:44:11 -07:00
445066776b 0.3.57 2016-08-31 15:42:18 -07:00
6cb204c548 Bump pxt-core to 0.3.66 2016-08-31 15:42:16 -07:00
6ed6a525fd 0.3.56 2016-08-31 15:41:46 -07:00
ba6dc9f136 Bump pxt-core to 0.3.66 2016-08-31 15:41:44 -07:00
ae17d4380e re-adds arduino board definitino 2016-08-31 14:55:58 -07:00
f79b726f87 tiny progress on generic part support 2016-08-31 14:48:44 -07:00
0f3c7b8c0f Merge branch 'breadboarding' of https://github.com/Microsoft/pxt-microbit into breadboarding 2016-08-31 14:07:16 -07:00
dac6f5af73 specifying packages needed to render blocks 2016-08-31 14:07:10 -07:00
99fb074952 adds more padding under breadboard 2016-08-31 14:03:25 -07:00
66ba26586f fixes wire highlighting in instructions 2016-08-31 13:59:20 -07:00
e27c4de108 micro:bit wireframe 2016-08-31 13:39:47 -07:00
f19a110953 Merge branch 'breadboarding' of github.com:Microsoft/pxt-microbit into breadboarding 2016-08-31 11:34:58 -07:00
626055d3eb fixes microbit board height issue 2016-08-31 11:34:49 -07:00
9085c98c7f merging TD_ID 2016-08-31 11:23:30 -07:00
85e3148f23 Merge branch 'breadboarding' of https://github.com/Microsoft/pxt-microbit into breadboarding 2016-08-31 11:22:38 -07:00
c2e37a2c6e refactoring instructions to work with boardHost 2016-08-31 11:14:16 -07:00
b4ad4819a5 adding additional built-in parts 2016-08-31 08:46:44 -07:00
d9f2c7cc42 don't show text cursor on labels 2016-08-31 05:31:09 -07:00
15638d2767 don't use 'let' in .html files 2016-08-31 04:55:26 -07:00
5ecd20583b Merge branch 'master' into breadboarding 2016-08-30 21:41:17 -07:00
4c27d62796 removing external boards 2016-08-30 21:15:27 -07:00
90da72a8de support for resizing simulator based on usage of parts 2016-08-30 17:18:03 -07:00
8f50beb938 adds new custom NeoPixel svg 2016-08-30 15:59:02 -07:00
52527dd584 fixes micro:bit + breadboard sim size issue 2016-08-30 15:33:57 -07:00
fc0faf5181 removes unused m:b params 2016-08-30 14:17:17 -07:00
cd9589e562 adds "boardhost" to handle composition of ...
... breadboard, board, wires, and definition allocation.
2016-08-30 14:13:44 -07:00
af7c51b954 renames state -> board 2016-08-30 12:42:30 -07:00
a65e71f3b1 moves all of pxt-arduino breadboarding here...
... see pxt-arduino history starting here: acd49bb795
2016-08-30 11:55:00 -07:00
89e899cc79 splits m:b simulator state 2016-08-30 11:51:32 -07:00
a984778dfd renames simsvg and libmbit 2016-08-30 11:27:29 -07:00
008cbf543f adds "speaker" parts annotation 2016-08-30 11:17:15 -07:00
61ee841431 adds arduino & speaker svgs 2016-08-30 11:15:24 -07:00
114 changed files with 6021 additions and 2628 deletions

View File

@ -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
![](/static/mb/crocodile-clips-0.png)
```blocks
input.onPinPressed(TouchPin.P0, () => {
basic.showNumber(Math.random(10))
})
```
### Connecting Crocodile Clips
![](/static/mb/crocodile-clips-2.jpg)
### 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)

View File

@ -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.
![](/static/mb/lessons/seismograph22.png)
## 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
View 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.
![](/static/mb/vscode.png)
## Native clients
There are no native clients available yet.

View File

@ -1,9 +0,0 @@
# String
```cards
String.fromCharCode(0);
```
### See Also
[fromCharCode](/reference//math/string-from-char-code)

View File

@ -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
![](/static/mb/show-leds-1.png)
You should see code similar to this:
```blocks
basic.showLeds(`
. . . . .
. # . # .
. . . . .
# . . . #
. # # # .
`)
```
### Creating an image

View File

@ -20,7 +20,11 @@ led.plot(2, 2)
led.toggleAll()
```
![](/static/mb/toggle-all-0.png)
```sim
basic.clearScreen()
led.plot(2, 2)
led.toggleAll()
```
### See also

View File

@ -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
![](/static/mb/offline-0.png)
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.
![](/static/mb/offline-1.png)
## 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:
![](/static/mb/offline-2.png)
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
View 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
View 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
View 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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 277 KiB

After

Width:  |  Height:  |  Size: 97 KiB

BIN
docs/static/mb/csv.png vendored

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.2 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 199 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 262 KiB

View File

@ -183,6 +183,8 @@ namespace pxt {
//%
RefRecord* mkRecord(int reflen, int totallen);
//%
RefRecord* mkClassInstance(int offset);
//%
void debugMemLeaks();
//%
int incr(uint32_t e);

View File

@ -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) {

View File

@ -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
![](/static/mb/antenna-0.png)
### 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)

View File

@ -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:
![](/static/mb/boolean-0.png)
The next three blocks represent the three Boolean (logic) operators:
![](/static/mb/boolean-1.png)
The next six blocks represent comparison operators that yield a Boolean value. Most comparisons you will do involve [numbers](/reference/types/number):
![](/static/mb/boolean-2.png)
### 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)

View File

@ -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:
![](/static/mb/antenna-0.png)
The block below retrieves (gets) the current value of a global variable:
![](/static/mb/data-0.png)
### 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)

View File

@ -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:
![](/static/mb/data-1.jpg)
* `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:
![](/static/mb/data-2.jpg)
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:
![](/static/mb/data-3.jpg)
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.
![](/static/mb/data-4.png)
* `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)

View File

@ -9,8 +9,6 @@ Repeat code a fixed number of times.
### Block Editor
![](/static/mb/events-0.png)
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:
```

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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
![](/static/mb/game-library/add-point-to-score-0.png)
## 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)

View File

@ -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
![](/static/mb/game-library/start-countdown-0.png)
## 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)

View File

@ -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

View File

@ -9,8 +9,6 @@ Conditionally run code depending on whether a [Boolean](/reference/types/boolean
### Block Editor
![](/static/mb/hourofcode-0.png)
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

View File

@ -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

View File

@ -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

View File

@ -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 programs 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.

View File

@ -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.

View File

@ -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.
![](/static/mb/lessons/bounce-image-0.png)
<br/>
```
basic.showAnimation(`
# . . . .
# . . . .
# . . . .
# . . . .
# . . . .
`, 400)
```
## 3. Write the code that will display this animation with two frames.
![](/static/mb/lessons/bounce-image-1.png)
<br/>
```
basic.showAnimation(`
# . . . . . # . . .
# . . . . . # . . .
# . . . . . # . . .
# . . . . . # . . .
# . . . . . # . . .
`, 400)
```
## 4. Write the code that will display this animation with three frames.
![](/static/mb/lessons/bounce-image-2.png)
<br/>
```
basic.showAnimation(`
# . . . . . # . . . . . # . .
# . . . . . # . . . . . # . .
# . . . . . # . . . . . # . .
# . . . . . # . . . . . # . .
# . . . . . # . . . . . # . .
`, 400)
```
<br/>

View File

@ -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.
![](/static/mb/lessons/bounce-image-0.png)
<br/>
## 3. Write the code that will display this animation with two frames.
![](/static/mb/lessons/bounce-image-1.png)
<br/>
<br/>
## 4. Write the code that will display this animation with three frames.
![](/static/mb/lessons/bounce-image-2.png)
<br/>
<br/>

View File

@ -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:
![](/static/mb/simulator-0.png)
In the picture above, [plot image](/reference/led/plot-image) create a heart image that appears on the BBC micro:bit simulator.

View File

@ -17,7 +17,6 @@ let condition = false
### Block Editor
![](/static/mb/string-0.png)
### Touch Develop

View File

@ -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"
}
}

View File

@ -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
View 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
View 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
View 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;
}

View 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);
}
}

View File

@ -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
})
}
}

View File

@ -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);
})
}
}

View 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
View 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;
}
}

View File

@ -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
View 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
View 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
View 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
View 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
View 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;
}
}

Some files were not shown because too many files have changed in this diff Show More