Compare commits

...

84 Commits

Author SHA1 Message Date
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
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
98bc8d2a27 0.3.56 2016-08-31 15:20:34 -07:00
58d0e238e3 Bump pxt-core to 0.3.65 2016-08-31 15:20:33 -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
60f8dd8228 annotating note with TD_ID 2016-08-31 11:22:07 -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
a34bcee7a2 added simulator 2016-08-30 13:39:31 -07:00
d011cdb37e updated cli / vscode docs 2016-08-30 13:34:05 -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
b37e823b57 0.3.55 2016-08-30 11:35:31 -07:00
8cb31daa48 Adding CLI pointer 2016-08-30 11:35:16 -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
e856d59235 0.3.54 2016-08-30 12:50:18 +02:00
32753d3395 Bump pxt-core to 0.3.61 2016-08-30 12:50:18 +02:00
7b11a04727 Upgrade pxt-microbit-core to 0.4.1 2016-08-30 12:48:58 +02:00
6d6c053e4f 0.3.53 2016-08-29 23:25:12 -07:00
1008fdd371 Bump pxt-core to 0.3.60 2016-08-29 23:25:11 -07:00
ddc2b7437b re-ordering of blocks in docs 2016-08-29 14:49:51 -07:00
b9c95ebb1e 0.3.52 2016-08-29 13:26:01 -07:00
b16d02ec66 Bump pxt-core to 0.3.59 2016-08-29 13:25:58 -07:00
38fb36087a fixing local deployment (pxtc import issue) 2016-08-26 17:51:15 -07:00
5dbbc2266f 0.3.51 2016-08-26 12:17:42 -07:00
2a22001e02 0.3.50 2016-08-26 12:12:32 -07:00
214ff25995 Merge branch 'master' of https://github.com/Microsoft/pxt-microbit 2016-08-26 12:01:14 -07:00
f4768d99ff Merge pull request #220 from Microsoft/parts
adds "parts" annotations
2016-08-26 12:00:59 -07:00
8792f9fe36 0.3.49 2016-08-26 11:56:22 -07:00
3d45bef910 Bump pxt-core to 0.3.56 2016-08-26 11:56:20 -07:00
3de6b33163 added README.md in default blocks target 2016-08-26 09:58:13 -07:00
3f1c03ea51 added search / watch excludes 2016-08-26 09:31:06 -07:00
a4063d636d merging changes 2016-08-26 08:36:49 -07:00
92b5b76171 Add target-specific favicon; see https://github.com/Microsoft/pxt/issues/54 2016-08-26 16:35:13 +02:00
51d285b0b0 Set target-specific avatar; fixes https://github.com/Microsoft/pxt/issues/97 2016-08-26 16:15:58 +02:00
4d8afdd3ae Format 2016-08-26 16:12:22 +02:00
13b21ad275 0.3.48 2016-08-26 15:24:48 +02:00
d90a43a6d8 Bump pxt-core to 0.3.55 2016-08-26 15:24:48 +02:00
6fd14e718d Use the new pxtc namespace 2016-08-26 15:19:04 +02:00
c79f043529 0.3.47 2016-08-26 13:18:35 +02:00
3a676c7151 Bump pxt-core to 0.3.53 2016-08-26 13:18:34 +02:00
0023710209 String/ptr -> boolean helpers added 2016-08-26 13:14:48 +02:00
0756091e8c Remove st/ldglb (no longer used) 2016-08-26 11:09:18 +02:00
8144df0023 0.3.47 2016-08-25 22:19:12 -07:00
b2137e2622 Bump pxt-core to 0.3.52 2016-08-25 22:19:11 -07:00
8b46698f50 adds "parts" annotations...
...to libs/microbit and libs/microbit-bluetooth
2016-08-22 08:48:48 -07:00
54 changed files with 6449 additions and 1846 deletions

15
.vscode/settings.json vendored
View File

@ -1,5 +1,20 @@
// Place your settings in this file to overwrite default and user settings.
{
"file.autoSave": "afterDelay",
"files.watcherExclude": {
"**/.git/objects/**": true,
"**/built/**": true,
"**/node_modules/**": true,
"**/yotta_modules/**": true,
"**/yotta_targets": true,
"**/pxt_modules/**": true
},
"search.exclude": {
"**/node_modules": true,
"**/yotta_modules/**": true,
"**/yotta_targets": true,
"**/pxt_modules/**": true
},
"tslint.enable": true,
"tslint.rulesDirectory": "node_modules/tslint-microsoft-contrib"
}

View File

@ -9,16 +9,16 @@ let execAsync: (cmd: string, options?: { cwd?: string }) => Promise<Buffer> = Pr
let readDirAsync = Promise.promisify(fs.readdir)
export function deployCoreAsync(res: ts.pxt.CompileResult) {
export function deployCoreAsync(res: ts.pxtc.CompileResult) {
return getBitDrivesAsync()
.then(drives => {
if (drives.length == 0) {
console.log("cannot find any drives to deploy to")
} else {
console.log(`copy ${ts.pxt.BINARY_HEX} to ` + drives.join(", "))
console.log(`copy ${ts.pxtc.BINARY_HEX} to ` + drives.join(", "))
}
return Promise.map(drives, d =>
writeFileAsync(d + ts.pxt.BINARY_HEX, res.outfiles[ts.pxt.BINARY_HEX])
writeFileAsync(d + ts.pxtc.BINARY_HEX, res.outfiles[ts.pxtc.BINARY_HEX])
.then(() => {
console.log("wrote hex file to " + d)
}))

17
docs/cli.md Normal file
View File

@ -0,0 +1,17 @@
# Command Line Interface
```sim
basic.forever(() => {
basic.showString("CLI<3")
})
```
It is possible to use the codethemicrobit tools from a command line interface (CLI). The PXT CLI allows to
* edit, compile or deploy JavaScript programs
* can easily be integrated in most IDEs. It comes with built-in support for [Visual Studio Code](/code)!
* run a local web server for the web editor
* author packages using JavaScript and/or C++
Using the CLI assumes that you have some experience with programming and will require to install tools on your machine as well.
* **[LET'S GET STARTED](https://pxt.io/cli)**

View File

@ -1,54 +1,16 @@
# Visual Studio Code
Visual Studio Code is a Free Open Source code editor that you can use to edit your programs.
[Visual Studio Code](https://code.visualstudio.com) is a Free Open Source code editor that you can use to edit your programs.
Working from Visual Studio code allows you to benefit from all the features
of a professional IDE while working with PXT: working with files,
git integration (or source control of your choice), hundreds of extensions.
![](https://code.visualstudio.com/home/home-screenshot-win-lg.png)
* background compilation
* auto-completion
* pxt command line integration
## Setup
**Follow [these instructions](https://pxt.io/cli)** to setup your machine and edit your programs in Visual Studio Code.
Follow these instructions to setup your machine and edit your programs in Visual Studio Code.
![](/static/mb/vscode.png)
* install [Visual Studio Code](https://code.visualstudio.com/)
* install [Node.JS](https://nodejs.org/en/)
* install the PXT Tools (on Mac or Linux, you might have to add ``sudo`` to the command).
```
pxt install -g pxt
```
* create a folder for your projects
```
mkdir microbit
```
* install the microbit target
```
pxt target microbit
```
That's it! You are ready to create new projects in code or open existing projects.
## Creating a new project
Open a shell to your ``microbit`` folder.
```
# create a new subfolder for your project
mkdir myproject
cd myproject
# start the project set
pxt init
# open code
code .
```
## Opening an existing project
You can extract a project from the embedded URL or .hex file. Open a shell to your projects folder
```
# extract the project from the URL
pxt extract EMBEDURL
```
where ``EMBEDURL`` is the published project URL.

View File

@ -26,4 +26,5 @@
### Developers
* [Command Line Interface](/cli)
* Learn about [packages](/packages)

BIN
docs/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -6,6 +6,7 @@ Control of the LED screen.
led.plot(0, 0);
led.unplot(0, 0);
led.point(0, 0);
led.toggle(0, 0);
led.brightness();
led.setBrightness(255);
led.stopAnimation();
@ -14,7 +15,6 @@ led.fadeIn();
led.fadeOut();
led.plotAll();
led.screenshot();
led.toggle(0, 0);
led.toggleAll();
led.setDisplayMode(DisplayMode.BackAndWhite);
```

4
docs/static/hardware/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
# don't check in until OSS request is approved
neopixel-black-60-vert.svg
sparkfun-*
raspberrypi-*

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

BIN
docs/static/mb/vscode.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

View File

@ -1,21 +1,28 @@
<meta name="twitter:card" content="summary" />
<meta name="twitter:site" content="@codethemicrobit" />
<meta name="twitter:title" content="@name@" />
<meta name="twitter:description" content="@description@" />
<meta name="twitter:image" content="@cardLogo@" />
<meta name="twitter:card" content="summary" />
<meta name="twitter:site" content="@codethemicrobit" />
<meta name="twitter:title" content="@name@" />
<meta name="twitter:description" content="@description@" />
<meta name="twitter:image" content="@cardLogo@" />
<meta property="og:title" content="@name@" />
<meta property="og:site_name" content="code the micro:bit" />
<meta property="og:description" content="@description@" />
<meta property="og:image" content="@cardLogo@" />
<!--
<meta property="og:title" content="@name@" />
<meta property="og:site_name" content="code the micro:bit" />
<meta property="og:description" content="@description@" />
<meta property="og:image" content="@cardLogo@" />
<!--
<meta property="og:type" content="website" />
<meta property="fb:app_id" content="" />
-->
<link rel="apple-touch-icon" href="@appLogo@">
<link rel="icon" type="image/png" href="@appLogo@">
<link rel="mask-icon" href="https://az851932.vo.msecnd.net/pub/zwxazere/safari-pinned-tab.svg" color="#000000">
<link rel="shortcut icon" href="@appLogo@">
<meta name="theme-color" content="@accentColor@">
<link rel="apple-touch-icon" href="@appLogo@">
<link rel="icon" type="image/png" href="@appLogo@">
<link rel="mask-icon" href="https://az851932.vo.msecnd.net/pub/zwxazere/safari-pinned-tab.svg" color="#000000">
<link rel="shortcut icon" href="@appLogo@">
<meta name="theme-color" content="@accentColor@">
<style>
#root .avatar .avatar-image {
background-image: url(https://az851932.vo.msecnd.net/pub/jovrytni/microbit.simplified.svg);
background-size: contain;
background-repeat: no-repeat;
}
</style>

View File

@ -32,6 +32,7 @@ namespace bluetooth {
*/
//% help=bluetooth/start-io-pin-service
//% blockId=bluetooth_start_io_pin_service block="bluetooth io pin service" blockGap=8
//% parts="bluetooth"
void startIOPinService() {
new MicroBitIOPinService(*uBit.ble, uBit.io);
}
@ -41,6 +42,7 @@ namespace bluetooth {
*/
//% help=bluetooth/start-led-service
//% blockId=bluetooth_start_led_service block="bluetooth led service" blockGap=8
//% parts="bluetooth"
void startLEDService() {
new MicroBitLEDService(*uBit.ble, uBit.display);
}
@ -50,6 +52,7 @@ namespace bluetooth {
*/
//% help=bluetooth/start-temperature-service
//% blockId=bluetooth_start_temperature_service block="bluetooth temperature service" blockGap=8
//% parts="bluetooth"
void startTemperatureService() {
new MicroBitTemperatureService(*uBit.ble, uBit.thermometer);
}
@ -59,6 +62,7 @@ namespace bluetooth {
*/
//% help=bluetooth/start-magnetometer-service
//% blockId=bluetooth_start_magnetometer_service block="bluetooth magnetometer service" blockGap=8
//% parts="bluetooth"
void startMagnetometerService() {
new MicroBitMagnetometerService(*uBit.ble, uBit.compass);
}
@ -68,6 +72,7 @@ namespace bluetooth {
*/
//% help=bluetooth/start-accelerometer-service
//% blockId=bluetooth_start_accelerometer_service block="bluetooth accelerometer service" blockGap=8
//% parts="bluetooth"
void startAccelerometerService() {
new MicroBitAccelerometerService(*uBit.ble, uBit.accelerometer);
}
@ -77,6 +82,7 @@ namespace bluetooth {
*/
//% help=bluetooth/start-button-service
//% blockId=bluetooth_start_button_service block="bluetooth button service" blockGap=8
//% parts="bluetooth"
void startButtonService() {
new MicroBitButtonService(*uBit.ble);
}
@ -86,6 +92,7 @@ namespace bluetooth {
*/
//% help=bluetooth/start-uart-service
//% blockId=bluetooth_start_uart_service block="bluetooth uart service" blockGap=8
//% parts="bluetooth"
void startUartService() {
if (uart) return;
// 61 octet buffer size is 3 x (MTU - 3) + 1
@ -99,6 +106,7 @@ namespace bluetooth {
*/
//% help=bluetooth/uart-write
//% blockId=bluetooth_uart_write block="bluetooth uart write %data" blockGap=8
//% parts="bluetooth"
void uartWrite(StringData *data) {
startUartService();
uart->send(ManagedString(data));
@ -109,6 +117,7 @@ namespace bluetooth {
*/
//% help=bluetooth/uart-read
//% blockId=bluetooth_uart_read block="bluetooth uart read %del=bluetooth_uart_delimiter_conv" blockGap=8
//% parts="bluetooth"
StringData* uartRead(StringData *del) {
startUartService();
return uart->readUntil(ManagedString(del)).leakData();
@ -119,6 +128,7 @@ namespace bluetooth {
*/
//% blockId="bluetooth_uart_delimiter_conv" block="%del"
//% weight=1
//% parts="bluetooth"
StringData* delimiters(Delimiters del) {
ManagedString c("\n\n,$:.#"[max(0, min(6, (int)del))]);
return c.leakData();
@ -129,6 +139,7 @@ namespace bluetooth {
*/
//% help=bluetooth/on-bluetooth-connected weight=20
//% blockId=bluetooth_on_connected block="on bluetooth connected" blockGap=8
//% parts="bluetooth"
void onBluetoothConnected(Action body) {
registerWithDal(MICROBIT_ID_BLE, MICROBIT_BLE_EVT_CONNECTED, body);
}
@ -139,6 +150,7 @@ namespace bluetooth {
*/
//% help=bluetooth/on-bluetooth-disconnected weight=19
//% blockId=bluetooth_on_disconnected block="on bluetooth disconnected"
//% parts="bluetooth"
void onBluetoothDisconnected(Action body) {
registerWithDal(MICROBIT_ID_BLE, MICROBIT_BLE_EVT_DISCONNECTED, body);
}

View File

@ -11,70 +11,80 @@ declare namespace bluetooth {
* Starts the Bluetooth IO pin service.
*/
//% help=bluetooth/start-io-pin-service
//% blockId=bluetooth_start_io_pin_service block="bluetooth io pin service" blockGap=8 shim=bluetooth::startIOPinService
//% blockId=bluetooth_start_io_pin_service block="bluetooth io pin service" blockGap=8
//% parts="bluetooth" shim=bluetooth::startIOPinService
function startIOPinService(): void;
/**
* Starts the Bluetooth LED service
*/
//% help=bluetooth/start-led-service
//% blockId=bluetooth_start_led_service block="bluetooth led service" blockGap=8 shim=bluetooth::startLEDService
//% blockId=bluetooth_start_led_service block="bluetooth led service" blockGap=8
//% parts="bluetooth" shim=bluetooth::startLEDService
function startLEDService(): void;
/**
* Starts the Bluetooth temperature service
*/
//% help=bluetooth/start-temperature-service
//% blockId=bluetooth_start_temperature_service block="bluetooth temperature service" blockGap=8 shim=bluetooth::startTemperatureService
//% blockId=bluetooth_start_temperature_service block="bluetooth temperature service" blockGap=8
//% parts="bluetooth" shim=bluetooth::startTemperatureService
function startTemperatureService(): void;
/**
* Starts the Bluetooth magnetometer service
*/
//% help=bluetooth/start-magnetometer-service
//% blockId=bluetooth_start_magnetometer_service block="bluetooth magnetometer service" blockGap=8 shim=bluetooth::startMagnetometerService
//% blockId=bluetooth_start_magnetometer_service block="bluetooth magnetometer service" blockGap=8
//% parts="bluetooth" shim=bluetooth::startMagnetometerService
function startMagnetometerService(): void;
/**
* Starts the Bluetooth accelerometer service
*/
//% help=bluetooth/start-accelerometer-service
//% blockId=bluetooth_start_accelerometer_service block="bluetooth accelerometer service" blockGap=8 shim=bluetooth::startAccelerometerService
//% blockId=bluetooth_start_accelerometer_service block="bluetooth accelerometer service" blockGap=8
//% parts="bluetooth" shim=bluetooth::startAccelerometerService
function startAccelerometerService(): void;
/**
* Starts the Bluetooth button service
*/
//% help=bluetooth/start-button-service
//% blockId=bluetooth_start_button_service block="bluetooth button service" blockGap=8 shim=bluetooth::startButtonService
//% blockId=bluetooth_start_button_service block="bluetooth button service" blockGap=8
//% parts="bluetooth" shim=bluetooth::startButtonService
function startButtonService(): void;
/**
* Starts the Bluetooth UART service
*/
//% help=bluetooth/start-uart-service
//% blockId=bluetooth_start_uart_service block="bluetooth uart service" blockGap=8 shim=bluetooth::startUartService
//% blockId=bluetooth_start_uart_service block="bluetooth uart service" blockGap=8
//% parts="bluetooth" shim=bluetooth::startUartService
function startUartService(): void;
/**
* Writes to the Bluetooth UART service buffer. From there the data is transmitted over Bluetooth to a connected device.
*/
//% help=bluetooth/uart-write
//% blockId=bluetooth_uart_write block="bluetooth uart write %data" blockGap=8 shim=bluetooth::uartWrite
//% blockId=bluetooth_uart_write block="bluetooth uart write %data" blockGap=8
//% parts="bluetooth" shim=bluetooth::uartWrite
function uartWrite(data: string): void;
/**
* Reads from the Bluetooth UART service buffer, returning its contents when the specified delimiter character is encountered.
*/
//% help=bluetooth/uart-read
//% blockId=bluetooth_uart_read block="bluetooth uart read %del=bluetooth_uart_delimiter_conv" blockGap=8 shim=bluetooth::uartRead
//% blockId=bluetooth_uart_read block="bluetooth uart read %del=bluetooth_uart_delimiter_conv" blockGap=8
//% parts="bluetooth" shim=bluetooth::uartRead
function uartRead(del: string): string;
/**
* Returns the delimiter corresponding string
*/
//% blockId="bluetooth_uart_delimiter_conv" block="%del"
//% weight=1 shim=bluetooth::delimiters
//% weight=1
//% parts="bluetooth" shim=bluetooth::delimiters
function delimiters(del: Delimiters): string;
/**
@ -82,7 +92,8 @@ declare namespace bluetooth {
* @param body Code to run when a Bluetooth connection is established
*/
//% help=bluetooth/on-bluetooth-connected weight=20
//% blockId=bluetooth_on_connected block="on bluetooth connected" blockGap=8 shim=bluetooth::onBluetoothConnected
//% blockId=bluetooth_on_connected block="on bluetooth connected" blockGap=8
//% parts="bluetooth" shim=bluetooth::onBluetoothConnected
function onBluetoothConnected(body: () => void): void;
/**
@ -90,7 +101,8 @@ declare namespace bluetooth {
* @param body Code to run when a Bluetooth connection is lost
*/
//% help=bluetooth/on-bluetooth-disconnected weight=19
//% blockId=bluetooth_on_disconnected block="on bluetooth disconnected" shim=bluetooth::onBluetoothDisconnected
//% blockId=bluetooth_on_disconnected block="on bluetooth disconnected"
//% parts="bluetooth" shim=bluetooth::onBluetoothDisconnected
function onBluetoothDisconnected(body: () => void): void;
}

View File

@ -15,6 +15,7 @@ namespace basic {
//% weight=96
//% blockId=device_show_number block="show|number %number" blockGap=8 icon="\uf1ec"
//% async
//% parts="ledmatrix"
void showNumber(int value, int interval = 150) {
if (interval < 0)
return;
@ -37,6 +38,7 @@ namespace basic {
//% imageLiteral=1 async
//% blockId=device_show_leds
//% block="show leds" icon="\uf00a"
//% parts="ledmatrix"
void showLeds(ImageLiteral leds, int interval = 400) {
uBit.display.print(MicroBitImage(imageBytes(leds)), 0, 0, 0, interval);
}
@ -51,6 +53,7 @@ namespace basic {
//% block="show|string %text" icon="\uf031"
//% async
//% blockId=device_print_message
//% parts="ledmatrix"
void showString(StringData *text, int interval = 150) {
if (interval < 0)
return;
@ -71,6 +74,7 @@ namespace basic {
*/
//% help=basic/clear-screen weight=79
//% blockId=device_clear_display block="clear screen" icon="\uf12d"
//% parts="ledmatrix"
void clearScreen() {
uBit.display.image.clear();
}
@ -81,6 +85,7 @@ namespace basic {
* @param interval time in milliseconds between each redraw
*/
//% help=basic/show-animation imageLiteral=1 async
//% parts="ledmatrix"
void showAnimation(ImageLiteral leds, int interval = 400) {
uBit.display.animate(MicroBitImage(imageBytes(leds)), interval, 5, 0);
}
@ -90,6 +95,7 @@ namespace basic {
* @param leds pattern of LEDs to turn on/off
*/
//% help=basic/plot-leds weight=80
//% parts="ledmatrix"
void plotLeds(ImageLiteral leds) {
MicroBitImage i(imageBytes(leds));
uBit.display.print(i, 0, 0, 0, 0);

View File

@ -196,6 +196,8 @@ namespace pxt {
int programHash();
//%
void *ptrOfLiteral(int offset);
//%
int getNumGlobals();
}
namespace pxtrt {
@ -261,36 +263,6 @@ namespace pxtrt {
r->unref();
}
//%
uint32_t ldglb(int idx) {
check(0 <= idx && idx < numGlobals, ERR_OUT_OF_BOUNDS, 7);
return globals[idx];
}
//%
uint32_t ldglbRef(int idx) {
check(0 <= idx && idx < numGlobals, ERR_OUT_OF_BOUNDS, 7);
uint32_t tmp = globals[idx];
incr(tmp);
return tmp;
}
// note the idx comes last - it's more convenient that way in the emitter
//%
void stglb(uint32_t v, int idx)
{
check(0 <= idx && idx < numGlobals, ERR_OUT_OF_BOUNDS, 7);
globals[idx] = v;
}
//%
void stglbRef(uint32_t v, int idx)
{
check(0 <= idx && idx < numGlobals, ERR_OUT_OF_BOUNDS, 7);
decr(globals[idx]);
globals[idx] = v;
}
// Store a captured local in a closure. It returns the action, so it can be chained.
//%
RefAction *stclo(RefAction *a, int idx, uint32_t v)
@ -306,15 +278,38 @@ namespace pxtrt {
microbit_panic(code);
}
//%
int stringToBool(StringData *s) {
if (s == NULL) return 0;
if (s->len == 0) {
s->decr();
return 0;
}
s->decr();
return 1;
}
//%
StringData* emptyToNull(StringData *s) {
if (!s || s->len == 0)
return NULL;
return s;
}
//%
int ptrToBool(uint32_t p) {
if (p) {
decr(p);
return 1;
} else {
return 0;
}
}
//
// Debugger
//
//%
uint32_t getNumGlobals() {
return numGlobals;
}
//%
void* getGlobalsPtr() {
return globals;

View File

@ -41,6 +41,7 @@ namespace game {
*/
//% weight=60
//% blockId=game_create_sprite block="create sprite at|x: %x|y: %y"
//% parts="ledmatrix"
export function createSprite(x: number, y: number): LedSprite {
init();
let p = new LedSprite(x, y);
@ -64,6 +65,7 @@ namespace game {
*/
//% weight=10 help=game/add-score
//% blockId=game_add_score block="change score by|%points" blockGap=8
//% parts="ledmatrix"
export function addScore(points: number): void {
setScore(_score + points);
control.inBackground(() => {
@ -82,6 +84,7 @@ namespace game {
*/
//% weight=9 help=game/start-countdown
//% blockId=game_start_countdown block="start countdown|(ms) %duration" blockGap=8
//% parts="ledmatrix"
export function startCountdown(ms: number): void {
if (checkStart()) {
basic.showAnimation(`1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0
@ -104,6 +107,7 @@ namespace game {
*/
//% weight=8 help=game/game-over
//% blockId=game_game_over block="game over"
//% parts="ledmatrix"
export function gameOver(): void {
if (!_isGameOver) {
_isGameOver = true;
@ -194,6 +198,7 @@ namespace game {
* @param life TODO
*/
//% weight=10
//% parts="ledmatrix"
export function removeLife(life: number): void {
setLife(_life - life);
control.inBackground(() => {
@ -210,6 +215,7 @@ namespace game {
* Increments the level and display a message.
*/
//% weight=10
//% parts="ledmatrix"
export function levelUp(): void {
_level = _level + 1;
basic.showString("LEVEL:", 150);
@ -246,6 +252,7 @@ namespace game {
* Displays the score on the screen.
*/
//% weight=60
//% parts="ledmatrix"
export function showScore(): void {
basic.showString(" SCORE ", 100);
basic.showNumber(_score, 150);
@ -303,6 +310,7 @@ namespace game {
*/
//% weight=50
//% blockId=game_move_sprite block="%sprite|move by %leds" blockGap=8
//% parts="ledmatrix"
public move(leds: number): void {
if (this._dir == 0) {
this._y = this._y - leds;
@ -336,6 +344,7 @@ namespace game {
* @param x TODO
* @param y TODO
*/
//% parts="ledmatrix"
public goTo(x: number, y: number): void {
this._x = x;
this._y = y;
@ -350,6 +359,7 @@ namespace game {
*/
//% weight=18
//% blockId=game_sprite_bounce block="%sprite|if on edge, bounce"
//% parts="ledmatrix"
public ifOnEdgeBounce(): void {
if (this._dir == 0 && this._y == 0) {
this._dir = 180;
@ -484,6 +494,7 @@ namespace game {
* @param this TODO
* @param degrees TODO
*/
//% parts="ledmatrix"
public setDirection(degrees: number): void {
this._dir = ((degrees / 45) % 8) * 45;
if (this._dir <= -180) {
@ -596,6 +607,7 @@ namespace game {
* @param this TODO
* @param brightness TODO
*/
//% parts="ledmatrix"
public setBrightness(brightness: number): void {
this._brightness = Math.clamp(0, 255, brightness);
plot();
@ -664,6 +676,7 @@ namespace game {
}
//% weight=-1
//% parts="ledmatrix"
public _plot(now: number) {
let ps = this
if (ps._brightness > 0) {
@ -701,6 +714,7 @@ namespace game {
/**
* Plots the current sprites on the screen
*/
//% parts="ledmatrix"
function plot(): void {
if (game.isGameOver()) {
return;

View File

@ -10,6 +10,7 @@ namespace images {
*/
//% weight=75 help=images/create-image
//% blockId=device_build_image block="create image"
//% parts="ledmatrix"
Image createImage(ImageLiteral leds) {
return MicroBitImage(imageBytes(leds)).clone().leakData();
}
@ -19,6 +20,7 @@ namespace images {
*/
//% weight=74 help=images/create-big-image
//% blockId=device_build_big_image block="create big image" imageLiteral=2
//% parts="ledmatrix"
Image createBigImage(ImageLiteral leds) {
return createImage(leds);
}
@ -29,6 +31,7 @@ namespace ImageMethods {
* Plots the image at a given column to the screen
*/
//% help=images/plot-image
//% parts="ledmatrix"
void plotImage(Image i, int xOffset = 0) {
uBit.display.print(MicroBitImage(i), -xOffset, 0, 0, 0);
}
@ -39,6 +42,7 @@ namespace ImageMethods {
*/
//% help=images/show-image weight=80 blockNamespace=images
//% blockId=device_show_image_offset block="show image %sprite|at offset %offset" blockGap=8
//% parts="ledmatrix"
void showImage(Image sprite, int xOffset) {
uBit.display.print(MicroBitImage(sprite), -xOffset, 0, 0);
}
@ -48,6 +52,7 @@ namespace ImageMethods {
* @param xOffset column index to start displaying the image
*/
//% help=images/plot-frame weight=80
//% parts="ledmatrix"
void plotFrame(Image i, int xOffset) {
// TODO showImage() used in original implementation
plotImage(i, xOffset * 5);
@ -60,6 +65,7 @@ namespace ImageMethods {
*/
//% help=images/show-image weight=79 async blockNamespace=images
//% blockId=device_scroll_image block="scroll image %sprite|with offset %frameoffset|and interval (ms) %delay" blockGap=8
//% parts="ledmatrix"
void scrollImage(Image id, int frameOffset, int interval) {
MicroBitImage i(id);
if (i.getWidth() <= 5)
@ -73,6 +79,7 @@ namespace ImageMethods {
* Sets all pixels off.
*/
//% help=images/clear
//% parts="ledmatrix"
void clear(Image i) {
MicroBitImage(i).clear();
}
@ -81,6 +88,7 @@ namespace ImageMethods {
* Sets a specific pixel brightness at a given position
*/
//%
//% parts="ledmatrix"
void setPixelBrightness(Image i, int x, int y, int value) {
MicroBitImage(i).setPixelValue(x, y, value);
}
@ -90,6 +98,7 @@ namespace ImageMethods {
* Gets the pixel brightness ([0..255]) at a given position
*/
//%
//% parts="ledmatrix"
int pixelBrightness(Image i, int x, int y) {
int pix = MicroBitImage(i).getPixelValue(x, y);
if (pix < 0) return 0;
@ -120,6 +129,7 @@ namespace ImageMethods {
* @param value TODO
*/
//% help=images/set-pixel
//% parts="ledmatrix"
void setPixel(Image i, int x, int y, bool value) {
setPixelBrightness(i, x, y, value ? 255 : 0);
}
@ -130,6 +140,7 @@ namespace ImageMethods {
* @param y TODO
*/
//% help=images/pixel
//% parts="ledmatrix"
bool pixel(Image i, int x, int y) {
return pixelBrightness(i, x, y) > 0;
}
@ -140,6 +151,7 @@ namespace ImageMethods {
* @param frame TODO
*/
//% weight=70 help=images/show-frame
//% parts="ledmatrix"
void showFrame(Image i, int frame) {
showImage(i, frame * 5);
}

View File

@ -116,6 +116,7 @@ namespace input {
*/
//% help=input/on-button-pressed weight=85 blockGap=8
//% blockId=device_button_event block="on button|%NAME|pressed" icon="\uf192"
//% parts="buttonpair"
void onButtonPressed(Button button, Action body) {
registerWithDal((int)button, MICROBIT_BUTTON_EVT_CLICK, body);
}
@ -126,6 +127,7 @@ namespace input {
*/
//% help=input/on-gesture weight=84 blockGap=8
//% blockId=device_gesture_event block="on |%NAME" icon="\uf135"
//% parts="accelerometer"
void onGesture(Gesture gesture, Action body) {
if ((int)gesture == MICROBIT_ACCELEROMETER_EVT_3G && uBit.accelerometer.getRange() < 3)
uBit.accelerometer.setRange(4);
@ -134,7 +136,7 @@ namespace input {
registerWithDal(MICROBIT_ID_GESTURE, (int)gesture, body);
}
/**
/**
* Do something when a pin is pressed.
* @param name the pin that needs to be pressed
* @param body the code to run when the pin is pressed
@ -173,6 +175,7 @@ namespace input {
//% block="button|%NAME|is pressed"
//% blockId=device_get_button2
//% icon="\uf192" blockGap=8
//% parts="buttonpair"
bool buttonIsPressed(Button button) {
if (button == Button::A)
return uBit.buttonA.isPressed();
@ -201,6 +204,7 @@ namespace input {
//% help=input/compass-heading
//% weight=56 icon="\uf14e"
//% blockId=device_heading block="compass heading (°)" blockGap=8
//% parts="compass"
int compassHeading() {
return uBit.compass.heading();
}
@ -212,6 +216,7 @@ namespace input {
//% weight=55 icon="\uf06d"
//% help=input/temperature
//% blockId=device_temperature block="temperature (°C)" blockGap=8
//% parts="thermometer"
int temperature() {
return uBit.thermometer.getTemperature();
}
@ -229,6 +234,7 @@ namespace input {
*/
//% help=input/acceleration weight=54 icon="\uf135"
//% blockId=device_acceleration block="acceleration (mg)|%NAME" blockGap=8
//% parts="accelerometer"
int acceleration(Dimension dimension) {
switch (dimension) {
case Dimension::X: return uBit.accelerometer.getX();
@ -245,6 +251,7 @@ namespace input {
*/
//% help=input/light-level weight=53
//% blockId=device_get_light_level block="light level" blockGap=8 icon="\uf185"
//% parts="ledmatrix"
int lightLevel() {
return uBit.display.readLightLevel();
}
@ -255,6 +262,7 @@ namespace input {
*/
//% help=input/rotation weight=52
//% blockId=device_get_rotation block="rotation (°)|%NAME" blockGap=8 icon="\uf197"
//% parts="accelerometer"
int rotation(Rotation kind) {
switch (kind) {
case Rotation::Pitch: return uBit.accelerometer.getPitch();
@ -269,6 +277,7 @@ namespace input {
*/
//% help=input/magnetic-force weight=51
//% blockId=device_get_magnetic_force block="magnetic force (µT)|%NAME" blockGap=8 icon="\uf076"
//% parts="compass"
int magneticForce(Dimension dimension) {
if (!uBit.compass.isCalibrated())
uBit.compass.calibrate();
@ -304,6 +313,7 @@ namespace input {
//% help=input/set-accelerometer-range
//% blockId=device_set_accelerometer_range block="set accelerometer|range %range" icon="\uf135"
//% weight=5
//% parts="accelerometer"
void setAccelerometerRange(AcceleratorRange range) {
uBit.accelerometer.setRange((int)range);
}

View File

@ -18,6 +18,7 @@ namespace led {
*/
//% help=led/plot weight=78
//% blockId=device_plot block="plot|x %x|y %y" icon="\uf205" blockGap=8
//% parts="ledmatrix"
void plot(int x, int y) {
uBit.display.image.setPixelValue(x, y, 1);
}
@ -29,6 +30,7 @@ namespace led {
*/
//% help=led/unplot weight=77
//% blockId=device_unplot block="unplot|x %x|y %y" icon="\uf204" blockGap=8
//% parts="ledmatrix"
void unplot(int x, int y) {
uBit.display.image.setPixelValue(x, y, 0);
}
@ -40,6 +42,7 @@ namespace led {
*/
//% help=led/point weight=76
//% blockId=device_point block="point|x %x|y %y" icon="\uf10c"
//% parts="ledmatrix"
bool point(int x, int y) {
int pix = uBit.display.image.getPixelValue(x, y);
return pix > 0;
@ -50,6 +53,7 @@ namespace led {
*/
//% help=led/brightness weight=60
//% blockId=device_get_brightness block="brightness" icon="\uf042" blockGap=8
//% parts="ledmatrix"
int brightness() {
return uBit.display.getBrightness();
}
@ -60,6 +64,7 @@ namespace led {
*/
//% help=led/set-brightness weight=59
//% blockId=device_set_brightness block="set brightness %value" icon="\uf042"
//% parts="ledmatrix"
void setBrightness(int value) {
uBit.display.setBrightness(value);
}
@ -69,6 +74,7 @@ namespace led {
*/
//% weight=50 help=led/stop-animation
//% blockId=device_stop_animation block="stop animation" icon="\uf04d"
//% parts="ledmatrix"
void stopAnimation() {
uBit.display.stopAnimation();
}
@ -78,6 +84,7 @@ namespace led {
* @param mode TODO
*/
//% weight=1 help=led/set-display-mode
//% parts="ledmatrix"
void setDisplayMode(DisplayMode_ mode) {
uBit.display.setDisplayMode((DisplayMode)mode);
}
@ -86,6 +93,7 @@ namespace led {
* Takes a screenshot of the LED screen and returns an image.
*/
//% help=led/screenshot
//% parts="ledmatrix"
Image screenshot() {
return uBit.display.screenShot().leakData();
/*

View File

@ -17,6 +17,7 @@
*/
//% help=led/plot-bar-graph weight=20
//% blockId=device_plot_bar_graph block="plot bar graph of %value |up to %high" icon="\uf080" blockExternalInputs=true
//% parts="ledmatrix"
export function plotBarGraph(value: number, high: number): void {
let now = input.runningTime();
serial.writeString(value.toString() + "\r\n");
@ -53,6 +54,7 @@
*/
//% help=led/toggle weight=77
//% blockId=device_led_toggle block="toggle|x %x|y %y" icon="\uf204" blockGap=8
//% parts="ledmatrix"
export function toggle(x: number, y: number): void {
if (led.point(x, y)) {
led.unplot(x, y);
@ -65,6 +67,7 @@
* Turns all LEDS on
*/
//% help=led/plot-all
//% parts="ledmatrix"
export function plotAll(): void {
for (let i = 0; i < 5; i++) {
for (let j = 0; j < 5; j++) {
@ -77,6 +80,7 @@
* Inverts the current LED display
*/
//% help=led/toggle-all
//% parts="ledmatrix"
export function toggleAll(): void {
for (let i = 0; i < 5; i++) {
for (let j = 0; j < 5; j++) {
@ -90,6 +94,7 @@
* @param ms TODO
*/
//% help=led/fade-in
//% parts="ledmatrix"
export function fadeIn(ms: number = 700): void {
if (ms < 20) {
led.setBrightness(255);
@ -112,6 +117,7 @@
* @param ms TODO
*/
//% help=led/fade-out
//% parts="ledmatrix"
export function fadeOut(ms: number = 700): void {
if (ms < 20) {
led.setBrightness(0);

View File

@ -1,63 +1,110 @@
enum Note {
C = 262,
//% block=C#
//% blockIdentity=music.noteFrequency
CSharp = 277,
//% blockIdentity=music.noteFrequency
D = 294,
//% blockIdentity=music.noteFrequency
Eb = 311,
//% blockIdentity=music.noteFrequency
E = 330,
//% blockIdentity=music.noteFrequency
F = 349,
//% block=F#
//% blockIdentity=music.noteFrequency
FSharp = 370,
//% blockIdentity=music.noteFrequency
G = 392,
//% block=G#
//% blockIdentity=music.noteFrequency
GSharp = 415,
//% blockIdentity=music.noteFrequency
A = 440,
//% blockIdentity=music.noteFrequency
Bb = 466,
//% blockIdentity=music.noteFrequency
B = 494,
//% blockIdentity=music.noteFrequency
C3 = 131,
//% block=C#3
//% blockIdentity=music.noteFrequency
CSharp3 = 139,
//% blockIdentity=music.noteFrequency
D3 = 147,
//% blockIdentity=music.noteFrequency
Eb3 = 156,
//% blockIdentity=music.noteFrequency
E3 = 165,
//% blockIdentity=music.noteFrequency
F3 = 175,
//% block=F#3
//% blockIdentity=music.noteFrequency
FSharp3 = 185,
//% blockIdentity=music.noteFrequency
G3 = 196,
//% block=G#3
//% blockIdentity=music.noteFrequency
GSharp3 = 208,
//% blockIdentity=music.noteFrequency
A3 = 220,
//% blockIdentity=music.noteFrequency
Bb3 = 233,
//% blockIdentity=music.noteFrequency
B3 = 247,
//% blockIdentity=music.noteFrequency
C4 = 262,
//% block=C#4
//% blockIdentity=music.noteFrequency
CSharp4 = 277,
//% blockIdentity=music.noteFrequency
D4 = 294,
//% blockIdentity=music.noteFrequency
Eb4 = 311,
//% blockIdentity=music.noteFrequency
E4 = 330,
//% blockIdentity=music.noteFrequency
F4 = 349,
//% block=F#4
//% blockIdentity=music.noteFrequency
FSharp4 = 370,
//% blockIdentity=music.noteFrequency
G4 = 392,
//% block=G#4
//% blockIdentity=music.noteFrequency
GSharp4 = 415,
//% blockIdentity=music.noteFrequency
A4 = 440,
//% blockIdentity=music.noteFrequency
Bb4 = 466,
//% blockIdentity=music.noteFrequency
B4 = 494,
//% blockIdentity=music.noteFrequency
C5 = 523,
//% block=C#5
//% blockIdentity=music.noteFrequency
CSharp5 = 555,
//% blockIdentity=music.noteFrequency
D5 = 587,
//% blockIdentity=music.noteFrequency
Eb5 = 622,
//% blockIdentity=music.noteFrequency
E5 = 659,
//% blockIdentity=music.noteFrequency
F5 = 698,
//% block=F#5
//% blockIdentity=music.noteFrequency
FSharp5 = 740,
//% blockIdentity=music.noteFrequency
G5 = 784,
//% block=G#5
//% blockIdentity=music.noteFrequency
GSharp5 = 831,
//% blockIdentity=music.noteFrequency
A5 = 880,
//% blockIdentity=music.noteFrequency
Bb5 = 932,
//% blockIdentity=music.noteFrequency
B5 = 989,
}
@ -88,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);
@ -99,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);
@ -110,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);
}
@ -121,6 +171,8 @@ 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;
}
@ -134,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;
@ -150,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;
@ -161,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);
}
@ -172,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

@ -11,14 +11,16 @@ declare namespace images {
* Creates an image that fits on the LED screen.
*/
//% weight=75 help=images/create-image
//% blockId=device_build_image block="create image" imageLiteral=1 shim=images::createImage
//% blockId=device_build_image block="create image"
//% parts="ledmatrix" imageLiteral=1 shim=images::createImage
function createImage(leds: string): Image;
/**
* Creates an image with 2 frames.
*/
//% weight=74 help=images/create-big-image
//% blockId=device_build_big_image block="create big image" imageLiteral=2 shim=images::createBigImage
//% blockId=device_build_big_image block="create big image" imageLiteral=2
//% parts="ledmatrix" shim=images::createBigImage
function createBigImage(leds: string): Image;
}
@ -27,7 +29,8 @@ declare interface Image {
/**
* Plots the image at a given column to the screen
*/
//% help=images/plot-image xOffset.defl=0 shim=ImageMethods::plotImage
//% help=images/plot-image
//% parts="ledmatrix" xOffset.defl=0 shim=ImageMethods::plotImage
plotImage(xOffset?: number): void;
/**
@ -35,14 +38,16 @@ declare interface Image {
* @param xOffset column index to start displaying the image
*/
//% help=images/show-image weight=80 blockNamespace=images
//% blockId=device_show_image_offset block="show image %sprite|at offset %offset" blockGap=8 shim=ImageMethods::showImage
//% blockId=device_show_image_offset block="show image %sprite|at offset %offset" blockGap=8
//% parts="ledmatrix" shim=ImageMethods::showImage
showImage(xOffset: number): void;
/**
* Draws the ``index``-th frame of the image on the screen.
* @param xOffset column index to start displaying the image
*/
//% help=images/plot-frame weight=80 shim=ImageMethods::plotFrame
//% help=images/plot-frame weight=80
//% parts="ledmatrix" shim=ImageMethods::plotFrame
plotFrame(xOffset: number): void;
/**
@ -51,25 +56,29 @@ declare interface Image {
* @param interval time between each animation step in milli seconds, eg: 200
*/
//% help=images/show-image weight=79 async blockNamespace=images
//% blockId=device_scroll_image block="scroll image %sprite|with offset %frameoffset|and interval (ms) %delay" blockGap=8 shim=ImageMethods::scrollImage
//% blockId=device_scroll_image block="scroll image %sprite|with offset %frameoffset|and interval (ms) %delay" blockGap=8
//% parts="ledmatrix" shim=ImageMethods::scrollImage
scrollImage(frameOffset: number, interval: number): void;
/**
* Sets all pixels off.
*/
//% help=images/clear shim=ImageMethods::clear
//% help=images/clear
//% parts="ledmatrix" shim=ImageMethods::clear
clear(): void;
/**
* Sets a specific pixel brightness at a given position
*/
//% shim=ImageMethods::setPixelBrightness
//%
//% parts="ledmatrix" shim=ImageMethods::setPixelBrightness
setPixelBrightness(x: number, y: number, value: number): void;
/**
* Gets the pixel brightness ([0..255]) at a given position
*/
//% shim=ImageMethods::pixelBrightness
//%
//% parts="ledmatrix" shim=ImageMethods::pixelBrightness
pixelBrightness(x: number, y: number): number;
/**
@ -90,7 +99,8 @@ declare interface Image {
* @param y TODO
* @param value TODO
*/
//% help=images/set-pixel shim=ImageMethods::setPixel
//% help=images/set-pixel
//% parts="ledmatrix" shim=ImageMethods::setPixel
setPixel(x: number, y: number, value: boolean): void;
/**
@ -98,14 +108,16 @@ declare interface Image {
* @param x TODO
* @param y TODO
*/
//% help=images/pixel shim=ImageMethods::pixel
//% help=images/pixel
//% parts="ledmatrix" shim=ImageMethods::pixel
pixel(x: number, y: number): boolean;
/**
* Shows a particular frame of the image strip.
* @param frame TODO
*/
//% weight=70 help=images/show-frame shim=ImageMethods::showFrame
//% weight=70 help=images/show-frame
//% parts="ledmatrix" shim=ImageMethods::showFrame
showFrame(frame: number): void;
}
@ -123,7 +135,8 @@ declare namespace basic {
//% help=basic/show-number
//% weight=96
//% blockId=device_show_number block="show|number %number" blockGap=8 icon="\uf1ec"
//% async interval.defl=150 shim=basic::showNumber
//% async
//% parts="ledmatrix" interval.defl=150 shim=basic::showNumber
function showNumber(value: number, interval?: number): void;
/**
@ -135,7 +148,8 @@ declare namespace basic {
//% weight=95 blockGap=8
//% imageLiteral=1 async
//% blockId=device_show_leds
//% block="show leds" icon="\uf00a" interval.defl=400 shim=basic::showLeds
//% block="show leds" icon="\uf00a"
//% parts="ledmatrix" interval.defl=400 shim=basic::showLeds
function showLeds(leds: string, interval?: number): void;
/**
@ -147,14 +161,16 @@ declare namespace basic {
//% weight=87 blockGap=8
//% block="show|string %text" icon="\uf031"
//% async
//% blockId=device_print_message interval.defl=150 shim=basic::showString
//% blockId=device_print_message
//% parts="ledmatrix" interval.defl=150 shim=basic::showString
function showString(text: string, interval?: number): void;
/**
* Turn off all LEDs
*/
//% help=basic/clear-screen weight=79
//% blockId=device_clear_display block="clear screen" icon="\uf12d" shim=basic::clearScreen
//% blockId=device_clear_display block="clear screen" icon="\uf12d"
//% parts="ledmatrix" shim=basic::clearScreen
function clearScreen(): void;
/**
@ -162,14 +178,16 @@ declare namespace basic {
* @param leds pattern of LEDs to turn on/off
* @param interval time in milliseconds between each redraw
*/
//% help=basic/show-animation imageLiteral=1 async interval.defl=400 shim=basic::showAnimation
//% help=basic/show-animation imageLiteral=1 async
//% parts="ledmatrix" interval.defl=400 shim=basic::showAnimation
function showAnimation(leds: string, interval?: number): void;
/**
* Draws an image on the LED screen.
* @param leds pattern of LEDs to turn on/off
*/
//% help=basic/plot-leds weight=80 imageLiteral=1 shim=basic::plotLeds
//% help=basic/plot-leds weight=80
//% parts="ledmatrix" imageLiteral=1 shim=basic::plotLeds
function plotLeds(leds: string): void;
/**
@ -201,7 +219,8 @@ declare namespace input {
* @param body TODO
*/
//% help=input/on-button-pressed weight=85 blockGap=8
//% blockId=device_button_event block="on button|%NAME|pressed" icon="\uf192" shim=input::onButtonPressed
//% blockId=device_button_event block="on button|%NAME|pressed" icon="\uf192"
//% parts="buttonpair" shim=input::onButtonPressed
function onButtonPressed(button: Button, body: () => void): void;
/**
@ -209,7 +228,8 @@ declare namespace input {
* @param body TODO
*/
//% help=input/on-gesture weight=84 blockGap=8
//% blockId=device_gesture_event block="on |%NAME" icon="\uf135" shim=input::onGesture
//% blockId=device_gesture_event block="on |%NAME" icon="\uf135"
//% parts="accelerometer" shim=input::onGesture
function onGesture(gesture: Gesture, body: () => void): void;
/**
@ -236,7 +256,8 @@ declare namespace input {
//% help=input/button-is-pressed weight=57
//% block="button|%NAME|is pressed"
//% blockId=device_get_button2
//% icon="\uf192" blockGap=8 shim=input::buttonIsPressed
//% icon="\uf192" blockGap=8
//% parts="buttonpair" shim=input::buttonIsPressed
function buttonIsPressed(button: Button): boolean;
/**
@ -253,7 +274,8 @@ declare namespace input {
*/
//% help=input/compass-heading
//% weight=56 icon="\uf14e"
//% blockId=device_heading block="compass heading (°)" blockGap=8 shim=input::compassHeading
//% blockId=device_heading block="compass heading (°)" blockGap=8
//% parts="compass" shim=input::compassHeading
function compassHeading(): number;
/**
@ -261,7 +283,8 @@ declare namespace input {
*/
//% weight=55 icon="\uf06d"
//% help=input/temperature
//% blockId=device_temperature block="temperature (°C)" blockGap=8 shim=input::temperature
//% blockId=device_temperature block="temperature (°C)" blockGap=8
//% parts="thermometer" shim=input::temperature
function temperature(): number;
/**
@ -269,14 +292,16 @@ declare namespace input {
* @param dimension TODO
*/
//% help=input/acceleration weight=54 icon="\uf135"
//% blockId=device_acceleration block="acceleration (mg)|%NAME" blockGap=8 shim=input::acceleration
//% blockId=device_acceleration block="acceleration (mg)|%NAME" blockGap=8
//% parts="accelerometer" shim=input::acceleration
function acceleration(dimension: Dimension): number;
/**
* Reads the light level applied to the LED screen in a range from ``0`` (dark) to ``255`` bright.
*/
//% help=input/light-level weight=53
//% blockId=device_get_light_level block="light level" blockGap=8 icon="\uf185" shim=input::lightLevel
//% blockId=device_get_light_level block="light level" blockGap=8 icon="\uf185"
//% parts="ledmatrix" shim=input::lightLevel
function lightLevel(): number;
/**
@ -284,7 +309,8 @@ declare namespace input {
* @param kind TODO
*/
//% help=input/rotation weight=52
//% blockId=device_get_rotation block="rotation (°)|%NAME" blockGap=8 icon="\uf197" shim=input::rotation
//% blockId=device_get_rotation block="rotation (°)|%NAME" blockGap=8 icon="\uf197"
//% parts="accelerometer" shim=input::rotation
function rotation(kind: Rotation): number;
/**
@ -292,7 +318,8 @@ declare namespace input {
* @param dimension TODO
*/
//% help=input/magnetic-force weight=51
//% blockId=device_get_magnetic_force block="magnetic force (µT)|%NAME" blockGap=8 icon="\uf076" shim=input::magneticForce
//% blockId=device_get_magnetic_force block="magnetic force (µT)|%NAME" blockGap=8 icon="\uf076"
//% parts="compass" shim=input::magneticForce
function magneticForce(dimension: Dimension): number;
/**
@ -314,7 +341,8 @@ declare namespace input {
*/
//% help=input/set-accelerometer-range
//% blockId=device_set_accelerometer_range block="set accelerometer|range %range" icon="\uf135"
//% weight=5 shim=input::setAccelerometerRange
//% weight=5
//% parts="accelerometer" shim=input::setAccelerometerRange
function setAccelerometerRange(range: AcceleratorRange): void;
}
@ -400,7 +428,8 @@ declare namespace led {
* @param y TODO
*/
//% help=led/plot weight=78
//% blockId=device_plot block="plot|x %x|y %y" icon="\uf205" blockGap=8 shim=led::plot
//% blockId=device_plot block="plot|x %x|y %y" icon="\uf205" blockGap=8
//% parts="ledmatrix" shim=led::plot
function plot(x: number, y: number): void;
/**
@ -409,7 +438,8 @@ declare namespace led {
* @param y TODO
*/
//% help=led/unplot weight=77
//% blockId=device_unplot block="unplot|x %x|y %y" icon="\uf204" blockGap=8 shim=led::unplot
//% blockId=device_unplot block="unplot|x %x|y %y" icon="\uf204" blockGap=8
//% parts="ledmatrix" shim=led::unplot
function unplot(x: number, y: number): void;
/**
@ -418,14 +448,16 @@ declare namespace led {
* @param y TODO
*/
//% help=led/point weight=76
//% blockId=device_point block="point|x %x|y %y" icon="\uf10c" shim=led::point
//% blockId=device_point block="point|x %x|y %y" icon="\uf10c"
//% parts="ledmatrix" shim=led::point
function point(x: number, y: number): boolean;
/**
* Get the screen brightness from 0 (off) to 255 (full bright).
*/
//% help=led/brightness weight=60
//% blockId=device_get_brightness block="brightness" icon="\uf042" blockGap=8 shim=led::brightness
//% blockId=device_get_brightness block="brightness" icon="\uf042" blockGap=8
//% parts="ledmatrix" shim=led::brightness
function brightness(): number;
/**
@ -433,27 +465,31 @@ declare namespace led {
* @param value the brightness value, eg:255, 127, 0
*/
//% help=led/set-brightness weight=59
//% blockId=device_set_brightness block="set brightness %value" icon="\uf042" shim=led::setBrightness
//% blockId=device_set_brightness block="set brightness %value" icon="\uf042"
//% parts="ledmatrix" shim=led::setBrightness
function setBrightness(value: number): void;
/**
* Cancels the current animation and clears other pending animations.
*/
//% weight=50 help=led/stop-animation
//% blockId=device_stop_animation block="stop animation" icon="\uf04d" shim=led::stopAnimation
//% blockId=device_stop_animation block="stop animation" icon="\uf04d"
//% parts="ledmatrix" shim=led::stopAnimation
function stopAnimation(): void;
/**
* Sets the display mode between black and white and greyscale for rendering LEDs.
* @param mode TODO
*/
//% weight=1 help=led/set-display-mode shim=led::setDisplayMode
//% weight=1 help=led/set-display-mode
//% parts="ledmatrix" shim=led::setDisplayMode
function setDisplayMode(mode: DisplayMode): void;
/**
* Takes a screenshot of the LED screen and returns an image.
*/
//% help=led/screenshot shim=led::screenshot
//% help=led/screenshot
//% parts="ledmatrix" shim=led::screenshot
function screenshot(): Image;
}
declare namespace pins {

View File

@ -1,6 +1,6 @@
{
"name": "pxt-microbit",
"version": "0.3.46",
"version": "0.3.62",
"description": "micro:bit target for PXT",
"keywords": [
"JavaScript",
@ -29,6 +29,6 @@
"typescript": "^1.8.7"
},
"dependencies": {
"pxt-core": "0.3.51"
"pxt-core": "0.3.71"
}
}

View File

@ -31,12 +31,14 @@
"description": "",
"files": [
"main.blocks",
"main.ts"
"main.ts",
"README.md"
]
},
"files": {
"main.blocks": "<xml xmlns=\"http://www.w3.org/1999/xhtml\">\n<block type=\"device_forever\">\n<statement name=\"HANDLER\">\n<block type=\"device_show_leds\">\n<field name=\"LED00\">FALSE</field>\n<field name=\"LED10\">FALSE</field>\n<field name=\"LED20\">FALSE</field>\n<field name=\"LED30\">FALSE</field>\n<field name=\"LED40\">FALSE</field>\n<field name=\"LED01\">FALSE</field>\n<field name=\"LED11\">TRUE</field>\n<field name=\"LED21\">FALSE</field>\n<field name=\"LED31\">TRUE</field>\n<field name=\"LED41\">FALSE</field>\n<field name=\"LED02\">FALSE</field>\n<field name=\"LED12\">FALSE</field>\n<field name=\"LED22\">FALSE</field>\n<field name=\"LED32\">FALSE</field>\n<field name=\"LED42\">FALSE</field>\n<field name=\"LED03\">TRUE</field>\n<field name=\"LED13\">FALSE</field>\n<field name=\"LED23\">FALSE</field>\n<field name=\"LED33\">FALSE</field>\n<field name=\"LED43\">TRUE</field>\n<field name=\"LED04\">FALSE</field>\n<field name=\"LED14\">TRUE</field>\n<field name=\"LED24\">TRUE</field>\n<field name=\"LED34\">TRUE</field>\n<field name=\"LED44\">FALSE</field>\n<next>\n<block type=\"device_show_leds\">\n<field name=\"LED00\">FALSE</field>\n<field name=\"LED10\">FALSE</field>\n<field name=\"LED20\">FALSE</field>\n<field name=\"LED30\">FALSE</field>\n<field name=\"LED40\">FALSE</field>\n<field name=\"LED01\">FALSE</field>\n<field name=\"LED11\">FALSE</field>\n<field name=\"LED21\">FALSE</field>\n<field name=\"LED31\">FALSE</field>\n<field name=\"LED41\">FALSE</field>\n<field name=\"LED02\">FALSE</field>\n<field name=\"LED12\">FALSE</field>\n<field name=\"LED22\">FALSE</field>\n<field name=\"LED32\">FALSE</field>\n<field name=\"LED42\">FALSE</field>\n<field name=\"LED03\">FALSE</field>\n<field name=\"LED13\">FALSE</field>\n<field name=\"LED23\">FALSE</field>\n<field name=\"LED33\">FALSE</field>\n<field name=\"LED43\">FALSE</field>\n<field name=\"LED04\">FALSE</field>\n<field name=\"LED14\">FALSE</field>\n<field name=\"LED24\">FALSE</field>\n<field name=\"LED34\">FALSE</field>\n<field name=\"LED44\">FALSE</field>\n</block>\n</next>\n</block>\n</statement>\n</block>\n</xml>",
"main.ts": "basic.forever(() => {\r\n basic.showLeds(`\r\n . # . # .\r\n # . # . #\r\n # . . . #\r\n . # . # .\r\n . . # . .\r\n `)\r\n basic.showLeds(`\r\n . . . . .\r\n . . . . .\r\n . . . . .\r\n . . . . .\r\n . . . . .\r\n `)\r\n})\r\n"
"main.ts": "basic.forever(() => {\r\n basic.showLeds(`\r\n . # . # .\r\n # . # . #\r\n # . . . #\r\n . # . # .\r\n . . # . .\r\n `)\r\n basic.showLeds(`\r\n . . . . .\r\n . . . . .\r\n . . . . .\r\n . . . . .\r\n . . . . .\r\n `)\r\n})\r\n",
"README.md": ""
}
},
"tsprj": {
@ -49,11 +51,13 @@
},
"description": "",
"files": [
"main.ts"
"main.ts",
"README.md"
]
},
"files": {
"main.ts": "basic.showLeds(`\r\n . . . . .\r\n . # . # .\r\n . . . . .\r\n # . . . #\r\n . # # # .\r\n `);"
"main.ts": "basic.showLeds(`\r\n . . . . .\r\n . # . # .\r\n . . . . .\r\n # . . . #\r\n . # # # .\r\n `);",
"README.md": ""
}
},
"compile": {
@ -71,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.3.1",
"gittag": "v0.4.1",
"serviceId": "ws"
},
"serial": {
@ -132,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();
}
}

100
sim/dalboard.ts Normal file
View File

@ -0,0 +1,100 @@
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;
//TODO: read from pxt.json/pxttarget.json
let boardDef = MICROBIT_DEF;
// let boardDef = ARDUINO_ZERO;
// let boardDef = SPARKFUN_PHOTON;
// let boardDef = RASPBERRYPI_MODELB;
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();
}
}
}

205
sim/definitions.ts Normal file
View File

@ -0,0 +1,205 @@
/// <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,
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[]
}
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,
left: number,
top: number,
pinDist: 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"],
}
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,
left: -180,
top: -135,
pinDist: 70,
},
breadboardColumnsNeeded: 5,
breadboardStartRow: "f",
pinAllocation: {
type: "auto",
gpioPinsNeeded: 1,
},
assemblyStep: 0,
wires: [
{start: ["breadboard", "j", 1], end: ["GPIO", 0], color: "white", assemblyStep: 1},
{start: ["breadboard", "j", 3], end: "ground", color: "white", 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),
};
}

View File

@ -0,0 +1,669 @@
/// <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: BoardImageDefinition): visuals.SVGElAndSize {
return new visuals.MicrobitBoardSvg({
theme: visuals.randomTheme()
}).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(type: "wire" | string, opts: mkCmpDivOpts): HTMLElement {
let el: visuals.SVGElAndSize;
if (type == "wire") {
//TODO: support non-croc wire parts
el = visuals.mkWirePart([0, 0], opts.wireClr || "red", true);
} else {
let cnstr = builtinComponentPartVisual[type];
el = cnstr([0, 0]);
}
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 rowCol: BBRowCol = [`${cmpInst.breadboardStartRow}`, `${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(<BoardImageDefinition>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;
}
if (typeof c.visual === "string") {
let builtinVisual = <string>c.visual;
let cmp = mkCmpDiv(builtinVisual, {
left: QUANT_LBL(quant),
leftSize: QUANT_LBL_SIZE,
cmpScale: PARTS_CMP_SCALE,
});
addClass(cmp, "partslist-cmp");
panel.appendChild(cmp);
} else {
//TODO: handle generic components
}
});
// 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;
if (typeof c.visual === "string") {
let builtinVisual = <string>c.visual;
let cmp = mkCmpDiv(builtinVisual, {
top: `(${row},${col})`,
topSize: LOC_LBL_SIZE,
cmpHeight: REQ_CMP_HEIGHT,
cmpScale: REQ_CMP_SCALE
})
addClass(cmp, "cmp-div");
reqsDiv.appendChild(cmp);
} else {
//TODO: generic component
}
});
});
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 = MICROBIT_DEF;
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>

218
sim/simlib.ts Normal file
View File

@ -0,0 +1,218 @@
/// <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 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();
}
pxt.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();
}
pxt.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();
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();
}
}
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;
}
}

17
sim/state/lightsensor.ts Normal file
View File

@ -0,0 +1,17 @@
namespace pxsim {
export class LightSensorState {
usesLightLevel = false;
lightLevel = 128;
}
}
namespace pxsim.input {
export function lightLevel(): number {
let b = board().lightSensorState;
if (!b.usesLightLevel) {
b.usesLightLevel = true;
runtime.queueDisplayUpdate();
}
return b.lightLevel;
}
}

228
sim/state/misc.ts Normal file
View File

@ -0,0 +1,228 @@
namespace pxsim {
/**
* 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().ledMatrixState.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 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;
}
}
export interface RuntimeOptions {
theme: string;
}
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);
}
}
}
namespace pxsim.basic {
export var pause = thread.pause;
export var forever = thread.forever;
}
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 runningTime(): number {
return runtime.runningTime();
}
export function calibrate() {
}
}
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 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
}
}
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
}
}

57
sim/state/neopixel.ts Normal file
View File

@ -0,0 +1,57 @@
namespace pxsim {
export function sendBufferAsm(buffer: Buffer, pin: DigitalPin) {
let b = board();
if (b) {
let np = b.neopixelState;
if (np) {
np.updateBuffer(buffer, pin);
runtime.queueDisplayUpdate();
}
}
}
}
namespace pxsim {
export enum NeoPixelMode {RGB, RGBW};
export type RGBW = [number, number, number, number];
function readNeoPixelBuffer(inBuffer: Uint8Array[], outColors: RGBW[], mode: NeoPixelMode) {
let buf = inBuffer;
let stride = mode === NeoPixelMode.RGBW ? 4 : 3;
let pixelCount = Math.floor(buf.length / stride);
for (let i = 0; i < pixelCount; i++) {
// NOTE: for whatever reason, NeoPixels pack GRB not RGB
let r = buf[i * stride + 1] as any as number
let g = buf[i * stride + 0] as any as number
let b = buf[i * stride + 2] as any as number
let w = 0;
if (stride === 4)
w = buf[i * stride + 3] as any as number
outColors[i] = [r, g, b, w]
}
}
export class NeoPixelState {
private buffers: {[pin: number]: Uint8Array[]} = {};
private colors: {[pin: number]: RGBW[]} = {};
private dirty: {[pin: number]: boolean} = {};
public updateBuffer(buffer: Buffer, pin: DigitalPin) {
//update buffers
let buf = <Uint8Array[]>(<any>buffer).data;
this.buffers[pin] = buf;
this.dirty[pin] = true;
}
public getColors(pin: number, mode: NeoPixelMode): RGBW[] {
let outColors = this.colors[pin] || (this.colors[pin] = []);
if (this.dirty[pin]) {
let buf = this.buffers[pin] || (this.buffers[pin] = []);
readNeoPixelBuffer(buf, outColors, mode);
this.dirty[pin] = false;
}
return outColors;
}
}
}

158
sim/state/radio.ts Normal file
View File

@ -0,0 +1,158 @@
namespace pxsim {
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);
}
(<DalBoard>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
})
}
}
export class RadioState {
bus: RadioBus;
constructor(runtime: Runtime) {
this.bus = new RadioBus(runtime);
}
public recievePacket(packet: SimulatorRadioPacketMessage) {
this.bus.datagram.queue({ data: packet.data, rssi: packet.rssi || 0 })
}
}
}
namespace pxsim.radio {
export function broadcastMessage(msg: number): void {
board().radioState.bus.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().radioState.bus.setGroup(id);
}
export function setTransmitPower(power: number): void {
board().radioState.bus.setTransmitPower(power);
}
export function setTransmitSerialNumber(transmit: boolean): void {
board().radioState.bus.setTransmitSerialNumber(transmit);
}
export function sendNumber(value: number): void {
board().radioState.bus.datagram.send([value]);
}
export function sendString(msg: string): void {
board().radioState.bus.datagram.send(msg);
}
export function writeValueToSerial(): void {
let b = board();
let v = b.radioState.bus.datagram.recv().data[0];
b.writeSerial(`{v:${v}}`);
}
export function sendValue(name: string, value: number) {
board().radioState.bus.datagram.send([value]);
}
export function receiveNumber(): number {
let buffer = board().radioState.bus.datagram.recv().data;
if (buffer instanceof Array) return buffer[0];
return 0;
}
export function receiveString(): string {
let buffer = board().radioState.bus.datagram.recv().data;
if (typeof buffer === "string") return <string>buffer;
return "";
}
export function receivedNumberAt(index: number): number {
let buffer = board().radioState.bus.datagram.recv().data;
if (buffer instanceof Array) return buffer[index] || 0;
return 0;
}
export function receivedSignalStrength(): number {
return board().radioState.bus.datagram.lastReceived.rssi;
}
export function onDataReceived(handler: RefAction): void {
pxt.registerWithDal(DAL.MICROBIT_ID_RADIO, DAL.MICROBIT_RADIO_EVT_DATAGRAM, handler);
radio.receiveNumber();
}
}

54
sim/state/serial.ts Normal file
View File

@ -0,0 +1,54 @@
namespace pxsim {
export class SerialState {
serialIn: string[] = [];
public recieveData(data: string) {
this.serialIn.push();
}
readSerial() {
let v = this.serialIn.shift() || "";
return v;
}
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
})
this.serialOutBuffer = ""
break;
}
}
}
}
}
namespace pxsim.serial {
export function writeString(s: string) {
board().writeSerial(s);
}
export function readString(): string {
return board().serialState.readSerial();
}
export function readLine(): string {
return board().serialState.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?
}
}

18
sim/state/thermometer.ts Normal file
View File

@ -0,0 +1,18 @@
namespace pxsim {
export class ThermometerState {
usesTemperature = false;
temperature = 21;
}
}
namespace pxsim.input {
export function temperature(): number {
let b = board();
if (!b.thermometerState.usesTemperature) {
b.thermometerState.usesTemperature = true;
runtime.queueDisplayUpdate();
}
return b.thermometerState.temperature;
}
}

181
sim/visuals/boardhost.ts Normal file
View File

@ -0,0 +1,181 @@
namespace pxsim.visuals {
export interface BoardHostOpts {
state: DalBoard,
boardDef: BoardDefinition,
cmpsList?: string[],
cmpDefs: Map<PartDefinition>,
fnArgs: any,
forceBreadboard?: boolean,
maxWidth?: string,
maxHeight?: string
wireframe?: boolean
}
export class BoardHost {
private components: IBoardComponent<any>[] = [];
private wireFactory: WireFactory;
private breadboard: Breadboard;
private fromBBCoord: (xy: Coord) => Coord;
private fromMBCoord: (xy: Coord) => Coord;
private boardView: BoardView;
private view: SVGSVGElement;
private style: SVGStyleElement;
private defs: SVGDefsElement;
private state: DalBoard;
constructor(opts: BoardHostOpts) {
this.state = opts.state;
let onboardCmps = opts.boardDef.onboardComponents || [];
let activeComponents = (opts.cmpsList || []).filter(c => onboardCmps.indexOf(c) < 0);
activeComponents.sort();
this.boardView = new visuals.MicrobitBoardSvg({
runtime: runtime,
theme: visuals.randomTheme(),
disableTilt: false,
wireframe: opts.wireframe,
});
let useBreadboard = 0 < activeComponents.length || opts.forceBreadboard;
if (useBreadboard) {
this.breadboard = new Breadboard({
wireframe: opts.wireframe,
});
let composition = composeSVG({
el1: this.boardView.getView(),
scaleUnit1: this.boardView.getPinDist(),
el2: this.breadboard.getSVGAndSize(),
scaleUnit2: this.breadboard.getPinDist(),
margin: [0, 0, 20, 0],
middleMargin: 80,
maxWidth: opts.maxWidth,
maxHeight: opts.maxHeight,
});
let under = composition.under;
let over = composition.over;
this.view = composition.host;
let edges = composition.edges;
this.fromMBCoord = composition.toHostCoord1;
this.fromBBCoord = composition.toHostCoord2;
let pinDist = composition.scaleUnit;
this.style = <SVGStyleElement>svg.child(this.view, "style", {});
this.defs = <SVGDefsElement>svg.child(this.view, "defs", {});
this.wireFactory = new WireFactory(under, over, edges, this.style, this.getLocCoord.bind(this));
let allocRes = allocateDefinitions({
boardDef: opts.boardDef,
cmpDefs: opts.cmpDefs,
fnArgs: opts.fnArgs,
getBBCoord: this.breadboard.getCoord.bind(this.breadboard),
cmpList: activeComponents,
});
this.addAll(allocRes);
} else {
let el = this.boardView.getView().el;
this.view = el;
if (opts.maxWidth)
svg.hydrate(this.view, { width: opts.maxWidth });
if (opts.maxHeight)
svg.hydrate(this.view, { height: opts.maxHeight });
}
this.state.updateSubscribers.push(() => this.updateState());
}
public highlightBoardPin(pinNm: string) {
this.boardView.highlightPin(pinNm);
}
public highlightBreadboardPin(rowCol: BBRowCol) {
this.breadboard.highlightLoc(rowCol);
}
public highlightWire(wire: Wire) {
//TODO: move to wiring.ts
//underboard wires
wire.wires.forEach(e => {
svg.addClass(e, "highlight");
(<any>e).style["visibility"] = "visible";
});
//un greyed out
svg.addClass(wire.endG, "highlight");
}
public getView(): SVGElement {
return this.view;
}
private updateState() {
this.components.forEach(c => c.updateState());
}
private getBBCoord(rowCol: BBRowCol) {
let bbCoord = this.breadboard.getCoord(rowCol);
return this.fromBBCoord(bbCoord);
}
private getPinCoord(pin: string) {
let boardCoord = this.boardView.getCoord(pin);
return this.fromMBCoord(boardCoord);
}
public getLocCoord(loc: Loc): Coord {
let coord: Coord;
if (loc.type === "breadboard") {
let rowCol = (<BBLoc>loc).rowCol;
coord = this.getBBCoord(rowCol);
} else {
let pinNm = (<BoardLoc>loc).pin;
coord = this.getPinCoord(pinNm);
}
if (!coord) {
console.error("Unknown location: " + name)
return [0, 0];
}
return coord;
}
public addComponent(cmpDesc: CmpInst): IBoardComponent<any> {
let cmp: IBoardComponent<any> = null;
if (typeof cmpDesc.visual === "string") {
let builtinVisual = cmpDesc.visual as string;
let cnstr = builtinComponentSimVisual[builtinVisual];
let stateFn = builtinComponentSimState[builtinVisual];
cmp = cnstr();
cmp.init(this.state.bus, stateFn(this.state), this.view, cmpDesc.microbitPins, cmpDesc.otherArgs);
this.components.push(cmp);
this.view.appendChild(cmp.element);
if (cmp.defs)
cmp.defs.forEach(d => this.defs.appendChild(d));
this.style.textContent += cmp.style || "";
let rowCol = <BBRowCol>[`${cmpDesc.breadboardStartRow}`, `${cmpDesc.breadboardStartColumn}`];
let coord = this.getBBCoord(rowCol);
cmp.moveToCoord(coord);
let getCmpClass = (type: string) => `sim-${type}-cmp`;
let cls = getCmpClass(name);
svg.addClass(cmp.element, cls);
svg.addClass(cmp.element, "sim-cmp");
cmp.updateTheme();
cmp.updateState();
} else {
let vis = cmpDesc.visual as PartVisualDefinition;
console.log("TODO PART: " + vis.image);
//TODO: support generic parts
}
return cmp;
}
public addWire(inst: WireInst): Wire {
return this.wireFactory.addWire(inst.start, inst.end, inst.color, true);
}
public addAll(basicWiresAndCmpsAndWires: AllocatorResult) {
let {powerWires, components} = basicWiresAndCmpsAndWires;
powerWires.forEach(w => this.addWire(w));
components.forEach((cAndWs, idx) => {
let {component, wires} = cAndWs;
wires.forEach(w => this.addWire(w));
this.addComponent(component);
});
}
}
}

649
sim/visuals/breadboard.ts Normal file
View File

@ -0,0 +1,649 @@
namespace pxsim.visuals {
// The distance between the center of two pins. This is the constant on which everything else is based.
const PIN_DIST = 15;
// CSS styling for the breadboard
const BLUE = "#1AA5D7";
const RED = "#DD4BA0";
const BREADBOARD_CSS = `
/* bread board */
.sim-bb-background {
fill:#E0E0E0;
}
.sim-bb-pin {
fill:#999;
}
.sim-bb-pin-hover {
visibility: hidden;
pointer-events: all;
stroke-width: ${PIN_DIST / 2}px;
stroke: transparent;
fill: #777;
}
.sim-bb-pin-hover:hover {
visibility: visible;
fill:#444;
}
.sim-bb-group-wire {
stroke: #999;
stroke-width: ${PIN_DIST / 4}px;
visibility: hidden;
}
.sim-bb-pin-group {
pointer-events: all;
}
.sim-bb-label,
.sim-bb-label-hover {
font-family:"Lucida Console", Monaco, monospace;
fill:#555;
pointer-events: all;
stroke-width: 0;
cursor: default;
}
.sim-bb-label-hover {
visibility: hidden;
fill:#000;
font-weight: bold;
}
.sim-bb-bar {
stroke-width: 0;
}
.sim-bb-blue {
fill:${BLUE};
stroke:${BLUE}
}
.sim-bb-red {
fill:${RED};
stroke:${RED};
}
.sim-bb-pin-group:hover .sim-bb-pin-hover,
.sim-bb-pin-group:hover .sim-bb-group-wire,
.sim-bb-pin-group:hover .sim-bb-label-hover {
visibility: visible;
}
.sim-bb-pin-group:hover .sim-bb-label {
visibility: hidden;
}
/* outline mode */
.sim-bb-outline .sim-bb-background {
stroke-width: ${PIN_DIST / 7}px;
fill: #FFF;
stroke: #000;
}
.sim-bb-outline .sim-bb-mid-channel {
fill: #FFF;
stroke: #888;
stroke-width: 1px;
}
/* grayed out */
.grayed .sim-bb-red,
.grayed .sim-bb-blue {
fill: #BBB;
}
.grayed .sim-bb-pin {
fill: #BBB;
}
.grayed .sim-bb-label {
fill: #BBB;
}
.grayed .sim-bb-background {
stroke: #BBB;
}
.grayed .sim-bb-group-wire {
stroke: #DDD;
}
/* highlighted */
.sim-bb-label.highlight {
visibility: hidden;
}
.sim-bb-label-hover.highlight {
visibility: visible;
}
.sim-bb-blue.highlight {
fill:${BLUE};
}
.sim-bb-red.highlight {
fill:${RED};
}
`
// Pin rows and coluns
const MID_ROWS = 10;
const MID_ROW_GAPS = [4, 4];
const MID_ROW_AND_GAPS = MID_ROWS + MID_ROW_GAPS.length;
const MID_COLS = 30;
const BAR_ROWS = 2;
const BAR_COLS = 25;
const POWER_ROWS = BAR_ROWS * 2;
const POWER_COLS = BAR_COLS * 2;
const BAR_COL_GAPS = [4, 9, 14, 19];
const BAR_COL_AND_GAPS = BAR_COLS + BAR_COL_GAPS.length;
// Essential dimensions
const WIDTH = PIN_DIST * (MID_COLS + 3);
const HEIGHT = PIN_DIST * (MID_ROW_AND_GAPS + POWER_ROWS + 5.5);
const MID_RATIO = 2.0 / 3.0;
const BAR_RATIO = (1.0 - MID_RATIO) * 0.5;
const MID_HEIGHT = HEIGHT * MID_RATIO;
const BAR_HEIGHT = HEIGHT * BAR_RATIO;
// Pin grids
const MID_GRID_WIDTH = (MID_COLS - 1) * PIN_DIST;
const MID_GRID_HEIGHT = (MID_ROW_AND_GAPS - 1) * PIN_DIST;
const MID_GRID_X = (WIDTH - MID_GRID_WIDTH) / 2.0;
const MID_GRID_Y = BAR_HEIGHT + (MID_HEIGHT - MID_GRID_HEIGHT) / 2.0;
const BAR_GRID_HEIGHT = (BAR_ROWS - 1) * PIN_DIST;
const BAR_GRID_WIDTH = (BAR_COL_AND_GAPS - 1) * PIN_DIST;
const BAR_TOP_GRID_X = (WIDTH - BAR_GRID_WIDTH) / 2.0;
const BAR_TOP_GRID_Y = (BAR_HEIGHT - BAR_GRID_HEIGHT) / 2.0;
const BAR_BOT_GRID_X = BAR_TOP_GRID_X;
const BAR_BOT_GRID_Y = BAR_TOP_GRID_Y + BAR_HEIGHT + MID_HEIGHT;
// Individual pins
const PIN_HOVER_SCALAR = 1.3;
const PIN_WIDTH = PIN_DIST / 2.5;
const PIN_ROUNDING = PIN_DIST / 7.5;
// Labels
const PIN_LBL_SIZE = PIN_DIST * 0.7;
const PIN_LBL_HOVER_SCALAR = 1.3;
const PLUS_LBL_SIZE = PIN_DIST * 1.7;
const MINUS_LBL_SIZE = PIN_DIST * 2;
const POWER_LBL_OFFSET = PIN_DIST * 0.8;
const MINUS_LBL_EXTRA_OFFSET = PIN_DIST * 0.07;
const LBL_ROTATION = -90;
// Channels
const CHANNEL_HEIGHT = PIN_DIST * 1.0;
const SMALL_CHANNEL_HEIGHT = PIN_DIST * 0.05;
// Background
const BACKGROUND_ROUNDING = PIN_DIST * 0.3;
export interface GridPin {
el: SVGElement,
hoverEl: SVGElement,
cx: number,
cy: number,
row: string,
col: string,
group?: string
};
export interface GridOptions {
xOffset?: number,
yOffset?: number,
rowCount: number,
colCount: number,
rowStartIdx?: number,
colStartIdx?: number,
pinDist: number,
mkPin: () => SVGElAndSize,
mkHoverPin: () => SVGElAndSize,
getRowName: (rowIdx: number) => string,
getColName: (colIdx: number) => string,
getGroupName?: (rowIdx: number, colIdx: number) => string,
rowIdxsWithGap?: number[],
colIdxsWithGap?: number[],
};
export interface GridResult {
g: SVGGElement,
allPins: GridPin[],
}
export function mkGrid(opts: GridOptions): GridResult {
let xOff = opts.xOffset || 0;
let yOff = opts.yOffset || 0;
let allPins: GridPin[] = [];
let grid = <SVGGElement>svg.elt("g");
let colIdxOffset = opts.colStartIdx || 0;
let rowIdxOffset = opts.rowStartIdx || 0;
let copyArr = <T>(arr: T[]): T[] => arr ? arr.slice(0, arr.length) : [];
let removeAll = <T>(arr: T[], e: T): number => {
let res = 0;
let idx: number;
while (0 <= (idx = arr.indexOf(e))) {
arr.splice(idx, 1);
res += 1;
}
return res;
};
let rowGaps = 0;
let rowIdxsWithGap = copyArr(opts.rowIdxsWithGap)
for (let i = 0; i < opts.rowCount; i++) {
let colGaps = 0;
let colIdxsWithGap = copyArr(opts.colIdxsWithGap)
let cy = yOff + i * opts.pinDist + rowGaps * opts.pinDist;
let rowIdx = i + rowIdxOffset;
for (let j = 0; j < opts.colCount; j++) {
let cx = xOff + j * opts.pinDist + colGaps * opts.pinDist;
let colIdx = j + colIdxOffset;
const addEl = (pin: SVGElAndSize) => {
let pinX = cx - pin.w * 0.5;
let pinY = cy - pin.h * 0.5;
svg.hydrate(pin.el, {x: pinX, y: pinY});
grid.appendChild(pin.el);
return pin.el;
}
let el = addEl(opts.mkPin());
let hoverEl = addEl(opts.mkHoverPin());
let row = opts.getRowName(rowIdx);
let col = opts.getColName(colIdx);
let group = opts.getGroupName ? opts.getGroupName(rowIdx, colIdx) : null;
let gridPin: GridPin = {el: el, hoverEl: hoverEl, cx: cx, cy: cy, row: row, col: col, group: group};
allPins.push(gridPin);
//column gaps
colGaps += removeAll(colIdxsWithGap, colIdx);
}
//row gaps
rowGaps += removeAll(rowIdxsWithGap, rowIdx);
}
return {g: grid, allPins: allPins};
}
function mkBBPin(): SVGElAndSize {
let el = svg.elt("rect");
let width = PIN_WIDTH;
svg.hydrate(el, {
class: "sim-bb-pin",
rx: PIN_ROUNDING,
ry: PIN_ROUNDING,
width: width,
height: width
});
return {el: el, w: width, h: width, x: 0, y: 0};
}
function mkBBHoverPin(): SVGElAndSize {
let el = svg.elt("rect");
let width = PIN_WIDTH * PIN_HOVER_SCALAR;
svg.hydrate(el, {
class: "sim-bb-pin-hover",
rx: PIN_ROUNDING,
ry: PIN_ROUNDING,
width: width,
height: width,
});
return {el: el, w: width, h: width, x: 0, y: 0};
}
export interface GridLabel {
el: SVGTextElement,
hoverEl: SVGTextElement,
txt: string,
group?: string,
};
function mkBBLabel(cx: number, cy: number, size: number, rotation: number, txt: string, group: string, extraClasses?: string[]): GridLabel {
//lbl
let el = mkTxt(cx, cy, size, rotation, txt);
svg.addClass(el, "sim-bb-label");
if (extraClasses)
extraClasses.forEach(c => svg.addClass(el, c));
//hover lbl
let hoverEl = mkTxt(cx, cy, size * PIN_LBL_HOVER_SCALAR, rotation, txt);
svg.addClass(hoverEl, "sim-bb-label-hover");
if (extraClasses)
extraClasses.forEach(c => svg.addClass(hoverEl, c));
let lbl = {el: el, hoverEl: hoverEl, txt: txt, group: group};
return lbl;
}
interface BBBar {
el: SVGRectElement,
group?: string
};
export interface BreadboardOpts {
wireframe?: boolean,
}
export class Breadboard {
public bb: SVGSVGElement;
private styleEl: SVGStyleElement;
private defs: SVGDefsElement;
//truth
private allPins: GridPin[] = [];
private allLabels: GridLabel[] = [];
private allPowerBars: BBBar[] = [];
//quick lookup caches
private rowColToPin: Map<Map<GridPin>> = {};
private rowColToLbls: Map<Map<GridLabel[]>> = {};
constructor(opts: BreadboardOpts) {
this.buildDom();
if (opts.wireframe)
svg.addClass(this.bb, "sim-bb-outline");
}
public updateLocation(x: number, y: number) {
svg.hydrate(this.bb, {
x: `${x}px`,
y: `${y}px`,
});
}
public getPin(row: string, col: string): GridPin {
let colToPin = this.rowColToPin[row];
if (!colToPin)
return null;
let pin = colToPin[col];
if (!pin)
return null;
return pin;
}
public getCoord(rowCol: BBRowCol): Coord {
let [row, col] = rowCol;
let pin = this.getPin(row, col);
if (!pin)
return null;
return [pin.cx, pin.cy];
}
public getPinDist() {
return PIN_DIST;
}
private buildDom() {
this.bb = <SVGSVGElement>svg.elt("svg", {
"version": "1.0",
"viewBox": `0 0 ${WIDTH} ${HEIGHT}`,
"class": `sim-bb`,
"width": WIDTH + "px",
"height": HEIGHT + "px",
});
this.styleEl = <SVGStyleElement>svg.child(this.bb, "style", {});
this.styleEl.textContent += BREADBOARD_CSS;
this.defs = <SVGDefsElement>svg.child(this.bb, "defs", {});
//background
svg.child(this.bb, "rect", { class: "sim-bb-background", width: WIDTH, height: HEIGHT, rx: BACKGROUND_ROUNDING, ry: BACKGROUND_ROUNDING});
//mid channel
let channelGid = "sim-bb-channel-grad";
let channelGrad = <SVGLinearGradientElement>svg.elt("linearGradient")
svg.hydrate(channelGrad, { id: channelGid, x1: "0%", y1: "0%", x2: "0%", y2: "100%" });
this.defs.appendChild(channelGrad);
let channelDark = "#AAA";
let channelLight = "#CCC";
let stop1 = svg.child(channelGrad, "stop", { offset: "0%", style: `stop-color: ${channelDark};` })
let stop2 = svg.child(channelGrad, "stop", { offset: "20%", style: `stop-color: ${channelLight};` })
let stop3 = svg.child(channelGrad, "stop", { offset: "80%", style: `stop-color: ${channelLight};` })
let stop4 = svg.child(channelGrad, "stop", { offset: "100%", style: `stop-color: ${channelDark};` })
const mkChannel = (cy: number, h: number, cls?: string) => {
let channel = svg.child(this.bb, "rect", { class: `sim-bb-channel ${cls || ""}`, y: cy - h / 2, width: WIDTH, height: h});
channel.setAttribute("fill", `url(#${channelGid})`);
return channel;
}
mkChannel(BAR_HEIGHT + MID_HEIGHT / 2, CHANNEL_HEIGHT, "sim-bb-mid-channel");
mkChannel(BAR_HEIGHT, SMALL_CHANNEL_HEIGHT);
mkChannel(BAR_HEIGHT + MID_HEIGHT, SMALL_CHANNEL_HEIGHT);
//-----pins
const getMidTopOrBot = (rowIdx: number) => rowIdx < MID_ROWS / 2.0 ? "b" : "t";
const getBarTopOrBot = (colIdx: number) => colIdx < POWER_COLS / 2.0 ? "b" : "t";
const alphabet = "abcdefghij".split("").reverse();
const getColName = (colIdx: number) => `${colIdx + 1}`;
const getMidRowName = (rowIdx: number) => alphabet[rowIdx];
const getMidGroupName = (rowIdx: number, colIdx: number) => {
let botOrTop = getMidTopOrBot(rowIdx);
let colNm = getColName(colIdx);
return `${botOrTop}${colNm}`;
};
const getBarRowName = (rowIdx: number) => rowIdx === 0 ? "-" : "+";
const getBarGroupName = (rowIdx: number, colIdx: number) => {
let botOrTop = getBarTopOrBot(colIdx);
let rowName = getBarRowName(rowIdx);
return `${rowName}${botOrTop}`;
};
//mid grid
let midGridRes = mkGrid({
xOffset: MID_GRID_X,
yOffset: MID_GRID_Y,
rowCount: MID_ROWS,
colCount: MID_COLS,
pinDist: PIN_DIST,
mkPin: mkBBPin,
mkHoverPin: mkBBHoverPin,
getRowName: getMidRowName,
getColName: getColName,
getGroupName: getMidGroupName,
rowIdxsWithGap: MID_ROW_GAPS,
});
let midGridG = midGridRes.g;
this.allPins = this.allPins.concat(midGridRes.allPins);
//bot bar
let botBarGridRes = mkGrid({
xOffset: BAR_BOT_GRID_X,
yOffset: BAR_BOT_GRID_Y,
rowCount: BAR_ROWS,
colCount: BAR_COLS,
pinDist: PIN_DIST,
mkPin: mkBBPin,
mkHoverPin: mkBBHoverPin,
getRowName: getBarRowName,
getColName: getColName,
getGroupName: getBarGroupName,
colIdxsWithGap: BAR_COL_GAPS,
});
let botBarGridG = botBarGridRes.g;
this.allPins = this.allPins.concat(botBarGridRes.allPins);
//top bar
let topBarGridRes = mkGrid({
xOffset: BAR_TOP_GRID_X,
yOffset: BAR_TOP_GRID_Y,
rowCount: BAR_ROWS,
colCount: BAR_COLS,
colStartIdx: BAR_COLS,
pinDist: PIN_DIST,
mkPin: mkBBPin,
mkHoverPin: mkBBHoverPin,
getRowName: getBarRowName,
getColName: getColName,
getGroupName: getBarGroupName,
colIdxsWithGap: BAR_COL_GAPS.map(g => g + BAR_COLS),
});
let topBarGridG = topBarGridRes.g;
this.allPins = this.allPins.concat(topBarGridRes.allPins);
//tooltip
this.allPins.forEach(pin => {
let {el, row, col, hoverEl} = pin
let title = `(${row},${col})`;
svg.hydrate(el, {title: title});
svg.hydrate(hoverEl, {title: title});
})
//catalog pins
this.allPins.forEach(pin => {
let colToPin = this.rowColToPin[pin.row];
if (!colToPin)
colToPin = this.rowColToPin[pin.row] = {};
colToPin[pin.col] = pin;
})
//-----labels
const mkBBLabelAtPin = (row: string, col: string, xOffset: number, yOffset: number, txt: string, group?: string): GridLabel => {
let size = PIN_LBL_SIZE;
let rotation = LBL_ROTATION;
let loc = this.getCoord([row, col]);
let [cx, cy] = loc;
let t = mkBBLabel(cx + xOffset, cy + yOffset, size, rotation, txt, group);
return t;
}
//columns
for (let colIdx = 0; colIdx < MID_COLS; colIdx++) {
let colNm = getColName(colIdx);
//top
let rowTIdx = 0;
let rowTNm = getMidRowName(rowTIdx);
let groupT = getMidGroupName(rowTIdx, colIdx);
let lblT = mkBBLabelAtPin(rowTNm, colNm, 0, -PIN_DIST, colNm, groupT);
this.allLabels.push(lblT);
//bottom
let rowBIdx = MID_ROWS - 1;
let rowBNm = getMidRowName(rowBIdx);
let groupB = getMidGroupName(rowBIdx, colIdx);
let lblB = mkBBLabelAtPin(rowBNm, colNm, 0, +PIN_DIST, colNm, groupB);
this.allLabels.push(lblB);
}
//rows
for (let rowIdx = 0; rowIdx < MID_ROWS; rowIdx++) {
let rowNm = getMidRowName(rowIdx);
//top
let colTIdx = 0;
let colTNm = getColName(colTIdx);
let lblT = mkBBLabelAtPin(rowNm, colTNm, -PIN_DIST, 0, rowNm);
this.allLabels.push(lblT);
//top
let colBIdx = MID_COLS - 1;
let colBNm = getColName(colBIdx);
let lblB = mkBBLabelAtPin(rowNm, colBNm, +PIN_DIST, 0, rowNm);
this.allLabels.push(lblB);
}
//+- labels
let botPowerLabels = [
//BL
mkBBLabel(0 + POWER_LBL_OFFSET + MINUS_LBL_EXTRA_OFFSET, BAR_HEIGHT + MID_HEIGHT + POWER_LBL_OFFSET, MINUS_LBL_SIZE, LBL_ROTATION, `-`, getBarGroupName(0, 0), [`sim-bb-blue`]),
mkBBLabel(0 + POWER_LBL_OFFSET, BAR_HEIGHT + MID_HEIGHT + BAR_HEIGHT - POWER_LBL_OFFSET, PLUS_LBL_SIZE, LBL_ROTATION, `+`, getBarGroupName(1, 0), [`sim-bb-red`]),
//BR
mkBBLabel(WIDTH - POWER_LBL_OFFSET + MINUS_LBL_EXTRA_OFFSET, BAR_HEIGHT + MID_HEIGHT + POWER_LBL_OFFSET, MINUS_LBL_SIZE, LBL_ROTATION, `-`, getBarGroupName(0, BAR_COLS - 1), [`sim-bb-blue`]),
mkBBLabel(WIDTH - POWER_LBL_OFFSET, BAR_HEIGHT + MID_HEIGHT + BAR_HEIGHT - POWER_LBL_OFFSET, PLUS_LBL_SIZE, LBL_ROTATION, `+`, getBarGroupName(1, BAR_COLS - 1), [`sim-bb-red`]),
];
this.allLabels = this.allLabels.concat(botPowerLabels);
let topPowerLabels = [
//TL
mkBBLabel(0 + POWER_LBL_OFFSET + MINUS_LBL_EXTRA_OFFSET, 0 + POWER_LBL_OFFSET, MINUS_LBL_SIZE, LBL_ROTATION, `-`, getBarGroupName(0, BAR_COLS), [`sim-bb-blue`]),
mkBBLabel(0 + POWER_LBL_OFFSET, BAR_HEIGHT - POWER_LBL_OFFSET, PLUS_LBL_SIZE, LBL_ROTATION, `+`, getBarGroupName(1, BAR_COLS), [`sim-bb-red`]),
//TR
mkBBLabel(WIDTH - POWER_LBL_OFFSET + MINUS_LBL_EXTRA_OFFSET, 0 + POWER_LBL_OFFSET, MINUS_LBL_SIZE, LBL_ROTATION, `-`, getBarGroupName(0, POWER_COLS - 1), [`sim-bb-blue`]),
mkBBLabel(WIDTH - POWER_LBL_OFFSET, BAR_HEIGHT - POWER_LBL_OFFSET, PLUS_LBL_SIZE, LBL_ROTATION, `+`, getBarGroupName(1, POWER_COLS - 1), [`sim-bb-red`]),
];
this.allLabels = this.allLabels.concat(topPowerLabels);
//catalog lbls
let lblNmToLbls: Map<GridLabel[]> = {};
this.allLabels.forEach(lbl => {
let {el, txt} = lbl;
let lbls = lblNmToLbls[txt] = lblNmToLbls[txt] || []
lbls.push(lbl);
});
const isPowerPin = (pin: GridPin) => pin.row === "-" || pin.row === "+";
this.allPins.forEach(pin => {
let {row, col, group} = pin;
let colToLbls = this.rowColToLbls[row] || (this.rowColToLbls[row] = {});
let lbls = colToLbls[col] || (colToLbls[col] = []);
if (isPowerPin(pin)) {
//power pins
let isBot = Number(col) <= BAR_COLS;
if (isBot)
botPowerLabels.filter(l => l.group == pin.group).forEach(l => lbls.push(l));
else
topPowerLabels.filter(l => l.group == pin.group).forEach(l => lbls.push(l));
} else {
//mid pins
let rowLbls = lblNmToLbls[row];
rowLbls.forEach(l => lbls.push(l));
let colLbls = lblNmToLbls[col];
colLbls.forEach(l => lbls.push(l));
}
})
//-----blue & red lines
const lnLen = BAR_GRID_WIDTH + PIN_DIST * 1.5;
const lnThickness = PIN_DIST / 5.0;
const lnYOff = PIN_DIST * 0.6;
const lnXOff = (lnLen - BAR_GRID_WIDTH) / 2.0;
const mkPowerLine = (x: number, y: number, group: string, cls: string): BBBar => {
let ln = <SVGRectElement>svg.elt("rect");
svg.hydrate(ln, {
class: `sim-bb-bar ${cls}`,
x: x,
y: y - lnThickness / 2.0,
width: lnLen,
height: lnThickness});
let bar: BBBar = {el: ln, group: group};
return bar;
}
let barLines = [
//top
mkPowerLine(BAR_BOT_GRID_X - lnXOff, BAR_BOT_GRID_Y - lnYOff, getBarGroupName(0, POWER_COLS - 1), "sim-bb-blue"),
mkPowerLine(BAR_BOT_GRID_X - lnXOff, BAR_BOT_GRID_Y + PIN_DIST + lnYOff, getBarGroupName(1, POWER_COLS - 1), "sim-bb-red"),
//bot
mkPowerLine(BAR_TOP_GRID_X - lnXOff, BAR_TOP_GRID_Y - lnYOff, getBarGroupName(0, 0), "sim-bb-blue"),
mkPowerLine(BAR_TOP_GRID_X - lnXOff, BAR_TOP_GRID_Y + PIN_DIST + lnYOff, getBarGroupName(1, 0), "sim-bb-red"),
];
this.allPowerBars = this.allPowerBars.concat(barLines);
//attach power bars
this.allPowerBars.forEach(b => this.bb.appendChild(b.el));
//-----electrically connected groups
//make groups
let allGrpNms = this.allPins.map(p => p.group).filter((g, i, a) => a.indexOf(g) == i);
let groups: SVGGElement[] = allGrpNms.map(grpNm => {
let g = <SVGGElement>svg.elt("g");
return g;
});
groups.forEach(g => svg.addClass(g, "sim-bb-pin-group"));
groups.forEach((g, i) => svg.addClass(g, `group-${allGrpNms[i]}`));
let grpNmToGroup: Map<SVGGElement> = {};
allGrpNms.forEach((g, i) => grpNmToGroup[g] = groups[i]);
//group pins and add connecting wire
let grpNmToPins: Map<GridPin[]> = {};
this.allPins.forEach((p, i) => {
let g = p.group;
let pins = grpNmToPins[g] || (grpNmToPins[g] = []);
pins.push(p);
});
//connecting wire
allGrpNms.forEach(grpNm => {
let pins = grpNmToPins[grpNm];
let [xs, ys] = [pins.map(p => p.cx), pins.map(p => p.cy)];
let minFn = (arr: number[]) => arr.reduce((a, b) => a < b ? a : b);
let maxFn = (arr: number[]) => arr.reduce((a, b) => a > b ? a : b);
let [minX, maxX, minY, maxY] = [minFn(xs), maxFn(xs), minFn(ys), maxFn(ys)];
let wire = svg.elt("rect");
let width = Math.max(maxX - minX, 0.0001/*rects with no width aren't displayed*/);
let height = Math.max(maxY - minY, 0.0001);
svg.hydrate(wire, {x: minX, y: minY, width: width, height: height});
svg.addClass(wire, "sim-bb-group-wire")
let g = grpNmToGroup[grpNm];
g.appendChild(wire);
});
//group pins
this.allPins.forEach(p => {
let g = grpNmToGroup[p.group];
g.appendChild(p.el);
g.appendChild(p.hoverEl);
})
//group lbls
let miscLblGroup = <SVGGElement>svg.elt("g");
svg.hydrate(miscLblGroup, {class: "sim-bb-group-misc"});
groups.push(miscLblGroup);
this.allLabels.forEach(l => {
if (l.group) {
let g = grpNmToGroup[l.group];
g.appendChild(l.el);
g.appendChild(l.hoverEl);
} else {
miscLblGroup.appendChild(l.el);
miscLblGroup.appendChild(l.hoverEl);
}
})
//attach to bb
groups.forEach(g => this.bb.appendChild(g)); //attach to breadboard
}
public getSVGAndSize(): SVGAndSize<SVGSVGElement> {
return {el: this.bb, y: 0, x: 0, w: WIDTH, h: HEIGHT};
}
public highlightLoc(rowCol: BBRowCol) {
let [row, col] = rowCol;
let pin = this.rowColToPin[row][col];
let {cx, cy} = pin;
let lbls = this.rowColToLbls[row][col];
const highlightLbl = (lbl: GridLabel) => {
svg.addClass(lbl.el, "highlight");
svg.addClass(lbl.hoverEl, "highlight");
};
lbls.forEach(highlightLbl);
}
}
}

204
sim/visuals/buttonpair.ts Normal file
View File

@ -0,0 +1,204 @@
/// <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.visuals {
export function mkBtnSvg(xy: Coord): SVGAndSize<SVGGElement> {
let [innerCls, outerCls] = ["sim-button", "sim-button-outer"];
const tabSize = PIN_DIST / 2.5;
const pegR = PIN_DIST / 5;
const btnR = PIN_DIST * .8;
const pegMargin = PIN_DIST / 8;
const plateR = PIN_DIST / 12;
const pegOffset = pegMargin + pegR;
let [x, y] = xy;
const left = x - tabSize / 2;
const top = y - tabSize / 2;
const plateH = 3 * PIN_DIST - tabSize;
const plateW = 2 * PIN_DIST + tabSize;
const plateL = left;
const plateT = top + tabSize;
const btnCX = plateL + plateW / 2;
const btnCY = plateT + plateH / 2;
let btng = <SVGGElement>svg.elt("g");
//tabs
const mkTab = (x: number, y: number) => {
svg.child(btng, "rect", { class: "sim-button-tab", x: x, y: y, width: tabSize, height: tabSize})
}
mkTab(left, top);
mkTab(left + 2 * PIN_DIST, top);
mkTab(left, top + 3 * PIN_DIST);
mkTab(left + 2 * PIN_DIST, top + 3 * PIN_DIST);
//plate
svg.child(btng, "rect", { class: outerCls, x: plateL, y: plateT, rx: plateR, ry: plateR, width: plateW, height: plateH });
//pegs
const mkPeg = (x: number, y: number) => {
svg.child(btng, "circle", { class: "sim-button-nut", cx: x, cy: y, r: pegR });
}
mkPeg(plateL + pegOffset, plateT + pegOffset)
mkPeg(plateL + plateW - pegOffset, plateT + pegOffset)
mkPeg(plateL + pegOffset, plateT + plateH - pegOffset)
mkPeg(plateL + plateW - pegOffset, plateT + plateH - pegOffset)
//inner btn
let innerBtn = svg.child(btng, "circle", { class: innerCls, cx: btnCX, cy: btnCY, r: btnR });
//return
return { el: btng, y: top, x: left, w: plateW, h: plateH + 2 * tabSize };
}
export const BUTTON_PAIR_STYLE = `
.sim-button {
pointer-events: none;
fill: #000;
}
.sim-button-outer:active ~ .sim-button,
.sim-button-virtual:active {
fill: #FFA500;
}
.sim-button-outer {
cursor: pointer;
fill: #979797;
}
.sim-button-outer:hover {
stroke:gray;
stroke-width: ${PIN_DIST / 5}px;
}
.sim-button-nut {
fill:#000;
pointer-events:none;
}
.sim-button-nut:hover {
stroke:${PIN_DIST / 15}px solid #704A4A;
}
.sim-button-tab {
fill:#FFF;
pointer-events:none;
}
.sim-button-virtual {
cursor: pointer;
fill: rgba(255, 255, 255, 0.6);
stroke: rgba(255, 255, 255, 1);
stroke-width: ${PIN_DIST / 5}px;
}
.sim-button-virtual:hover {
stroke: rgba(128, 128, 128, 1);
}
.sim-text-virtual {
fill: #000;
pointer-events:none;
}
`;
export class ButtonPairView implements IBoardComponent<ButtonPairState> {
public element: SVGElement;
public defs: SVGElement[];
public style = BUTTON_PAIR_STYLE;
private state: ButtonPairState;
private bus: EventBus;
private aBtn: SVGGElement;
private bBtn: SVGGElement;
private abBtn: SVGGElement;
public init(bus: EventBus, state: ButtonPairState) {
this.state = state;
this.bus = bus;
this.defs = [];
this.element = this.mkBtns();
this.updateState();
this.attachEvents();
}
public moveToCoord(xy: Coord) {
let btnWidth = PIN_DIST * 3;
let [x, y] = xy;
translateEl(this.aBtn, [x, y])
translateEl(this.bBtn, [x + btnWidth, y])
translateEl(this.abBtn, [x + PIN_DIST * 1.5, y + PIN_DIST * 4])
}
public updateState() {
let stateBtns = [this.state.aBtn, this.state.bBtn, this.state.abBtn];
let svgBtns = [this.aBtn, this.bBtn, this.abBtn];
if (this.state.usesButtonAB && this.abBtn.style.visibility != "visible") {
this.abBtn.style.visibility = "visible";
}
}
public updateTheme() {}
private mkBtns() {
this.aBtn = mkBtnSvg([0, 0]).el;
this.bBtn = mkBtnSvg([0, 0]).el;
const mkVirtualBtn = () => {
const numPins = 2;
const w = PIN_DIST * 2.8;
const offset = (w - (numPins * PIN_DIST)) / 2;
const corner = PIN_DIST / 2;
const cx = 0 - offset + w / 2;
const cy = cx;
const txtSize = PIN_DIST * 1.3;
const x = -offset;
const y = -offset;
const txtXOff = PIN_DIST / 7;
const txtYOff = PIN_DIST / 10;
let btng = <SVGGElement>svg.elt("g");
let btn = svg.child(btng, "rect", { class: "sim-button-virtual", x: x, y: y, rx: corner, ry: corner, width: w, height: w});
let btnTxt = mkTxt(cx + txtXOff, cy + txtYOff, txtSize, 0, "A+B");
svg.addClass(btnTxt, "sim-text")
svg.addClass(btnTxt, "sim-text-virtual");
btng.appendChild(btnTxt);
return btng;
}
this.abBtn = mkVirtualBtn();
this.abBtn.style.visibility = "hidden";
let el = svg.elt("g");
svg.addClass(el, "sim-buttonpair")
el.appendChild(this.aBtn);
el.appendChild(this.bBtn);
el.appendChild(this.abBtn);
return el;
}
private attachEvents() {
let btnStates = [this.state.aBtn, this.state.bBtn];
let btnSvgs = [this.aBtn, this.bBtn];
btnSvgs.forEach((btn, index) => {
btn.addEventListener(pointerEvents.down, ev => {
btnStates[index].pressed = true;
})
btn.addEventListener(pointerEvents.leave, ev => {
btnStates[index].pressed = false;
})
btn.addEventListener(pointerEvents.up, ev => {
btnStates[index].pressed = false;
this.bus.queue(btnStates[index].id, DAL.MICROBIT_BUTTON_EVT_UP);
this.bus.queue(btnStates[index].id, DAL.MICROBIT_BUTTON_EVT_CLICK);
})
})
let updateBtns = (s: boolean) => {
btnStates.forEach(b => b.pressed = s)
};
this.abBtn.addEventListener(pointerEvents.down, ev => {
updateBtns(true);
})
this.abBtn.addEventListener(pointerEvents.leave, ev => {
updateBtns(false);
})
this.abBtn.addEventListener(pointerEvents.up, ev => {
updateBtns(false);
this.bus.queue(this.state.abBtn.id, DAL.MICROBIT_BUTTON_EVT_UP);
this.bus.queue(this.state.abBtn.id, DAL.MICROBIT_BUTTON_EVT_CLICK);
})
}
}
}

515
sim/visuals/genericboard.ts Normal file
View File

@ -0,0 +1,515 @@
/// <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.visuals {
const svg = pxsim.svg;
export interface IBoardSvgProps {
runtime: pxsim.Runtime;
boardDef: BoardDefinition;
disableTilt?: boolean;
activeComponents: string[];
fnArgs?: any;
componentDefinitions: Map<PartDefinition>;
}
export const VIEW_WIDTH = 498;
export const VIEW_HEIGHT = 725;
const TOP_MARGIN = 20;
const MID_MARGIN = 40;
const BOT_MARGIN = 20;
const PIN_LBL_SIZE = PIN_DIST * 0.7;
const PIN_LBL_HOVER_SIZE = PIN_LBL_SIZE * 1.5;
const SQUARE_PIN_WIDTH = PIN_DIST * 0.66666;
const SQUARE_PIN_HOVER_WIDTH = PIN_DIST * 0.66666 + PIN_DIST / 3.0;
export type ComputedBoardDimensions = {
scaleFn: (n: number) => number,
height: number,
width: number,
xOff: number,
yOff: number
};
export function getBoardDimensions(vis: BoardImageDefinition): ComputedBoardDimensions {
let scaleFn = (n: number) => n * (PIN_DIST / vis.pinDist);
let width = scaleFn(vis.width);
return {
scaleFn: scaleFn,
height: scaleFn(vis.height),
width: width,
xOff: (VIEW_WIDTH - width) / 2.0,
yOff: TOP_MARGIN
}
}
export const BOARD_SYTLE = `
.noselect {
-webkit-touch-callout: none; /* iOS Safari */
-webkit-user-select: none; /* Chrome/Safari/Opera */
-khtml-user-select: none; /* Konqueror */
-moz-user-select: none; /* Firefox */
-ms-user-select: none; /* Internet Explorer/Edge */
user-select: none; /* Non-prefixed version, currently
not supported by any browser */
}
svg.sim.grayscale {
-moz-filter: grayscale(1);
-webkit-filter: grayscale(1);
filter: grayscale(1);
}
.sim-text {
font-family:"Lucida Console", Monaco, monospace;
font-size:25px;
fill:#fff;
pointer-events: none;
}
/* 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; }
}
.sim-board-pin {
fill:#999;
stroke:#000;
stroke-width:${PIN_DIST / 3.0}px;
}
.sim-board-pin-lbl {
fill: #333;
}
.gray-cover {
fill:#FFF;
opacity: 0.7;
stroke-width:0;
visibility: hidden;
}
.sim-board-pin-hover {
visibility: hidden;
pointer-events: all;
stroke-width:${PIN_DIST / 6.0}px;
}
.sim-board-pin-hover:hover {
visibility: visible;
}
.sim-board-pin-lbl {
visibility: hidden;
}
.sim-board-outline .sim-board-pin-lbl {
visibility: visible;
}
.sim-board-pin-lbl {
fill: #555;
}
.sim-board-pin-lbl-hover {
fill: red;
}
.sim-board-outline .sim-board-pin-lbl-hover {
fill: black;
}
.sim-board-pin-lbl,
.sim-board-pin-lbl-hover {
font-family:"Lucida Console", Monaco, monospace;
pointer-events: all;
stroke-width: 0;
}
.sim-board-pin-lbl-hover {
visibility: hidden;
}
.sim-board-outline .sim-board-pin-hover:hover + .sim-board-pin-lbl,
.sim-board-pin-lbl.highlight {
visibility: hidden;
}
.sim-board-outline .sim-board-pin-hover:hover + * + .sim-board-pin-lbl-hover,
.sim-board-pin-lbl-hover.highlight {
visibility: visible;
}
/* Graying out */
.grayed .sim-board-pin-lbl:not(.highlight) {
fill: #AAA;
}
.grayed .sim-board-pin:not(.highlight) {
fill:#BBB;
stroke:#777;
}
.grayed .gray-cover {
visibility: inherit;
}
.grayed .sim-cmp:not(.notgrayed) {
opacity: 0.3;
}
/* Highlighting */
.sim-board-pin-lbl.highlight {
fill: #000;
font-weight: bold;
}
.sim-board-pin.highlight {
fill:#999;
stroke:#000;
}
`;
let nextBoardId = 0;
export class GenericBoardSvg /*TODO: implements BoardView*/ {
public hostElement: SVGSVGElement;
private style: SVGStyleElement;
private defs: SVGDefsElement;
private g: SVGGElement;
public board: pxsim.DalBoard;
public background: SVGElement;
private components: IBoardComponent<any>[];
public breadboard: Breadboard;
private underboard: SVGGElement;
public boardDef: BoardDefinition;
private boardDim: ComputedBoardDimensions;
public componentDefs: Map<PartDefinition>;
private boardEdges: number[];
private id: number;
public bbX: number;
public bbY: number;
private boardTopEdge: number;
private boardBotEdge: number;
private wireFactory: WireFactory;
//truth
private allPins: GridPin[] = [];
private allLabels: GridLabel[] = [];
//cache
private pinNmToLbl: Map<GridLabel> = {};
private pinNmToPin: Map<GridPin> = {};
constructor(public props: IBoardSvgProps) {
this.id = nextBoardId++;
this.boardDef = props.boardDef;
this.boardDim = getBoardDimensions(<BoardImageDefinition>this.boardDef.visual);
this.board = this.props.runtime.board as pxsim.DalBoard;
this.board.updateView = () => this.updateState();
this.hostElement = <SVGSVGElement>svg.elt("svg")
svg.hydrate(this.hostElement, {
"version": "1.0",
"viewBox": `0 0 ${VIEW_WIDTH} ${VIEW_HEIGHT}`,
"enable-background": `new 0 0 ${VIEW_WIDTH} ${VIEW_HEIGHT}`,
"class": `sim sim-board-id-${this.id}`,
"x": "0px",
"y": "0px"
});
this.style = <SVGStyleElement>svg.child(this.hostElement, "style", {});
this.style.textContent += BOARD_SYTLE;
this.defs = <SVGDefsElement>svg.child(this.hostElement, "defs", {});
this.g = <SVGGElement>svg.elt("g");
this.hostElement.appendChild(this.g);
this.underboard = <SVGGElement>svg.child(this.g, "g", {class: "sim-underboard"});
this.components = [];
this.componentDefs = props.componentDefinitions;
// breadboard
this.breadboard = new Breadboard({})
this.g.appendChild(this.breadboard.bb);
let bbSize = this.breadboard.getSVGAndSize();
let [bbWidth, bbHeight] = [bbSize.w, bbSize.h];
const bbX = (VIEW_WIDTH - bbWidth) / 2;
this.bbX = bbX;
const bbY = TOP_MARGIN + this.boardDim.height + MID_MARGIN;
this.bbY = bbY;
this.breadboard.updateLocation(bbX, bbY);
// edges
this.boardTopEdge = TOP_MARGIN;
this.boardBotEdge = TOP_MARGIN + this.boardDim.height;
this.boardEdges = [this.boardTopEdge, this.boardBotEdge, bbY, bbY + bbHeight]
this.wireFactory = new WireFactory(this.underboard, this.g, this.boardEdges, this.style, this.getLocCoord.bind(this));
this.buildDom();
this.updateTheme();
this.updateState();
let cmps = props.activeComponents;
if (cmps.length) {
let allocRes = allocateDefinitions({
boardDef: this.boardDef,
cmpDefs: this.componentDefs,
fnArgs: this.props.fnArgs,
getBBCoord: this.getBBCoord.bind(this),
cmpList: props.activeComponents,
});
this.addAll(allocRes);
}
}
private getBoardPinCoord(pinNm: string): Coord {
let pin = this.pinNmToPin[pinNm];
if (!pin)
return null;
return [pin.cx, pin.cy];
}
private getBBCoord(rowCol: BBRowCol): Coord {
let bbCoord = this.breadboard.getCoord(rowCol);
if (!bbCoord)
return null;
let [x, y] = bbCoord;
return [x + this.bbX, y + this.bbY];
}
public getLocCoord(loc: Loc): Coord {
let coord: Coord;
if (loc.type === "breadboard") {
let rowCol = (<BBLoc>loc).rowCol;
coord = this.getBBCoord(rowCol);
} else {
let pinNm = (<BoardLoc>loc).pin;
coord = this.getBoardPinCoord(pinNm);
}
if (!coord) {
console.error("Unknown location: " + name)
return [0, 0];
}
return coord;
}
private mkGrayCover(x: number, y: number, w: number, h: number) {
let rect = <SVGRectElement>svg.elt("rect");
svg.hydrate(rect, {x: x, y: y, width: w, height: h, class: "gray-cover"});
return rect;
}
private getCmpClass = (type: string) => `sim-${type}-cmp`;
public addWire(inst: WireInst): Wire {
return this.wireFactory.addWire(inst.start, inst.end, inst.color);
}
public addAll(basicWiresAndCmpsAndWires: AllocatorResult) {
let {powerWires, components} = basicWiresAndCmpsAndWires;
powerWires.forEach(w => this.addWire(w));
components.forEach((cAndWs, idx) => {
let {component, wires} = cAndWs;
wires.forEach(w => this.addWire(w));
this.addComponent(component);
});
}
public addComponent(cmpDesc: CmpInst): IBoardComponent<any> {
let cmp: IBoardComponent<any> = null;
if (typeof cmpDesc.visual === "string") {
let builtinVisual = cmpDesc.visual as string;
let cnstr = builtinComponentSimVisual[builtinVisual];
let stateFn = builtinComponentSimState[builtinVisual];
let state = stateFn(this.board);
cmp = cnstr();
cmp.init(this.board.bus, state, this.hostElement, cmpDesc.microbitPins, cmpDesc.otherArgs);
this.components.push(cmp);
this.g.appendChild(cmp.element);
if (cmp.defs)
cmp.defs.forEach(d => this.defs.appendChild(d));
this.style.textContent += cmp.style || "";
let rowCol = <BBRowCol>[`${cmpDesc.breadboardStartRow}`, `${cmpDesc.breadboardStartColumn}`];
let coord = this.getBBCoord(rowCol);
cmp.moveToCoord(coord);
let cls = this.getCmpClass(name);
svg.addClass(cmp.element, cls);
svg.addClass(cmp.element, "sim-cmp");
cmp.updateTheme();
cmp.updateState();
} else {
//TODO: adding generic components
}
return cmp;
}
private updateTheme() {
this.components.forEach(c => c.updateTheme());
}
public updateState() {
let state = this.board;
if (!state) return;
this.components.forEach(c => c.updateState());
if (!runtime || runtime.dead) svg.addClass(this.hostElement, "grayscale");
else svg.removeClass(this.hostElement, "grayscale");
}
private buildDom() {
// filters
let glow = svg.child(this.defs, "filter", { id: "filterglow", x: "-5%", y: "-5%", width: "120%", height: "120%" });
svg.child(glow, "feGaussianBlur", { stdDeviation: "5", result: "glow" });
let merge = svg.child(glow, "feMerge", {});
for (let i = 0; i < 3; ++i)
svg.child(merge, "feMergeNode", { in: "glow" })
// main board
this.background = svg.child(this.g, "image",
{ class: "sim-board", x: this.boardDim.xOff, y: this.boardDim.yOff, width: this.boardDim.width, height: this.boardDim.height,
"href": `${(<BoardImageDefinition>this.boardDef.visual).image}`});
let backgroundCover = this.mkGrayCover(this.boardDim.xOff, this.boardDim.yOff, this.boardDim.width, this.boardDim.height);
this.g.appendChild(backgroundCover);
// ----- pins
const mkSquarePin = (): SVGElAndSize => {
let el = svg.elt("rect");
let width = SQUARE_PIN_WIDTH;
svg.hydrate(el, {
class: "sim-board-pin",
width: width,
height: width,
});
return {el: el, w: width, h: width, x: 0, y: 0};
}
const mkSquareHoverPin = (): SVGElAndSize => {
let el = svg.elt("rect");
let width = SQUARE_PIN_HOVER_WIDTH;
svg.hydrate(el, {
class: "sim-board-pin-hover",
width: width,
height: width
});
return {el: el, w: width, h: width, x: 0, y: 0};
}
const mkPinBlockGrid = (pinBlock: PinBlockDefinition, blockIdx: number) => {
let xOffset = this.boardDim.xOff + this.boardDim.scaleFn(pinBlock.x) + PIN_DIST / 2.0;
let yOffset = this.boardDim.yOff + this.boardDim.scaleFn(pinBlock.y) + PIN_DIST / 2.0;
let rowCount = 1;
let colCount = pinBlock.labels.length;
let getColName = (colIdx: number) => pinBlock.labels[colIdx];
let getRowName = () => `${blockIdx + 1}`
let getGroupName = () => pinBlock.labels.join(" ");
let gridRes = mkGrid({
xOffset: xOffset,
yOffset: yOffset,
rowCount: rowCount,
colCount: colCount,
pinDist: PIN_DIST,
mkPin: mkSquarePin,
mkHoverPin: mkSquareHoverPin,
getRowName: getRowName,
getColName: getColName,
getGroupName: getGroupName,
});
let pins = gridRes.allPins;
let pinsG = gridRes.g;
svg.addClass(gridRes.g, "sim-board-pin-group");
return gridRes;
};
let pinBlocks = (<BoardImageDefinition>this.boardDef.visual).pinBlocks.map(mkPinBlockGrid);
pinBlocks.forEach(blk => blk.allPins.forEach(p => {
this.allPins.push(p);
}));
//tooltip
this.allPins.forEach(p => {
let tooltip = p.col;
svg.hydrate(p.el, {title: tooltip});
svg.hydrate(p.hoverEl, {title: tooltip});
});
//attach pins
this.allPins.forEach(p => {
this.g.appendChild(p.el);
this.g.appendChild(p.hoverEl);
});
//catalog pins
this.allPins.forEach(p => {
this.pinNmToPin[p.col] = p;
});
// ----- labels
const mkLabelTxtEl = (pinX: number, pinY: number, size: number, txt: string): SVGTextElement => {
//TODO: extract constants
let lblY: number;
let lblX: number;
let edges = [this.boardTopEdge, this.boardBotEdge];
let distFromTopBot = edges.map(e => Math.abs(e - pinY));
let closestEdgeIdx = distFromTopBot.reduce((pi, n, ni) => n < distFromTopBot[pi] ? ni : pi, 0);
let topEdge = closestEdgeIdx == 0;
if (topEdge) {
let lblLen = size * 0.25 * txt.length;
lblX = pinX;
lblY = pinY + 12 + lblLen;
} else {
let lblLen = size * 0.32 * txt.length;
lblX = pinX;
lblY = pinY - 11 - lblLen;
}
let el = mkTxt(lblX, lblY, size, -90, txt);
return el;
};
const mkLabel = (pinX: number, pinY: number, txt: string): GridLabel => {
let el = mkLabelTxtEl(pinX, pinY, PIN_LBL_SIZE, txt);
svg.addClass(el, "sim-board-pin-lbl");
let hoverEl = mkLabelTxtEl(pinX, pinY, PIN_LBL_HOVER_SIZE, txt);
svg.addClass(hoverEl, "sim-board-pin-lbl-hover");
let label: GridLabel = {el: el, hoverEl: hoverEl, txt: txt};
return label;
}
this.allLabels = this.allPins.map(p => {
return mkLabel(p.cx, p.cy, p.col);
});
//attach labels
this.allLabels.forEach(l => {
this.g.appendChild(l.el);
this.g.appendChild(l.hoverEl);
});
//catalog labels
this.allPins.forEach((pin, pinIdx) => {
let lbl = this.allLabels[pinIdx];
this.pinNmToLbl[pin.col] = lbl;
});
}
public highlightLoc(pinNm: string) {
let lbl = this.pinNmToLbl[pinNm];
let pin = this.pinNmToPin[pinNm];
if (lbl && pin) {
svg.addClass(lbl.el, "highlight");
svg.addClass(lbl.hoverEl, "highlight");
svg.addClass(pin.el, "highlight");
svg.addClass(pin.hoverEl, "highlight");
}
}
public highlightWire(wire: Wire) {
//underboard wires
wire.wires.forEach(e => {
(<any>e).style["visibility"] = "visible";
});
//un greyed out
[wire.end1, wire.end2].forEach(e => {
svg.addClass(e, "highlight");
});
wire.wires.forEach(e => {
svg.addClass(e, "highlight");
});
}
}
}

View File

@ -0,0 +1,16 @@
namespace pxsim.visuals {
export class GenericComponentView implements IBoardComponent<any> {
public style: string;
public element: SVGElement;
defs: SVGElement[];
init(bus: EventBus, state: any, svgEl: SVGSVGElement, gpioPins: string[], otherArgs: string[]): void {
}
moveToCoord(xy: Coord): void {
}
updateState(): void {
}
updateTheme(): void {
}
}
}

130
sim/visuals/ledmatrix.ts Normal file
View File

@ -0,0 +1,130 @@
/// <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.visuals {
export function mkLedMatrixSvg(xy: Coord, rows: number, cols: number):
{el: SVGGElement, y: number, x: number, w: number, h: number, leds: SVGElement[], ledsOuter: SVGElement[], background: SVGElement} {
let result: {el: SVGGElement, y: number, x: number, w: number, h: number, leds: SVGElement[], ledsOuter: SVGElement[], background: SVGElement}
= {el: null, y: 0, x: 0, w: 0, h: 0, leds: [], ledsOuter: [], background: null};
result.el = <SVGGElement>svg.elt("g");
let width = cols * PIN_DIST;
let height = rows * PIN_DIST;
let ledRad = Math.round(PIN_DIST * .35);
let spacing = PIN_DIST;
let padding = (spacing - 2 * ledRad) / 2.0;
let [x, y] = xy;
let left = x - (ledRad + padding);
let top = y - (ledRad + padding);
result.x = left;
result.y = top;
result.w = width;
result.h = height;
result.background = svg.child(result.el, "rect", {class: "sim-display", x: left, y: top, width: width, height: height})
// ledsOuter
result.leds = [];
result.ledsOuter = [];
let hoverRad = ledRad * 1.2;
for (let i = 0; i < rows; ++i) {
let y = top + ledRad + i * spacing + padding;
for (let j = 0; j < cols; ++j) {
let x = left + ledRad + j * spacing + padding;
result.ledsOuter.push(svg.child(result.el, "circle", { class: "sim-led-back", cx: x, cy: y, r: ledRad }));
result.leds.push(svg.child(result.el, "circle", { class: "sim-led", cx: x, cy: y, r: hoverRad, title: `(${j},${i})` }));
}
}
//default theme
svg.fill(result.background, defaultLedMatrixTheme.background);
svg.fills(result.leds, defaultLedMatrixTheme.ledOn);
svg.fills(result.ledsOuter, defaultLedMatrixTheme.ledOff);
//turn off LEDs
result.leds.forEach(l => (<SVGStylable><any>l).style.opacity = 0 + "");
return result;
}
export interface ILedMatrixTheme {
background?: string;
ledOn?: string;
ledOff?: string;
}
export var defaultLedMatrixTheme: ILedMatrixTheme = {
background: "#000",
ledOn: "#ff5f5f",
ledOff: "#DDD",
};
export const LED_MATRIX_STYLE = `
.sim-led-back:hover {
stroke:#a0a0a0;
stroke-width:3px;
}
.sim-led:hover {
stroke:#ff7f7f;
stroke-width:3px;
}
`
export class LedMatrixView implements IBoardComponent<LedMatrixState> {
private background: SVGElement;
private ledsOuter: SVGElement[];
private leds: SVGElement[];
private state: LedMatrixState;
private bus: EventBus;
public element: SVGElement;
public defs: SVGElement[];
private theme: ILedMatrixTheme;
private DRAW_SIZE = 8;
private ACTIVE_SIZE = 5;
public style = LED_MATRIX_STYLE;
public init(bus: EventBus, state: LedMatrixState) {
this.bus = bus;
this.state = state;
this.theme = defaultLedMatrixTheme;
this.defs = [];
this.element = this.buildDom();
}
public moveToCoord(xy: Coord) {
let [x, y] = xy;
translateEl(this.element, [x, y]);
}
public updateTheme() {
svg.fill(this.background, this.theme.background);
svg.fills(this.leds, this.theme.ledOn);
svg.fills(this.ledsOuter, this.theme.ledOff);
}
public updateState() {
let bw = this.state.displayMode == pxsim.DisplayMode.bw
let img = this.state.image;
this.leds.forEach((led, i) => {
let sel = (<SVGStylable><any>led)
let dx = i % this.DRAW_SIZE;
let dy = (i - dx) / this.DRAW_SIZE;
if (dx < this.ACTIVE_SIZE && dy < this.ACTIVE_SIZE) {
let j = dx + dy * this.ACTIVE_SIZE;
sel.style.opacity = ((bw ? img.data[j] > 0 ? 255 : 0 : img.data[j]) / 255.0) + "";
} else {
sel.style.opacity = 0 + "";
}
})
}
public buildDom() {
let res = mkLedMatrixSvg([0, 0], this.DRAW_SIZE, this.DRAW_SIZE);
let display = res.el;
this.background = res.background;
this.leds = res.leds;
this.ledsOuter = res.ledsOuter;
return display;
}
}
}

256
sim/visuals/neopixel.ts Normal file
View File

@ -0,0 +1,256 @@
/// <reference path="../../node_modules/pxt-core/built/pxtsim.d.ts"/>
/// <reference path="../../libs/microbit/dal.d.ts"/>
/// <reference path="../../libs/microbit/shims.d.ts"/>
/// <reference path="../../libs/microbit/enums.d.ts"/>
/// <reference path="../state/neopixel.ts"/>
/// <reference path="../simlib.ts"/>
//TODO move to utils
namespace pxsim.visuals {
//expects rgb from 0,255, gives h in [0,360], s in [0, 100], l in [0, 100]
export function rgbToHsl(rgb: [number, number, number]): [number, number, number] {
let [r, g, b] = rgb;
let [r$, g$, b$] = [r / 255, g / 255, b / 255];
let cMin = Math.min(r$, g$, b$);
let cMax = Math.max(r$, g$, b$);
let cDelta = cMax - cMin;
let h: number, s: number, l: number;
let maxAndMin = cMax + cMin;
//lum
l = (maxAndMin / 2) * 100
if (cDelta === 0)
s = h = 0;
else {
//hue
if (cMax === r$)
h = 60 * (((g$ - b$) / cDelta) % 6);
else if (cMax === g$)
h = 60 * (((b$ - r$) / cDelta) + 2);
else if (cMax === b$)
h = 60 * (((r$ - g$) / cDelta) + 4);
//sat
if (l > 50)
s = 100 * (cDelta / (2 - maxAndMin));
else
s = 100 * (cDelta / maxAndMin);
}
return [Math.floor(h), Math.floor(s), Math.floor(l)];
}
}
namespace pxsim.visuals {
const PIXEL_SPACING = PIN_DIST * 3;
const PIXEL_RADIUS = PIN_DIST;
const CANVAS_WIDTH = 1.2 * PIN_DIST;
const CANVAS_HEIGHT = 12 * PIN_DIST;
const CANVAS_VIEW_WIDTH = CANVAS_WIDTH;
const CANVAS_VIEW_HEIGHT = CANVAS_HEIGHT;
const CANVAS_VIEW_PADDING = PIN_DIST * 4;
const CANVAS_LEFT = 1.4 * PIN_DIST;
const CANVAS_TOP = PIN_DIST;
// For the instructions parts list
export function mkNeoPixelPart(xy: Coord = [0, 0]): SVGElAndSize {
const NP_PART_XOFF = -13.5;
const NP_PART_YOFF = -11;
const NP_PART_WIDTH = 87.5;
const NP_PART_HEIGHT = 190;
const NEOPIXEL_PART_IMG = "neopixel.svg";
let [x, y] = xy;
let l = x + NP_PART_XOFF;
let t = y + NP_PART_YOFF;
let w = NP_PART_WIDTH;
let h = NP_PART_HEIGHT;
let img = <SVGImageElement>svg.elt("image");
svg.hydrate(img, {class: "sim-neopixel-strip", x: l, y: t, width: w, height: h,
href: `/static/hardware/${NEOPIXEL_PART_IMG}`});
return {el: img, x: l, y: t, w: w, h: h};
}
export class NeoPixel implements SVGAndSize<SVGCircleElement> {
public el: SVGCircleElement;
public w: number;
public h: number;
public x: number;
public y: number;
public cx: number;
public cy: number;
constructor(xy: Coord = [0, 0]) {
let circle = <SVGCircleElement>svg.elt("circle");
let r = PIXEL_RADIUS;
let [cx, cy] = xy;
svg.hydrate(circle, {cx: cx, cy: cy, r: r, class: "sim-neopixel"});
this.el = circle;
this.w = r * 2;
this.h = r * 2;
this.x = cx - r;
this.y = cy - r;
this.cx = cx;
this.cy = cy;
}
public setRgb(rgb: [number, number, number]) {
let hsl = rgbToHsl(rgb);
let [h, s, l] = hsl;
//We ignore luminosity since it doesn't map well to real-life brightness
let fill = `hsl(${h}, ${s}%, 70%)`;
this.el.setAttribute("fill", fill);
}
}
export class NeoPixelCanvas {
public canvas: SVGSVGElement;
public pin: number;
public pixels: NeoPixel[];
private viewBox: [number, number, number, number];
private background: SVGRectElement;
constructor(pin: number) {
this.pixels = [];
this.pin = pin;
let el = <SVGSVGElement>svg.elt("svg");
svg.hydrate(el, {
"class": `sim-neopixel-canvas`,
"x": "0px",
"y": "0px",
"width": `${CANVAS_WIDTH}px`,
"height": `${CANVAS_HEIGHT}px`,
});
this.canvas = el;
this.background = <SVGRectElement>svg.child(el, "rect", { class: "sim-neopixel-background hidden"});
this.updateViewBox(-CANVAS_VIEW_WIDTH / 2, 0, CANVAS_VIEW_WIDTH, CANVAS_VIEW_HEIGHT);
}
private updateViewBox(x: number, y: number, w: number, h: number) {
this.viewBox = [x, y, w, h];
svg.hydrate(this.canvas, {"viewBox": `${x} ${y} ${w} ${h}`});
svg.hydrate(this.background, {"x": x, "y": y, "width": w, "height": h});
}
public update(colors: RGBW[]) {
if (!colors || colors.length <= 0)
return;
for (let i = 0; i < colors.length; i++) {
let pixel = this.pixels[i];
if (!pixel) {
let cxy: Coord = [0, CANVAS_VIEW_PADDING + i * PIXEL_SPACING];
pixel = this.pixels[i] = new NeoPixel(cxy);
this.canvas.appendChild(pixel.el);
}
let color = colors[i];
pixel.setRgb(color);
svg.hydrate(pixel.el, {title: `offset: ${i}`});
}
//show the canvas if it's hidden
svg.removeClass(this.background, "hidden");
//resize if necessary
let [first, last] = [this.pixels[0], this.pixels[this.pixels.length - 1]]
let yDiff = last.cy - first.cy;
let newH = yDiff + CANVAS_VIEW_PADDING * 2;
let [oldX, oldY, oldW, oldH] = this.viewBox;
if (oldH < newH) {
let scalar = newH / oldH;
let newW = oldW * scalar;
this.updateViewBox(-newW / 2, oldY, newW, newH);
}
}
public setLoc(xy: Coord) {
let [x, y] = xy;
svg.hydrate(this.canvas, {x: x, y: y});
}
};
function gpioPinToPinNumber(gpioPin: string): number {
let pinNumStr = gpioPin.split("P")[1];
let pinNum = Number(pinNumStr) + 7 /*MICROBIT_ID_IO_P0; TODO: don't hardcode this, import enums.d.ts*/;
return pinNum
}
function parseNeoPixelMode(modeStr: string): NeoPixelMode {
const modeMap: Map<NeoPixelMode> = {
"NeoPixelMode.RGB": NeoPixelMode.RGB,
"NeoPixelMode.RGBW": NeoPixelMode.RGBW,
"*": NeoPixelMode.RGB,
};
let mode: NeoPixelMode = null;
for (let key in modeMap) {
if (key == modeStr) {
mode = modeMap[key];
break;
}
}
U.assert(mode != null, "Unknown NeoPixelMode: " + modeStr);
return mode;
}
export class NeoPixelView implements IBoardComponent<NeoPixelState> {
public style: string = `
.sim-neopixel-canvas {
}
.sim-neopixel-canvas-parent:hover {
transform-origin: center;
transform: scale(4) translateY(-60px);
}
.sim-neopixel-canvas .hidden {
visibility:hidden;
}
.sim-neopixel-background {
fill: rgba(255,255,255,0.9);
}
.sim-neopixel-strip {
}
`;
public element: SVGElement;
public defs: SVGElement[];
private state: NeoPixelState;
private canvas: NeoPixelCanvas;
private part: SVGElAndSize;
private stripGroup: SVGGElement;
private lastLocation: Coord;
private pin: number;
private mode: NeoPixelMode;
public init(bus: EventBus, state: NeoPixelState, svgEl: SVGSVGElement, gpioPins: string[], otherArgs: string[]): void {
U.assert(otherArgs.length === 1, "NeoPixels assumes a RGB vs RGBW mode is passed to it");
let modeStr = otherArgs[0];
this.mode = parseNeoPixelMode(modeStr);
this.state = state;
this.stripGroup = <SVGGElement>svg.elt("g");
this.element = this.stripGroup;
let pinStr = gpioPins[0];
this.pin = gpioPinToPinNumber(pinStr);
this.lastLocation = [0, 0];
let part = mkNeoPixelPart();
this.part = part;
this.stripGroup.appendChild(part.el);
let canvas = new NeoPixelCanvas(this.pin);
this.canvas = canvas;
let canvasG = svg.child(this.stripGroup, "g", {class: "sim-neopixel-canvas-parent"});
canvasG.appendChild(canvas.canvas);
this.updateStripLoc();
}
public moveToCoord(xy: Coord): void {
let [x, y] = xy;
let loc: Coord = [x, y];
this.lastLocation = loc;
this.updateStripLoc();
}
private updateStripLoc() {
let [x, y] = this.lastLocation;
this.canvas.setLoc([x + CANVAS_LEFT, y + CANVAS_TOP]);
svg.hydrate(this.part.el, {transform: `translate(${x} ${y})`}); //TODO: update part's l,h, etc.
}
public updateState(): void {
let colors = this.state.getColors(this.pin, this.mode);
this.canvas.update(colors);
}
public updateTheme (): void { }
}
}

425
sim/visuals/wiring.ts Normal file
View File

@ -0,0 +1,425 @@
namespace pxsim.visuals {
const WIRE_WIDTH = PIN_DIST / 2.5;
const BB_WIRE_SMOOTH = 0.7;
const INSTR_WIRE_SMOOTH = 0.8;
const WIRE_PART_CURVE_OFF = 15;
const WIRE_PART_LENGTH = 100;
export const WIRES_CSS = `
.sim-bb-wire {
fill:none;
stroke-linecap: round;
stroke-width:${WIRE_WIDTH}px;
pointer-events: none;
}
.sim-bb-wire-end {
stroke:#333;
fill:#333;
}
.sim-bb-wire-bare-end {
fill: #ccc;
}
.sim-bb-wire-hover {
stroke-width: ${WIRE_WIDTH}px;
visibility: hidden;
stroke-dasharray: ${PIN_DIST / 10.0},${PIN_DIST / 1.5};
/*stroke-opacity: 0.4;*/
}
.grayed .sim-bb-wire-ends-g:not(.highlight) .sim-bb-wire-end {
stroke: #777;
fill: #777;
}
.grayed .sim-bb-wire:not(.highlight) {
stroke: #CCC;
}
.sim-bb-wire-ends-g:hover .sim-bb-wire-end {
stroke: red;
fill: red;
}
.sim-bb-wire-ends-g:hover .sim-bb-wire-bare-end {
stroke: #FFF;
fill: #FFF;
}
`;
export interface Wire {
endG: SVGGElement;
end1: SVGElement;
end2: SVGElement;
wires: SVGElement[];
}
function cssEncodeColor(color: string): string {
//HACK/TODO: do real CSS encoding.
return color
.replace(/\#/g, "-")
.replace(/\(/g, "-")
.replace(/\)/g, "-")
.replace(/\,/g, "-")
.replace(/\./g, "-")
.replace(/\s/g, "");
}
export enum WireEndStyle {
BBJumper,
OpenJumper,
Croc,
}
export interface WireOpts { //TODO: use throughout
color?: string,
colorClass?: string,
bendFactor?: number,
}
export function mkWirePart(cp: [number, number], clr: string, croc: boolean = false): visuals.SVGAndSize<SVGGElement> {
let g = <SVGGElement>svg.elt("g");
let [cx, cy] = cp;
let offset = WIRE_PART_CURVE_OFF;
let p1: visuals.Coord = [cx - offset, cy - WIRE_PART_LENGTH / 2];
let p2: visuals.Coord = [cx + offset, cy + WIRE_PART_LENGTH / 2];
clr = visuals.mapWireColor(clr);
let e1: SVGElAndSize;
if (croc)
e1 = mkCrocEnd(p1, true, clr);
else
e1 = mkOpenJumperEnd(p1, true, clr);
let s = mkWirePartSeg(p1, p2, clr);
let e2 = mkOpenJumperEnd(p2, false, clr);
g.appendChild(s.el);
g.appendChild(e1.el);
g.appendChild(e2.el);
let l = Math.min(e1.x, e2.x);
let r = Math.max(e1.x + e1.w, e2.x + e2.w);
let t = Math.min(e1.y, e2.y);
let b = Math.max(e1.y + e1.h, e2.y + e2.h);
return {el: g, x: l, y: t, w: r - l, h: b - t};
}
function mkCurvedWireSeg(p1: [number, number], p2: [number, number], smooth: number, clrClass: string): SVGPathElement {
const coordStr = (xy: [number, number]): string => {return `${xy[0]}, ${xy[1]}`};
let [x1, y1] = p1;
let [x2, y2] = p2
let yLen = (y2 - y1);
let c1: [number, number] = [x1, y1 + yLen * smooth];
let c2: [number, number] = [x2, y2 - yLen * smooth];
let w = <SVGPathElement>svg.mkPath("sim-bb-wire", `M${coordStr(p1)} C${coordStr(c1)} ${coordStr(c2)} ${coordStr(p2)}`);
svg.addClass(w, `wire-stroke-${clrClass}`);
return w;
}
function mkWirePartSeg(p1: [number, number], p2: [number, number], clr: string): visuals.SVGAndSize<SVGPathElement> {
//TODO: merge with mkCurvedWireSeg
const coordStr = (xy: [number, number]): string => {return `${xy[0]}, ${xy[1]}`};
let [x1, y1] = p1;
let [x2, y2] = p2
let yLen = (y2 - y1);
let c1: [number, number] = [x1, y1 + yLen * .8];
let c2: [number, number] = [x2, y2 - yLen * .8];
let e = <SVGPathElement>svg.mkPath("sim-bb-wire", `M${coordStr(p1)} C${coordStr(c1)} ${coordStr(c2)} ${coordStr(p2)}`);
(<any>e).style["stroke"] = clr;
return {el: e, x: Math.min(x1, x2), y: Math.min(y1, y2), w: Math.abs(x1 - x2), h: Math.abs(y1 - y2)};
}
function mkWireSeg(p1: [number, number], p2: [number, number], clrClass: string): SVGPathElement {
const coordStr = (xy: [number, number]): string => {return `${xy[0]}, ${xy[1]}`};
let w = <SVGPathElement>svg.mkPath("sim-bb-wire", `M${coordStr(p1)} L${coordStr(p2)}`);
svg.addClass(w, `wire-stroke-${clrClass}`);
return w;
}
function mkBBJumperEnd(p: [number, number], clrClass: string): SVGElement {
const endW = PIN_DIST / 4;
let w = svg.elt("circle");
let x = p[0];
let y = p[1];
let r = WIRE_WIDTH / 2 + endW / 2;
svg.hydrate(w, {cx: x, cy: y, r: r, class: "sim-bb-wire-end"});
svg.addClass(w, `wire-fill-${clrClass}`);
(<any>w).style["stroke-width"] = `${endW}px`;
return w;
}
function mkOpenJumperEnd(p: [number, number], top: boolean, clr: string): visuals.SVGElAndSize {
let k = visuals.PIN_DIST * 0.24;
let plasticLength = k * 10;
let plasticWidth = k * 2;
let metalLength = k * 6;
let metalWidth = k;
const strokeWidth = visuals.PIN_DIST / 4.0;
let [cx, cy] = p;
let o = top ? -1 : 1;
let g = svg.elt("g")
let el = svg.elt("rect");
let h1 = plasticLength;
let w1 = plasticWidth;
let x1 = cx - w1 / 2;
let y1 = cy - (h1 / 2);
svg.hydrate(el, {x: x1, y: y1, width: w1, height: h1, rx: 0.5, ry: 0.5, class: "sim-bb-wire-end"});
(<any>el).style["stroke-width"] = `${strokeWidth}px`;
let el2 = svg.elt("rect");
let h2 = metalLength;
let w2 = metalWidth;
let cy2 = cy + o * (h1 / 2 + h2 / 2);
let x2 = cx - w2 / 2;
let y2 = cy2 - (h2 / 2);
svg.hydrate(el2, {x: x2, y: y2, width: w2, height: h2, class: "sim-bb-wire-bare-end"});
(<any>el2).style["fill"] = `#bbb`;
g.appendChild(el2);
g.appendChild(el);
return {el: g, x: x1 - strokeWidth, y: Math.min(y1, y2), w: w1 + strokeWidth * 2, h: h1 + h2};
}
function mkCrocEnd(p: [number, number], top: boolean, clr: string): SVGElAndSize {
//TODO: merge with mkOpenJumperEnd()
let k = visuals.PIN_DIST * 0.24;
const plasticWidth = k * 4;
const plasticLength = k * 10.0;
const metalWidth = k * 3.5;
const metalHeight = k * 3.5;
const pointScalar = .15;
const baseScalar = .3;
const taperScalar = .7;
const strokeWidth = visuals.PIN_DIST / 4.0;
let [cx, cy] = p;
let o = top ? -1 : 1;
let g = svg.elt("g")
let el = svg.elt("polygon");
let h1 = plasticLength;
let w1 = plasticWidth;
let x1 = cx - w1 / 2;
let y1 = cy - (h1 / 2);
let mkPnt = (xy: Coord) => `${xy[0]},${xy[1]}`;
let mkPnts = (...xys: Coord[]) => xys.map(xy => mkPnt(xy)).join(" ");
const topScalar = top ? pointScalar : baseScalar;
const midScalar = top ? taperScalar : (1 - taperScalar);
const botScalar = top ? baseScalar : pointScalar;
svg.hydrate(el, {
points: mkPnts(
[x1 + w1 * topScalar, y1], //TL
[x1 + w1 * (1 - topScalar), y1], //TR
[x1 + w1, y1 + h1 * midScalar], //MR
[x1 + w1 * (1 - botScalar), y1 + h1], //BR
[x1 + w1 * botScalar, y1 + h1], //BL
[x1, y1 + h1 * midScalar]) //ML
});
svg.hydrate(el, {rx: 0.5, ry: 0.5, class: "sim-bb-wire-end"});
(<any>el).style["stroke-width"] = `${strokeWidth}px`;
let el2 = svg.elt("rect");
let h2 = metalWidth;
let w2 = metalHeight;
let cy2 = cy + o * (h1 / 2 + h2 / 2);
let x2 = cx - w2 / 2;
let y2 = cy2 - (h2 / 2);
svg.hydrate(el2, {x: x2, y: y2, width: w2, height: h2, class: "sim-bb-wire-bare-end"});
g.appendChild(el2);
g.appendChild(el);
return {el: g, x: x1 - strokeWidth, y: Math.min(y1, y2), w: w1 + strokeWidth * 2, h: h1 + h2};
}
//TODO: make this stupid class obsolete
export class WireFactory {
private underboard: SVGGElement;
private overboard: SVGGElement;
private boardEdges: number[];
private getLocCoord: (loc: Loc) => Coord;
public styleEl: SVGStyleElement;
constructor(underboard: SVGGElement, overboard: SVGGElement, boardEdges: number[], styleEl: SVGStyleElement, getLocCoord: (loc: Loc) => Coord) {
this.styleEl = styleEl;
this.styleEl.textContent += WIRES_CSS;
this.underboard = underboard;
this.overboard = overboard;
this.boardEdges = boardEdges;
this.getLocCoord = getLocCoord;
}
private indexOfMin(vs: number[]): number {
let minIdx = 0;
let min = vs[0];
for (let i = 1; i < vs.length; i++) {
if (vs[i] < min) {
min = vs[i];
minIdx = i;
}
}
return minIdx;
}
private closestEdgeIdx(p: [number, number]): number {
let dists = this.boardEdges.map(e => Math.abs(p[1] - e));
let edgeIdx = this.indexOfMin(dists);
return edgeIdx;
}
private closestEdge(p: [number, number]): number {
return this.boardEdges[this.closestEdgeIdx(p)];
}
private nextWireId = 0;
private drawWire(pin1: Coord, pin2: Coord, color: string): Wire {
let wires: SVGElement[] = [];
let g = svg.child(this.overboard, "g", {class: "sim-bb-wire-group"});
const closestPointOffBoard = (p: [number, number]): [number, number] => {
const offset = PIN_DIST / 2;
let e = this.closestEdge(p);
let y: number;
if (e - p[1] < 0)
y = e - offset;
else
y = e + offset;
return [p[0], y];
}
let wireId = this.nextWireId++;
let clrClass = cssEncodeColor(color);
let end1 = mkBBJumperEnd(pin1, clrClass);
let end2 = mkBBJumperEnd(pin2, clrClass);
let endG = <SVGGElement>svg.child(g, "g", {class: "sim-bb-wire-ends-g"});
endG.appendChild(end1);
endG.appendChild(end2);
let edgeIdx1 = this.closestEdgeIdx(pin1);
let edgeIdx2 = this.closestEdgeIdx(pin2);
if (edgeIdx1 == edgeIdx2) {
let seg = mkWireSeg(pin1, pin2, clrClass);
g.appendChild(seg);
wires.push(seg);
} else {
let offP1 = closestPointOffBoard(pin1);
let offP2 = closestPointOffBoard(pin2);
let offSeg1 = mkWireSeg(pin1, offP1, clrClass);
let offSeg2 = mkWireSeg(pin2, offP2, clrClass);
let midSeg: SVGElement;
let midSegHover: SVGElement;
let isBetweenMiddleTwoEdges = (edgeIdx1 == 1 || edgeIdx1 == 2) && (edgeIdx2 == 1 || edgeIdx2 == 2);
if (isBetweenMiddleTwoEdges) {
midSeg = mkCurvedWireSeg(offP1, offP2, BB_WIRE_SMOOTH, clrClass);
midSegHover = mkCurvedWireSeg(offP1, offP2, BB_WIRE_SMOOTH, clrClass);
} else {
midSeg = mkWireSeg(offP1, offP2, clrClass);
midSegHover = mkWireSeg(offP1, offP2, clrClass);
}
svg.addClass(midSegHover, "sim-bb-wire-hover");
g.appendChild(offSeg1);
wires.push(offSeg1);
g.appendChild(offSeg2);
wires.push(offSeg2);
this.underboard.appendChild(midSeg);
wires.push(midSeg);
g.appendChild(midSegHover);
wires.push(midSegHover);
//set hover mechanism
let wireIdClass = `sim-bb-wire-id-${wireId}`;
const setId = (e: SVGElement) => svg.addClass(e, wireIdClass);
setId(endG);
setId(midSegHover);
this.styleEl.textContent += `
.${wireIdClass}:hover ~ .${wireIdClass}.sim-bb-wire-hover {
visibility: visible;
}`
}
// wire colors
let colorCSS = `
.wire-stroke-${clrClass} {
stroke: ${mapWireColor(color)};
}
.wire-fill-${clrClass} {
fill: ${mapWireColor(color)};
}
`
this.styleEl.textContent += colorCSS;
return {endG: endG, end1: end1, end2: end2, wires: wires};
}
private drawWireWithCrocs(pin1: Coord, pin2: Coord, color: string): Wire {
//TODO: merge with drawWire()
const PIN_Y_OFF = 40;
const CROC_Y_OFF = -17;
let wires: SVGElement[] = [];
let g = svg.child(this.overboard, "g", {class: "sim-bb-wire-group"});
const closestPointOffBoard = (p: [number, number]): [number, number] => {
const offset = PIN_DIST / 2;
let e = this.closestEdge(p);
let y: number;
if (e - p[1] < 0)
y = e - offset;
else
y = e + offset;
return [p[0], y];
}
let wireId = this.nextWireId++;
let clrClass = cssEncodeColor(color);
let end1 = mkBBJumperEnd(pin1, clrClass);
let pin2orig = pin2;
let [x2, y2] = pin2;
pin2 = [x2, y2 + PIN_Y_OFF];//HACK
[x2, y2] = pin2;
let endCoord2: Coord = [x2, y2 + CROC_Y_OFF]
let end2AndSize = mkCrocEnd(endCoord2, true, color);
let end2 = end2AndSize.el;
let endG = <SVGGElement>svg.child(g, "g", {class: "sim-bb-wire-ends-g"});
endG.appendChild(end1);
//endG.appendChild(end2);
let edgeIdx1 = this.closestEdgeIdx(pin1);
let edgeIdx2 = this.closestEdgeIdx(pin2orig);
if (edgeIdx1 == edgeIdx2) {
let seg = mkWireSeg(pin1, pin2, clrClass);
g.appendChild(seg);
wires.push(seg);
} else {
let offP1 = closestPointOffBoard(pin1);
//let offP2 = closestPointOffBoard(pin2orig);
let offSeg1 = mkWireSeg(pin1, offP1, clrClass);
//let offSeg2 = mkWireSeg(pin2, offP2, clrClass);
let midSeg: SVGElement;
let midSegHover: SVGElement;
let isBetweenMiddleTwoEdges = (edgeIdx1 == 1 || edgeIdx1 == 2) && (edgeIdx2 == 1 || edgeIdx2 == 2);
if (isBetweenMiddleTwoEdges) {
midSeg = mkCurvedWireSeg(offP1, pin2, BB_WIRE_SMOOTH, clrClass);
midSegHover = mkCurvedWireSeg(offP1, pin2, BB_WIRE_SMOOTH, clrClass);
} else {
midSeg = mkWireSeg(offP1, pin2, clrClass);
midSegHover = mkWireSeg(offP1, pin2, clrClass);
}
svg.addClass(midSegHover, "sim-bb-wire-hover");
g.appendChild(offSeg1);
wires.push(offSeg1);
// g.appendChild(offSeg2);
// wires.push(offSeg2);
this.underboard.appendChild(midSeg);
wires.push(midSeg);
//g.appendChild(midSegHover);
//wires.push(midSegHover);
//set hover mechanism
let wireIdClass = `sim-bb-wire-id-${wireId}`;
const setId = (e: SVGElement) => svg.addClass(e, wireIdClass);
setId(endG);
setId(midSegHover);
this.styleEl.textContent += `
.${wireIdClass}:hover ~ .${wireIdClass}.sim-bb-wire-hover {
visibility: visible;
}`
}
endG.appendChild(end2);//HACK
// wire colors
let colorCSS = `
.wire-stroke-${clrClass} {
stroke: ${mapWireColor(color)};
}
.wire-fill-${clrClass} {
fill: ${mapWireColor(color)};
}
`
this.styleEl.textContent += colorCSS;
return {endG: endG, end1: end1, end2: end2, wires: wires};
}
public addWire(start: Loc, end: Loc, color: string, withCrocs: boolean = false): Wire {
let startLoc = this.getLocCoord(start);
let endLoc = this.getLocCoord(end);
let wireEls: Wire;
if (withCrocs && end.type == "dalboard") {
wireEls = this.drawWireWithCrocs(startLoc, endLoc, color);
} else {
wireEls = this.drawWire(startLoc, endLoc, color);
}
return wireEls;
}
}
}

8
tests/base/tsconfig.json Normal file
View File

@ -0,0 +1,8 @@
{
"compilerOptions": {
"target": "es5",
"noImplicitAny": true,
"outDir": "built",
"rootDir": "."
}
}