Compare commits

..

36 Commits

Author SHA1 Message Date
dc6ce0efc7 0.0.53 2018-01-05 08:39:30 -08:00
4039a85bc9 Make sure the ESC button always stops the program 2018-01-05 16:17:33 +00:00
7bd6280292 Disable HID deploy from command line (seems broken; was crashing until my previous checkin) 2018-01-05 16:17:20 +00:00
2ebe96e563 Fix command line deploy 2018-01-05 15:33:09 +00:00
a9be582f90 gyro calibration done right (#186) 2018-01-04 23:21:19 -08:00
de91dc6ab7 Handle the case where multiple motors or multiple sensors are connected to the same port. (#181) 2018-01-04 21:57:01 -08:00
1e460eef9e basic reading of battery level (#182) 2018-01-04 21:50:13 -08:00
0db6987ee5 Add loader and animation (#180) 2018-01-04 16:17:14 -08:00
148657908c Merge pull request #179 from Microsoft/syncedmotorui
Synced motors label (in simulator)
2018-01-04 14:38:29 -08:00
f3f87331c8 nit 2018-01-04 14:09:10 -08:00
5aef77ccc6 Generalizing motorView for both medium and large motor views. Initial work towards synced motor views. Adding sync label for controller motor. 2018-01-04 14:03:50 -08:00
59ca9cf463 0.0.52 2018-01-04 13:04:23 -08:00
7da811246c Behaviors Driven Robotics (#178)
* adding avoid crash behavior

* more implementation

* add another simple behavior

* fixed synched motors

* bump common packages
2018-01-04 12:55:30 -08:00
69f8453947 Merge pull request #177 from Microsoft/sounds_bug_fix
Fix stopAllSounds simulator bug
2018-01-04 12:52:17 -08:00
39ba9b81af Initial work 2018-01-04 12:20:31 -08:00
51a14596cd 0.0.51 2018-01-04 11:41:49 -08:00
8518c446cd Merge pull request #176 from Microsoft/iefonticons
Fix font icons on IE, and add package icon on Windows
2018-01-04 11:40:42 -08:00
2f69df0d9d Fix font icons on IE, and add package icon on Windows 2018-01-04 11:39:20 -08:00
1789d0ce21 Lowercase menubar titles. 2018-01-04 10:42:37 -08:00
d0809510c4 Merge pull request #174 from Microsoft/addbetatag
Add beta tag.
2018-01-04 10:14:43 -08:00
6b9c0eaf65 Add beta tag. #150 2018-01-04 10:14:11 -08:00
52bdf94233 Merge pull request #173 from Microsoft/blockiconfont
Add block icon font
2018-01-04 09:57:11 -08:00
fd50ed8f7c Add block icon font 2018-01-04 09:52:12 -08:00
ba0eb93b0f use common packages tests implementation (#171) 2018-01-04 09:23:28 -08:00
9a4ed45797 0.0.50 2018-01-04 09:19:33 -08:00
820fdf3a3c Merge pull request #172 from Microsoft/fixwinicons
Fix check and cancel icons on windows
2018-01-04 09:18:30 -08:00
348d5ffc26 Fix check and cancel icons on windows 2018-01-04 09:17:30 -08:00
95e47a0b25 Merge pull request #169 from Microsoft/screenimgeditor
Initial screen field editor.
2018-01-04 08:37:56 -08:00
c3312ed5d1 Merge pull request #170 from Microsoft/clearcounts
moving motor blocks around
2018-01-04 08:37:39 -08:00
b7cdc7d0fe moving blocks around 2018-01-04 08:25:02 -08:00
dde5a35cd9 Initial screen field editor. Adding Lego designer assets to legoresources 2018-01-03 17:20:35 -08:00
be398d84ee Merge pull request #135 from Microsoft/field_ports
Add an output port field editor
2018-01-03 17:07:34 -08:00
4445acce7a Split editor extensions into multiple module loaded files. Re-using editor/deploy.ts for cmds. Fixing field ports 2018-01-03 16:18:39 -08:00
d6d8b0655b Merge branch 'master' into field_ports 2018-01-03 15:12:35 -08:00
216aa1ddaf Fix save icon 2018-01-03 15:07:45 -08:00
a1b059171b Add a output port field editor 2017-12-24 17:46:58 -08:00
89 changed files with 1057 additions and 525 deletions

View File

@ -2,14 +2,10 @@
import * as fs from 'fs';
require("./editor")
declare namespace pxt.editor {
function deployCoreAsync(resp: pxtc.CompileResult, disconnect?: boolean): Promise<void>;
}
const deploy = require("./editor/deploy")
export function deployCoreAsync(resp: pxtc.CompileResult) {
return pxt.editor.deployCoreAsync(resp, process.env["PXT_SERIAL"] ? false : true)
return deploy.deployCoreAsync(resp, process.env["PXT_SERIAL"] ? false : true)
.then(() => {
fs.writeFileSync("built/full-" + pxtc.BINARY_UF2, resp.outfiles[pxtc.BINARY_UF2], {
encoding: "base64"

View File

@ -1,8 +1,8 @@
@font-face {
font-family: "iconfont";
src: url("iconfont.eot?6e1ef95090bc1e1acc3a7e6bb86172de?#iefix") format("embedded-opentype"),
url("iconfont.woff2?6e1ef95090bc1e1acc3a7e6bb86172de") format("woff2"),
url("iconfont.woff?6e1ef95090bc1e1acc3a7e6bb86172de") format("woff");
src: url("iconfont.eot?73552ec404b3a3d3769a3f04fa58c2c4?#iefix") format("embedded-opentype"),
url("iconfont.woff2?73552ec404b3a3d3769a3f04fa58c2c4") format("woff2"),
url("iconfont.woff?73552ec404b3a3d3769a3f04fa58c2c4") format("woff");
}
.icon {
@ -67,21 +67,24 @@ url("iconfont.woff?6e1ef95090bc1e1acc3a7e6bb86172de") format("woff");
.icon-variables:before {
content: "\f111";
}
.icon-cancel:before {
.icon-advancedcollapsed:before {
content: "\f112";
}
.icon-check:before {
.icon-advancedexpanded:before {
content: "\f113";
}
.icon-download:before {
.icon-cancel:before {
content: "\f114";
}
.icon-save:before {
.icon-check:before {
content: "\f115";
}
.icon-advancedcollapsed:before {
.icon-download:before {
content: "\f116";
}
.icon-advancedexpanded:before {
.icon-save:before {
content: "\f117";
}
.icon-blocks:before {
content: "\f118";
}

Binary file not shown.

View File

@ -21,7 +21,7 @@
horiz-adv-x="23.578015492438215" d=" M17 0H6.4A5.901881224640355 5.901881224640355 0 0 0 5.1 0.3H2.3A1.283659166359277 1.283659166359277 0 0 0 1 1.6V4.5H0.5A0.5016599040944301 0.5016599040944301 0 0 0 0 5V35.4A0.5016599040944301 0.5016599040944301 0 0 0 0.5 35.9H1V38.7A1.283659166359277 1.283659166359277 0 0 0 2.3 40H10.6V39.6H12.8V40H21.2A1.283659166359277 1.283659166359277 0 0 0 22.5 38.7V35.9H23.1A0.5016599040944301 0.5016599040944301 0 0 0 23.6 35.4V5.1A0.5016599040944301 0.5016599040944301 0 0 0 23.1 4.6H22.5V1.6A1.283659166359277 1.283659166359277 0 0 0 21.2 0.4H18.5C18.4 0.1 17.2 0 17 0zM6.2 34.5L3.7 33.8A1.0033198081888601 1.0033198081888601 0 0 1 3.4 33.7A0.2360752489856142 0.2360752489856142 0 0 1 3.3 33.5A0.20656584286241245 0.20656584286241245 0 0 1 3.4 33.3L4.1 32.8A7.657690888970861 7.657690888970861 0 0 1 3.1 30A8.51346366654371 8.51346366654371 0 0 1 4.8 23.5C5.8 24.2 6.5 24.7 6.5 24.7A7.377351530800443 7.377351530800443 0 0 0 5.4 26.9A6.300258207303577 6.300258207303577 0 0 0 6 31.8L6.5 31.5H6.5L6.6 31.5A0.3541128734784213 0.3541128734784213 0 0 1 6.8 31.4A0.17705643673921065 0.17705643673921065 0 0 1 7 31.5A0.6344522316488379 0.6344522316488379 0 0 1 7 32L6.2 34.5zM17.5 31.8H17.5A6.300258207303577 6.300258207303577 0 0 0 18 26.9A7.510143858354851 7.510143858354851 0 0 0 17 24.7L17.1 24.6L18.6 23.5A8.631501291036518 8.631501291036518 0 0 1 20.3 30A7.657690888970861 7.657690888970861 0 0 1 19.3 32.8L19.8 33.1L20 33.2A0.22132054592401326 0.22132054592401326 0 0 1 20.1 33.4A0.26558465510881596 0.26558465510881596 0 0 1 20 33.6A1.0033198081888601 1.0033198081888601 0 0 1 19.7 33.7L17.2 34.5S17.1 34.1 17 33.6S16.6 32.3 16.5 32A0.619697528587237 0.619697528587237 0 0 1 16.5 31.5A0.17705643673921065 0.17705643673921065 0 0 1 16.6 31.4A0.48690520103282936 0.48690520103282936 0 0 1 16.8 31.4L16.8 31.4L17.4 31.7zM11.7 30.6A1.9918849133161198 1.9918849133161198 0 1 1 13.7 28.6A1.9918849133161198 1.9918849133161198 0 0 1 11.7 30.6H11.7z" />
<glyph glyph-name="addpackage"
unicode="&#xF105;"
horiz-adv-x="40" d=" M4.3 19.1C4.3 10.5 11.4 3.5 20 3.5C28.6 3.5 35.7 10.5 35.7 19.1C35.7 27.8 28.6 34.8 20 34.8C11.4 34.8 4.3 27.8 4.3 19.1z M30.4 20.9L21.7 20.9L21.7 29.6L18.3 29.6L18.3 20.9L9.6 20.9L9.6 17.4L18.3 17.4L18.3 8.7L21.7 8.7L21.7 17.4L30.4 17.4z" />
horiz-adv-x="40" d=" M20 35.7C11.3 35.7 4.3 28.7 4.3 20S11.3 4.3 20 4.3S35.7 11.3 35.7 20S28.7 35.7 20 35.7zM30.4 18.3H21.7V9.6H18.3V18.3H9.6V21.7H18.3V30.4H21.7V21.7H30.4V18.3z" />
<glyph glyph-name="brick"
unicode="&#xF106;"
horiz-adv-x="40" d=" M34.3 0.9H5.7V39.1H34.4V0.9zM10.4 32.7V18.4H29.6V32.7H10.4z" />
@ -58,24 +58,27 @@
<glyph glyph-name="variables"
unicode="&#xF111;"
horiz-adv-x="40" d=" M36.8 12V7.8H3.4V12H36.8z M36.8 22.1V17.9H3.4V22.1H36.8z M36.7 32.2V28H3.3V32.2H36.7z" />
<glyph glyph-name="cancel"
unicode="&#xF112;"
horiz-adv-x="40" d=" M4285.9 23284.1H4308.1V23280.4H4285.9V23284.1z M4285.9 23302.6H4289.6V23280.4H4285.9V23302.6z" />
<glyph glyph-name="check"
unicode="&#xF113;"
horiz-adv-x="40" d=" M4209.2 23456H4231.3V23452.3H4209.2V23456z M4201.2 23463.4H4204.9V23452.3H4201.2V23463.4z" />
<glyph glyph-name="download"
unicode="&#xF114;"
horiz-adv-x="40" d=" M5.2 15.7H36.5V1.7H5.2V15.7z M28.5 24.2L26.1 26.6L22.6 23.1L22.6 36.5L19.1 36.5L19.1 23.5L16 26.6L13.6 24.2L20.9 16.7L21 16.9L21.2 16.7z" />
<glyph glyph-name="save"
unicode="&#xF115;"
horiz-adv-x="40" d=" M2.3 20.1V36.6C2.3 37.8 2.4 38 3.7 38H11.5C12.7 38 12.9 37.8 12.9 36.6V28.6C12.9 27.6 13 27.4 14.1 27.4H25.9C27 27.4 27.1 27.6 27.1 28.6V36.8C27.1 37.8 27.3 38 28.3 38C30.8 38 30.8 38 32.3 36.4C34.1 34.7 35.7 33.1 37.4 31.4C37.7 31 37.9 30.5 37.9 30V3.6C37.9 2.5 37.7 2.2 36.7 2.2H3.5C2.4 2.2 2.3 2.3 2.3 3.6C2.3 9.1 2.3 14.7 2.3 20.1zM20.2 6H33C34.3 6 34.4 6.2 34.4 7.4V22.5C34.4 23.6 34.3 23.9 33 23.9H7.1C6.1 23.9 5.7 23.7 5.7 22.5V7.4C5.7 6.2 5.9 6 7.1 6H20.2z M24.7 34.5C24.7 33.6 24.7 32.8 24.7 31.9C24.7 31 24.3 30.9 23.7 30.9C23.1 30.9 22.4 30.9 22.1 30.9S21 31 21 31.7V31.9V36.9C21 37.3 21.2 37.8 21.9 38C21.9 38 21.9 38 22.1 38C22.6 38 23.3 38 23.8 38C24.3 38 24.9 37.6 24.9 37.1C24.9 37.1 24.9 37.1 24.9 36.9C24.7 36.3 24.7 35.2 24.7 34.5z M5.1 25H35.4V4.8H5.1V25z M6.2 24.1H34.5V5.9H6.2V24.1z" />
<glyph glyph-name="advancedcollapsed"
unicode="&#xF116;"
unicode="&#xF112;"
horiz-adv-x="40" d=" M39.7 28.2L36.2 31.5L20 15.3L3.8 31.5L0.3 28.2L18.3 10.3L18.3 10.3L20 8.5L20.5 9L20.5 9z" />
<glyph glyph-name="advancedexpanded"
unicode="&#xF117;"
unicode="&#xF113;"
horiz-adv-x="40" d=" M39.3 12L21.7 29.6L21.7 29.6L20 31.3L19.5 30.8L19.5 30.8L0.7 12L4 8.7L20 24.7L36 8.7z" />
<glyph glyph-name="cancel"
unicode="&#xF114;"
horiz-adv-x="40" d=" M33 29.6L29.4 33.2L20.2 24L11 33.2L7.3 29.6L16.5 20.3L7.3 11.1L11 7.3L20.2 16.5L29.4 7.3L33 11.1L23.8 20.3z" />
<glyph glyph-name="check"
unicode="&#xF115;"
horiz-adv-x="40" d=" M33.7 32.9L15.3 14.4L7.5 22.3L3.8 18.4L11.7 10.8L11.7 10.8L15.3 7.1L37.4 29.2z" />
<glyph glyph-name="download"
unicode="&#xF116;"
horiz-adv-x="40" d=" M5.2 15.7H36.5V1.7H5.2V15.7z M28.5 24.2L26.1 26.6L22.6 23.1L22.6 36.5L19.1 36.5L19.1 23.5L16 26.6L13.6 24.2L20.9 16.7L21 16.9L21.2 16.7z" />
<glyph glyph-name="save"
unicode="&#xF117;"
horiz-adv-x="40" d=" M25 34.5C25 33.6 25 32.8 25 31.9C25 31 24.7 30.9 24 30.9C23.5 30.9 22.8 30.9 22.4 30.9C22.1 30.9 21.4 31 21.4 31.7V31.9V36.9C21.4 37.3 21.6 37.8 22.3 38C22.3 38 22.3 38 22.4 38C23 38 23.7 38 24.2 38C24.7 38 25.2 37.6 25.2 37.1C25.2 37.1 25.2 37.1 25.2 36.9C25 36.3 25 35.2 25 34.5z M37.6 31.2C35.8 32.9 34.3 34.5 32.5 36.3C31 37.8 31 37.8 28.5 37.8C27.5 37.8 27.3 37.6 27.3 36.6V28.4C27.3 27.4 27.1 27.2 26.1 27.2H14.3C13.2 27.2 13 27.4 13 28.4V36.4C13 37.6 12.9 37.8 11.7 37.8H3.8C2.6 37.8 2.4 37.6 2.4 36.4V19.9C2.4 14.3 2.4 8.9 2.4 3.2C2.4 2 2.6 1.8 3.7 1.8H36.9C37.9 1.8 38.1 2.2 38.1 3.2V29.6C38.3 30.5 37.9 30.9 37.6 31.2zM33.7 6.7H7.1V23H33.7V6.7z" />
<glyph glyph-name="blocks"
unicode="&#xF118;"
horiz-adv-x="40" d=" M10.9 23H39V16.9H10.9V23z M39.2 27.1L39.2 33L0.9 33L0.9 31.1L0.9 27.1L0.9 12.9L0.9 7L39.2 7L39.2 12.9L6.9 12.9L6.9 27.1z" />
</font>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

1
docs/static/loader_back.svg vendored Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="301.499" height="179.742" viewBox="0 0 282.655 168.508"><g transform="translate(-41.005 -446.364)"><rect ry="30" rx="30" y="458.77" x="53.41" height="143.698" width="105.312" stroke="#000" stroke-width="17.724" stroke-linecap="round" stroke-linejoin="round"/><rect width="105.668" height="144.183" x="53.232" y="458.527" rx="30.101" ry="30.101" fill="#fff"/></g><g transform="translate(111.528 -446.364)"><rect width="105.312" height="143.698" x="53.41" y="458.77" rx="30" ry="30" stroke="#000" stroke-width="17.724" stroke-linecap="round" stroke-linejoin="round"/><rect ry="30.101" rx="30.101" y="458.527" x="53.232" height="144.183" width="105.668" fill="#fff"/></g></svg>

After

Width:  |  Height:  |  Size: 721 B

1
docs/static/loader_front.svg vendored Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="301.499" height="179.742" viewBox="0 0 282.655 168.508"><g stroke="#000" stroke-linecap="round" stroke-linejoin="round"><g transform="translate(124.66 -243.829)"><circle r="25.968" cy="356.872" cx="-38.891" fill="none"/><circle cx="-38.891" cy="356.872" r="21.013" stroke-width=".809"/><path d="M-30.581 341.741a13.835 13.835 0 0 1 5.76 4.504l-11.099 8.26z" fill="#fff" stroke-width=".533"/></g><g transform="translate(277.193 -243.829)"><circle cx="-38.891" cy="356.872" r="25.968" fill="none"/><circle r="21.013" cy="356.872" cx="-38.891" stroke-width=".809"/><path d="M-30.581 341.741a13.835 13.835 0 0 1 5.76 4.504l-11.099 8.26z" fill="#fff" stroke-width=".533"/></g></g></svg>

After

Width:  |  Height:  |  Size: 728 B

1
docs/static/loader_full.svg vendored Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="301.499" height="179.742" viewBox="0 0 282.655 168.508"><g transform="translate(-41.005 -446.364)"><rect ry="30" rx="30" y="458.77" x="53.41" height="143.698" width="105.312" stroke="#000" stroke-width="17.724" stroke-linecap="round" stroke-linejoin="round"/><rect width="105.668" height="144.183" x="53.232" y="458.527" rx="30.101" ry="30.101" fill="#fff"/></g><g transform="translate(124.66 -243.829)" stroke="#000" stroke-linecap="round" stroke-linejoin="round"><circle r="25.968" cy="356.872" cx="-38.891" fill="none"/><circle cx="-38.891" cy="356.872" r="21.013" stroke-width=".809"/><path d="M-30.581 341.741a13.835 13.835 0 0 1 5.76 4.504l-11.099 8.26z" fill="#fff" stroke-width=".533"/></g><g transform="translate(111.528 -446.364)"><rect width="105.312" height="143.698" x="53.41" y="458.77" rx="30" ry="30" stroke="#000" stroke-width="17.724" stroke-linecap="round" stroke-linejoin="round"/><rect ry="30.101" rx="30.101" y="458.527" x="53.232" height="144.183" width="105.668" fill="#fff"/></g><g transform="translate(277.193 -243.829)" stroke="#000" stroke-linecap="round" stroke-linejoin="round"><circle cx="-38.891" cy="356.872" r="25.968" fill="none"/><circle r="21.013" cy="356.872" cx="-38.891" stroke-width=".809"/><path d="M-30.581 341.741a13.835 13.835 0 0 1 5.76 4.504l-11.099 8.26z" fill="#fff" stroke-width=".533"/></g></svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

134
editor/deploy.ts Normal file
View File

@ -0,0 +1,134 @@
/// <reference path="../node_modules/pxt-core/built/pxteditor.d.ts"/>
/// <reference path="../node_modules/pxt-core/built/pxtsim.d.ts"/>
import UF2 = pxtc.UF2;
export let ev3: pxt.editor.Ev3Wrapper
export function debug() {
return initAsync()
.then(w => w.downloadFileAsync("/tmp/dmesg.txt", v => console.log(pxt.Util.uint8ArrayToString(v))))
}
function hf2Async() {
return pxt.HF2.mkPacketIOAsync()
.then(h => {
let w = new pxt.editor.Ev3Wrapper(h)
ev3 = w
return w.reconnectAsync(true)
.then(() => w)
})
}
let noHID = false
let initPromise: Promise<pxt.editor.Ev3Wrapper>
export function initAsync() {
if (initPromise)
return initPromise
let canHID = false
if (pxt.U.isNodeJS) {
// doesn't seem to work ATM
canHID = false
} else {
const forceHexDownload = /forceHexDownload/i.test(window.location.href);
if (pxt.Cloud.isLocalHost() && pxt.Cloud.localToken && !forceHexDownload)
canHID = true
}
if (noHID)
canHID = false
if (canHID) {
initPromise = hf2Async()
.catch(err => {
initPromise = null
noHID = true
return Promise.reject(err)
})
} else {
noHID = true
initPromise = Promise.reject(new Error("no HID"))
}
return initPromise
}
// this comes from aux/pxt.lms
const rbfTemplate = `
4c45474f580000006d000100000000001c000000000000000e000000821b038405018130813e8053
74617274696e672e2e2e0084006080XX00448581644886488405018130813e80427965210084000a
`
export function deployCoreAsync(resp: pxtc.CompileResult, isCli = false) {
let w: pxt.editor.Ev3Wrapper
let filename = resp.downloadFileBaseName || "pxt"
filename = filename.replace(/^lego-/, "")
let fspath = "../prjs/BrkProg_SAVE/"
let elfPath = fspath + filename + ".elf"
let rbfPath = fspath + filename + ".rbf"
let rbfHex = rbfTemplate
.replace(/\s+/g, "")
.replace("XX", pxt.U.toHex(pxt.U.stringToUint8Array(elfPath)))
let rbfBIN = pxt.U.fromHex(rbfHex)
pxt.HF2.write16(rbfBIN, 4, rbfBIN.length)
let origElfUF2 = UF2.parseFile(pxt.U.stringToUint8Array(atob(resp.outfiles[pxt.outputName()])))
let mkFile = (ext: string, data: Uint8Array = null) => {
let f = UF2.newBlockFile()
f.filename = "Projects/" + filename + ext
if (data)
UF2.writeBytes(f, 0, data)
return f
}
let elfUF2 = mkFile(".elf")
for (let b of origElfUF2) {
UF2.writeBytes(elfUF2, b.targetAddr, b.data)
}
let r = UF2.concatFiles([elfUF2, mkFile(".rbf", rbfBIN)])
let data = UF2.serializeFile(r)
resp.outfiles[pxtc.BINARY_UF2] = btoa(data)
let saveUF2Async = () => {
if (isCli || !pxt.commands.saveOnlyAsync) {
return Promise.resolve()
} else {
return pxt.commands.saveOnlyAsync(resp)
}
}
if (noHID) return saveUF2Async()
return initAsync()
.then(w_ => {
w = w_
if (w.isStreaming)
pxt.U.userError("please stop the program first")
return w.stopAsync()
})
.then(() => w.rmAsync(elfPath))
.then(() => w.flashAsync(elfPath, UF2.readBytes(origElfUF2, 0, origElfUF2.length * 256)))
.then(() => w.flashAsync(rbfPath, rbfBIN))
.then(() => w.runAsync(rbfPath))
.then(() => {
if (isCli)
return w.disconnectAsync()
else
return Promise.resolve()
//return Promise.delay(1000).then(() => w.dmesgAsync())
}).catch(e => {
// if we failed to initalize, retry
if (noHID)
return saveUF2Async()
else
return Promise.reject(e)
})
}

View File

@ -1,242 +1,120 @@
/// <reference path="../node_modules/pxt-core/built/pxteditor.d.ts" />
/// <reference path="../node_modules/pxt-core/built/pxteditor.d.ts"/>
/// <reference path="../node_modules/pxt-core/built/pxtsim.d.ts"/>
// When require()d from node, bind the global pxt namespace
namespace pxt {
export const dummyExport = 1;
import { deployCoreAsync, initAsync } from "./deploy";
import { FieldPorts } from "./field_ports";
import { FieldImages } from "./field_images";
pxt.editor.initExtensionsAsync = function(opts: pxt.editor.ExtensionOptions): Promise<pxt.editor.ExtensionResult> {
pxt.debug('loading pxt-ev3 target extensions...')
updateBlocklyShape();
const res: pxt.editor.ExtensionResult = {
fieldEditors: [{
selector: "ports",
editor: FieldPorts
}, {
selector: "images",
editor: FieldImages
}],
deployCoreAsync
};
initAsync().catch(e => {
// probably no HID - we'll try this again upon deployment
})
return Promise.resolve<pxt.editor.ExtensionResult>(res);
}
eval("if (typeof process === 'object' && process + '' === '[object process]') pxt = global.pxt")
namespace pxt.editor {
import UF2 = pxtc.UF2;
export let ev3: Ev3Wrapper
export function debug() {
return initAsync()
.then(w => w.downloadFileAsync("/tmp/dmesg.txt", v => console.log(pxt.Util.uint8ArrayToString(v))))
}
// this comes from aux/pxt.lms
const rbfTemplate = `
4c45474f580000006d000100000000001c000000000000000e000000821b038405018130813e8053
74617274696e672e2e2e0084006080XX00448581644886488405018130813e80427965210084000a
`
function hf2Async() {
return pxt.HF2.mkPacketIOAsync()
.then(h => {
let w = new Ev3Wrapper(h)
ev3 = w
return w.reconnectAsync(true)
.then(() => w)
})
}
let noHID = false
let initPromise: Promise<Ev3Wrapper>
function initAsync() {
if (initPromise)
return initPromise
let canHID = false
if (U.isNodeJS) {
canHID = true
} else {
const forceHexDownload = /forceHexDownload/i.test(window.location.href);
if (Cloud.isLocalHost() && Cloud.localToken && !forceHexDownload)
canHID = true
}
if (noHID)
canHID = false
if (canHID) {
initPromise = hf2Async()
.catch(err => {
initPromise = null
noHID = true
return Promise.reject(err)
})
} else {
noHID = true
initPromise = Promise.reject(new Error("no HID"))
}
return initPromise
}
export function deployCoreAsync(resp: pxtc.CompileResult, isCli = false) {
let w: Ev3Wrapper
let filename = resp.downloadFileBaseName || "pxt"
filename = filename.replace(/^lego-/, "")
let fspath = "../prjs/BrkProg_SAVE/"
let elfPath = fspath + filename + ".elf"
let rbfPath = fspath + filename + ".rbf"
let rbfHex = rbfTemplate
.replace(/\s+/g, "")
.replace("XX", U.toHex(U.stringToUint8Array(elfPath)))
let rbfBIN = U.fromHex(rbfHex)
HF2.write16(rbfBIN, 4, rbfBIN.length)
let origElfUF2 = UF2.parseFile(U.stringToUint8Array(atob(resp.outfiles[pxt.outputName()])))
let mkFile = (ext: string, data: Uint8Array = null) => {
let f = UF2.newBlockFile()
f.filename = "Projects/" + filename + ext
if (data)
UF2.writeBytes(f, 0, data)
return f
}
let elfUF2 = mkFile(".elf")
for (let b of origElfUF2) {
UF2.writeBytes(elfUF2, b.targetAddr, b.data)
}
let r = UF2.concatFiles([elfUF2, mkFile(".rbf", rbfBIN)])
let data = UF2.serializeFile(r)
resp.outfiles[pxtc.BINARY_UF2] = btoa(data)
let saveUF2Async = () => {
if (isCli || !pxt.commands.saveOnlyAsync) {
return Promise.resolve()
} else {
return pxt.commands.saveOnlyAsync(resp)
}
}
if (noHID) return saveUF2Async()
return initAsync()
.then(w_ => {
w = w_
if (w.isStreaming)
U.userError("please stop the program first")
return w.stopAsync()
})
.then(() => w.rmAsync(elfPath))
.then(() => w.flashAsync(elfPath, UF2.readBytes(origElfUF2, 0, origElfUF2.length * 256)))
.then(() => w.flashAsync(rbfPath, rbfBIN))
.then(() => w.runAsync(rbfPath))
.then(() => {
if (isCli)
return w.disconnectAsync()
else
return Promise.resolve()
//return Promise.delay(1000).then(() => w.dmesgAsync())
}).catch(e => {
// if we failed to initalize, retry
if (noHID)
return saveUF2Async()
else
return Promise.reject(e)
})
}
/**
* Update the shape of Blockly blocks with square corners
*/
function updateBlocklyShape() {
/**
* Update the shape of Blockly blocks with square corners
* Rounded corner radius.
* @const
*/
function updateBlocklyShape() {
(Blockly.BlockSvg as any).CORNER_RADIUS = 0 * (Blockly.BlockSvg as any).GRID_UNIT;
/**
* Rounded corner radius.
* @const
*/
(Blockly.BlockSvg as any).CORNER_RADIUS = 0 * (Blockly.BlockSvg as any).GRID_UNIT;
/**
* Inner space between edge of statement input and notch.
* @const
*/
(Blockly.BlockSvg as any).STATEMENT_INPUT_INNER_SPACE = 3 * (Blockly.BlockSvg as any).GRID_UNIT;
/**
* SVG path for drawing next/previous notch from left to right.
* @const
*/
(Blockly.BlockSvg as any).NOTCH_PATH_LEFT = (
'l 8,8 ' +
'h 16 ' +
'l 8,-8 '
);
/**
* Inner space between edge of statement input and notch.
* @const
*/
(Blockly.BlockSvg as any).STATEMENT_INPUT_INNER_SPACE = 3 * (Blockly.BlockSvg as any).GRID_UNIT;
/**
* SVG path for drawing next/previous notch from left to right.
* @const
*/
(Blockly.BlockSvg as any).NOTCH_PATH_LEFT = (
'l 8,8 ' +
'h 16 ' +
'l 8,-8 '
);
/**
* SVG path for drawing next/previous notch from right to left.
* @const
*/
(Blockly.BlockSvg as any).NOTCH_PATH_RIGHT = (
'l -8,8 ' +
'h -16 ' +
'l -8,-8 '
);
/**
* SVG path for drawing next/previous notch from right to left.
* @const
*/
(Blockly.BlockSvg as any).NOTCH_PATH_RIGHT = (
'l -8,8 ' +
'h -16 ' +
'l -8,-8 '
);
/**
* SVG start point for drawing the top-left corner.
* @const
*/
(Blockly.BlockSvg as any).TOP_LEFT_CORNER_START =
'm 0,' + 0;
/**
* SVG start point for drawing the top-left corner.
* @const
*/
(Blockly.BlockSvg as any).TOP_LEFT_CORNER_START =
'm 0,' + 0;
/**
* SVG path for drawing the rounded top-left corner.
* @const
*/
(Blockly.BlockSvg as any).TOP_LEFT_CORNER =
'l ' + (Blockly.BlockSvg as any).CORNER_RADIUS + ',0 ';
/**
* SVG path for drawing the rounded top-left corner.
* @const
*/
(Blockly.BlockSvg as any).TOP_LEFT_CORNER =
'l ' + (Blockly.BlockSvg as any).CORNER_RADIUS + ',0 ';
/**
* SVG path for drawing the rounded top-right corner.
* @const
*/
(Blockly.BlockSvg as any).TOP_RIGHT_CORNER =
'l ' + 0 + ',' + (Blockly.BlockSvg as any).CORNER_RADIUS;
/**
* SVG path for drawing the rounded top-right corner.
* @const
*/
(Blockly.BlockSvg as any).TOP_RIGHT_CORNER =
'l ' + 0 + ',' + (Blockly.BlockSvg as any).CORNER_RADIUS;
/**
* SVG path for drawing the rounded bottom-right corner.
* @const
*/
(Blockly.BlockSvg as any).BOTTOM_RIGHT_CORNER =
'l 0,' + (Blockly.BlockSvg as any).CORNER_RADIUS;
/**
* SVG path for drawing the rounded bottom-right corner.
* @const
*/
(Blockly.BlockSvg as any).BOTTOM_RIGHT_CORNER =
'l 0,' + (Blockly.BlockSvg as any).CORNER_RADIUS;
/**
* SVG path for drawing the rounded bottom-left corner.
* @const
*/
(Blockly.BlockSvg as any).BOTTOM_LEFT_CORNER =
'l -' + (Blockly.BlockSvg as any).CORNER_RADIUS + ',0';
/**
* SVG path for drawing the rounded bottom-left corner.
* @const
*/
(Blockly.BlockSvg as any).BOTTOM_LEFT_CORNER =
'l -' + (Blockly.BlockSvg as any).CORNER_RADIUS + ',0';
/**
* SVG path for drawing the top-left corner of a statement input.
* @const
*/
(Blockly.BlockSvg as any).INNER_TOP_LEFT_CORNER =
'l ' + (Blockly.BlockSvg as any).CORNER_RADIUS + ',-' + 0;
/**
* SVG path for drawing the top-left corner of a statement input.
* @const
*/
(Blockly.BlockSvg as any).INNER_TOP_LEFT_CORNER =
'l ' + (Blockly.BlockSvg as any).CORNER_RADIUS + ',-' + 0;
/**
* SVG path for drawing the bottom-left corner of a statement input.
* Includes the rounded inside corner.
* @const
*/
(Blockly.BlockSvg as any).INNER_BOTTOM_LEFT_CORNER =
'l ' + 0 + ',' + (Blockly.BlockSvg as any).CORNER_RADIUS * 2 +
'l ' + (Blockly.BlockSvg as any).CORNER_RADIUS + ',' + 0;
/**
* SVG path for drawing the bottom-left corner of a statement input.
* Includes the rounded inside corner.
* @const
*/
(Blockly.BlockSvg as any).INNER_BOTTOM_LEFT_CORNER =
'l ' + 0 + ',' + (Blockly.BlockSvg as any).CORNER_RADIUS * 2 +
'l ' + (Blockly.BlockSvg as any).CORNER_RADIUS + ',' + 0;
}
initExtensionsAsync = function (opts: pxt.editor.ExtensionOptions): Promise<pxt.editor.ExtensionResult> {
pxt.debug('loading pxt-ev3 target extensions...')
updateBlocklyShape();
const res: pxt.editor.ExtensionResult = {
deployCoreAsync,
};
initAsync().catch(e => {
// probably no HID - we'll try this again upon deployment
})
return Promise.resolve<pxt.editor.ExtensionResult>(res);
}
}
// When require()d from node, bind the global pxt namespace
// namespace pxt {
// export const dummyExport = 1;
// }
// eval("if (typeof process === 'object' && process + '' === '[object process]') pxt = global.pxt")

114
editor/field_images.ts Normal file
View File

@ -0,0 +1,114 @@
/// <reference path="../node_modules/pxt-core/localtypings/blockly.d.ts"/>
/// <reference path="../node_modules/pxt-core/built/pxtblocks.d.ts"/>
/// <reference path="../node_modules/pxt-core/built/pxtsim.d.ts"/>
export interface FieldImagesOptions extends pxtblockly.FieldImageDropdownOptions {
}
export class FieldImages extends pxtblockly.FieldImageDropdown implements Blockly.FieldCustom {
public isFieldCustom_ = true;
constructor(text: string, options: FieldImagesOptions, validator?: Function) {
super(text, options, validator);
}
/**
* Create a dropdown menu under the text.
* @private
*/
public showEditor_() {
// If there is an existing drop-down we own, this is a request to hide the drop-down.
if (Blockly.DropDownDiv.hideIfOwner(this)) {
return;
}
// If there is an existing drop-down someone else owns, hide it immediately and clear it.
Blockly.DropDownDiv.hideWithoutAnimation();
Blockly.DropDownDiv.clearContent();
// Populate the drop-down with the icons for this field.
let dropdownDiv = Blockly.DropDownDiv.getContentDiv();
let contentDiv = document.createElement('div');
// Accessibility properties
contentDiv.setAttribute('role', 'menu');
contentDiv.setAttribute('aria-haspopup', 'true');
const options = this.getOptions();
for (let i = 0, option: any; option = options[i]; i++) {
let content = (options[i] as any)[0]; // Human-readable text or image.
const value = (options[i] as any)[1]; // Language-neutral value.
// Icons with the type property placeholder take up space but don't have any functionality
// Use for special-case layouts
if (content.type == 'placeholder') {
let placeholder = document.createElement('span');
placeholder.setAttribute('class', 'blocklyDropDownPlaceholder');
placeholder.style.width = content.width + 'px';
placeholder.style.height = content.height + 'px';
contentDiv.appendChild(placeholder);
continue;
}
let button = document.createElement('button');
button.setAttribute('id', ':' + i); // For aria-activedescendant
button.setAttribute('role', 'menuitem');
button.setAttribute('class', 'blocklyDropDownButton');
button.title = content.alt;
if ((this as any).columns_) {
button.style.width = (((this as any).width_ / (this as any).columns_) - 8) + 'px';
//button.style.height = ((this.width_ / this.columns_) - 8) + 'px';
} else {
button.style.width = content.width + 'px';
button.style.height = content.height + 'px';
}
let backgroundColor = this.sourceBlock_.getColour();
if (value == this.getValue()) {
// This icon is selected, show it in a different colour
backgroundColor = this.sourceBlock_.getColourTertiary();
button.setAttribute('aria-selected', 'true');
}
button.style.backgroundColor = backgroundColor;
button.style.borderColor = this.sourceBlock_.getColourTertiary();
Blockly.bindEvent_(button, 'click', this, (this as any).buttonClick_);
Blockly.bindEvent_(button, 'mouseup', this, (this as any).buttonClick_);
// These are applied manually instead of using the :hover pseudoclass
// because Android has a bad long press "helper" menu and green highlight
// that we must prevent with ontouchstart preventDefault
Blockly.bindEvent_(button, 'mousedown', button, function (e) {
this.setAttribute('class', 'blocklyDropDownButton blocklyDropDownButtonHover');
e.preventDefault();
});
Blockly.bindEvent_(button, 'mouseover', button, function () {
this.setAttribute('class', 'blocklyDropDownButton blocklyDropDownButtonHover');
contentDiv.setAttribute('aria-activedescendant', this.id);
});
Blockly.bindEvent_(button, 'mouseout', button, function () {
this.setAttribute('class', 'blocklyDropDownButton');
contentDiv.removeAttribute('aria-activedescendant');
});
let buttonImg = document.createElement('img');
buttonImg.src = content.src;
//buttonImg.alt = icon.alt;
// Upon click/touch, we will be able to get the clicked element as e.target
// Store a data attribute on all possible click targets so we can match it to the icon.
button.setAttribute('data-value', value);
buttonImg.setAttribute('data-value', value);
button.appendChild(buttonImg);
contentDiv.appendChild(button);
}
contentDiv.style.width = (this as any).width_ + 'px';
dropdownDiv.appendChild(contentDiv);
Blockly.DropDownDiv.setColour(this.sourceBlock_.getColour(), this.sourceBlock_.getColourTertiary());
// Calculate positioning based on the field position.
var scale = this.sourceBlock_.workspace.scale;
var bBox = { width: this.size_.width, height: this.size_.height };
bBox.width *= scale;
bBox.height *= scale;
var position = this.fieldGroup_.getBoundingClientRect();
var primaryX = position.left + bBox.width / 2;
var primaryY = position.top + bBox.height;
var secondaryX = primaryX;
var secondaryY = position.top;
// Set bounds to workspace; show the drop-down.
(Blockly.DropDownDiv as any).setBoundsElement(this.sourceBlock_.workspace.getParentSvg().parentNode);
(Blockly.DropDownDiv as any).show(this, primaryX, primaryY, secondaryX, secondaryY,
(this as any).onHide_.bind(this));
}
}

147
editor/field_ports.ts Normal file
View File

@ -0,0 +1,147 @@
/// <reference path="../node_modules/pxt-core/localtypings/blockly.d.ts"/>
/// <reference path="../node_modules/pxt-core/built/pxtsim.d.ts"/>
export interface FieldPortsOptions extends Blockly.FieldCustomDropdownOptions {
columns?: string;
width?: string;
}
export class FieldPorts extends Blockly.FieldDropdown implements Blockly.FieldCustom {
public isFieldCustom_ = true;
// Width in pixels
private width_: number;
// Columns in grid
private columns_: number;
private savedPrimary_: string;
constructor(text: string, options: FieldPortsOptions, validator?: Function) {
super(options.data);
this.columns_ = parseInt(options.columns) || 4;
this.width_ = parseInt(options.width) || 300;
}
/**
* Create a dropdown menu under the text.
* @private
*/
public showEditor_() {
// If there is an existing drop-down we own, this is a request to hide the drop-down.
if (Blockly.DropDownDiv.hideIfOwner(this)) {
return;
}
// If there is an existing drop-down someone else owns, hide it immediately and clear it.
Blockly.DropDownDiv.hideWithoutAnimation();
Blockly.DropDownDiv.clearContent();
// Populate the drop-down with the icons for this field.
let dropdownDiv = Blockly.DropDownDiv.getContentDiv();
let contentDiv = document.createElement('div');
// Accessibility properties
contentDiv.setAttribute('role', 'menu');
contentDiv.setAttribute('aria-haspopup', 'true');
const options = this.getOptions();
for (let i = 0, option: any; option = options[i]; i++) {
let content = (options[i] as any)[0]; // Human-readable text or image.
const value = (options[i] as any)[1]; // Language-neutral value.
// Icons with the type property placeholder take up space but don't have any functionality
// Use for special-case layouts
if (content.type == 'placeholder') {
let placeholder = document.createElement('span');
placeholder.setAttribute('class', 'blocklyDropDownPlaceholder');
placeholder.style.width = content.width + 'px';
placeholder.style.height = content.height + 'px';
contentDiv.appendChild(placeholder);
continue;
}
let button = document.createElement('button');
button.setAttribute('id', ':' + i); // For aria-activedescendant
button.setAttribute('role', 'menuitem');
button.setAttribute('class', 'blocklyDropDownButton');
button.title = content.alt;
if (this.columns_) {
button.style.width = ((this.width_ / this.columns_) - 8) + 'px';
button.style.height = ((this.width_ / this.columns_) - 8) + 'px';
} else {
button.style.width = content.width + 'px';
button.style.height = content.height + 'px';
}
let backgroundColor = this.sourceBlock_.getColour();
if (value == this.getValue()) {
// This icon is selected, show it in a different colour
backgroundColor = this.sourceBlock_.getColourTertiary();
button.setAttribute('aria-selected', 'true');
}
button.style.backgroundColor = backgroundColor;
button.style.borderColor = this.sourceBlock_.getColourTertiary();
Blockly.bindEvent_(button, 'click', this, this.buttonClick_);
Blockly.bindEvent_(button, 'mouseup', this, this.buttonClick_);
// These are applied manually instead of using the :hover pseudoclass
// because Android has a bad long press "helper" menu and green highlight
// that we must prevent with ontouchstart preventDefault
Blockly.bindEvent_(button, 'mousedown', button, function (e) {
this.setAttribute('class', 'blocklyDropDownButton blocklyDropDownButtonHover');
e.preventDefault();
});
Blockly.bindEvent_(button, 'mouseover', button, function () {
this.setAttribute('class', 'blocklyDropDownButton blocklyDropDownButtonHover');
contentDiv.setAttribute('aria-activedescendant', this.id);
});
Blockly.bindEvent_(button, 'mouseout', button, function () {
this.setAttribute('class', 'blocklyDropDownButton');
contentDiv.removeAttribute('aria-activedescendant');
});
let buttonImg = document.createElement('img');
buttonImg.src = content.src;
//buttonImg.alt = icon.alt;
// Upon click/touch, we will be able to get the clicked element as e.target
// Store a data attribute on all possible click targets so we can match it to the icon.
button.setAttribute('data-value', value);
buttonImg.setAttribute('data-value', value);
button.appendChild(buttonImg);
contentDiv.appendChild(button);
}
contentDiv.style.width = this.width_ + 'px';
dropdownDiv.appendChild(contentDiv);
Blockly.DropDownDiv.setColour(this.sourceBlock_.getColour(), this.sourceBlock_.getColourTertiary());
// Calculate positioning based on the field position.
var scale = this.sourceBlock_.workspace.scale;
var bBox = { width: this.size_.width, height: this.size_.height };
bBox.width *= scale;
bBox.height *= scale;
var position = this.fieldGroup_.getBoundingClientRect();
var primaryX = position.left + bBox.width / 2;
var primaryY = position.top + bBox.height;
var secondaryX = primaryX;
var secondaryY = position.top;
// Set bounds to workspace; show the drop-down.
(Blockly.DropDownDiv as any).setBoundsElement(this.sourceBlock_.workspace.getParentSvg().parentNode);
(Blockly.DropDownDiv as any).show(this, primaryX, primaryY, secondaryX, secondaryY,
this.onHide_.bind(this));
}
/**
* Callback for when a button is clicked inside the drop-down.
* Should be bound to the FieldIconMenu.
* @param {Event} e DOM event for the click/touch
* @private
*/
private buttonClick_ = function (e: any) {
let value = e.target.getAttribute('data-value');
this.setValue(value);
Blockly.DropDownDiv.hide();
};
/**
* Callback for when the drop-down is hidden.
*/
private onHide_ = function () {
Blockly.DropDownDiv.content_.removeAttribute('role');
Blockly.DropDownDiv.content_.removeAttribute('aria-haspopup');
Blockly.DropDownDiv.content_.removeAttribute('aria-activedescendant');
};
}

View File

@ -1,12 +1,14 @@
{
"compilerOptions": {
"target": "es5",
"noImplicitAny": true,
"noImplicitAny": false,
"noImplicitReturns": true,
"declaration": true,
"out": "../built/editor.js",
"module": "commonjs",
"outDir": "../built/editor",
"rootDir": ".",
"newLine": "LF",
"sourceMap": false
"sourceMap": false,
"allowSyntheticDefaultImports": true,
"declaration": true
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 375 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 384 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 383 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 418 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 386 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 442 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 404 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 430 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 428 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 426 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 425 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 428 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 422 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 429 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 423 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 413 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 408 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 478 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 407 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 408 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 351 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 346 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 366 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 382 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 424 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 270 KiB

View File

@ -0,0 +1,14 @@
{
"behaviors": "Behavior drive blocks",
"behaviors.Behavior": "A behavior",
"behaviors.BehaviorManager": "A manager for behaviors",
"behaviors.BehaviorManager.add": "Adds a new behavior to the behavior manager",
"behaviors.BehaviorManager.add|param|behavior": "the behavior to add",
"behaviors.BehaviorManager.start": "Starts the behavior control loop",
"behaviors.BehaviorManager.stop": "Stops the execution loop",
"behaviors.addBehavior": "Adds the behavior and starts it",
"behaviors.addBehavior|param|behavior": "a behavior",
"behaviors.avoidCrash": "A behavior that stops all motors if the sensor distance get too short",
"behaviors.driveForward": "A behavior that turns on the motors to the specified speed",
"behaviors.driveForward|param|motors": "@param speed the desired speed, eg: 50"
}

View File

@ -0,0 +1,7 @@
{
"behaviors.addBehavior|block": "add behavior %behavior",
"behaviors.avoidCrash|block": "avoid crash using %ultrasonic",
"behaviors.driveForward|block": "drive %motors|forward at %speed|%",
"behaviors|block": "behaviors",
"{id:category}Behaviors": "Behaviors"
}

6
libs/behaviors/pxt.json Normal file
View File

@ -0,0 +1,6 @@
{
"additionalFilePath": "../../node_modules/pxt-common-packages/libs/behaviors",
"dependencies": {
"core": "file:../ev3"
}
}

View File

@ -0,0 +1,56 @@
namespace behaviors {
class AvoidCrashBehavior extends behaviors.Behavior {
private ultrasonic: sensors.UltraSonicSensor;
constructor(ultrasonic: sensors.UltraSonicSensor) {
super();
this.ultrasonic = ultrasonic;
}
shouldRun(): boolean {
return this.ultrasonic.distance() < 5;
}
run(): void {
motors.stopAllMotors();
this.active = false;
}
}
/**
* A behavior that stops all motors if the sensor distance get too short
*/
//% blockId=behaviorsAvoidCrash block="avoid crash using %ultrasonic"
export function avoidCrash(ultrasonic: sensors.UltraSonicSensor) : behaviors.Behavior {
return new AvoidCrashBehavior(ultrasonic);
}
class DriveForwardBehavior extends behaviors.Behavior {
private motors: motors.MotorBase;
private speed: number;
constructor(motors: motors.MotorBase, speed: number) {
super();
this.motors = motors;
this.speed = speed;
}
shouldRun(): boolean {
return true;
}
run(): void {
this.motors.setSpeed(this.speed);
pauseUntil(() => !this.active);
this.motors.setSpeed(0);
}
}
/**
* A behavior that turns on the motors to the specified speed
* @param motors
* @param speed the desired speed, eg: 50
*/
//% blockId=behaviorsDriveForward block="drive %motors|forward at %speed|%"
export function driveForward(motors: motors.MotorBase, speed: number): behaviors.Behavior {
return new DriveForwardBehavior(motors, speed);
}
}

View File

@ -111,6 +111,7 @@ namespace sensors {
//% blockId=colorOnColorDetected
//% parts="colorsensor"
//% blockNamespace=sensors
//% sensor.fieldEditor="ports"
//% weight=100 blockGap=8
//% group="Color Sensor"
onColorDetected(color: ColorSensorColor, handler: () => void) {
@ -130,6 +131,7 @@ namespace sensors {
//% blockId=colorPauseForColorDetected
//% parts="colorsensor"
//% blockNamespace=sensors
//% sensor.fieldEditor="ports"
//% weight=99 blockGap=8
//% group="Color Sensor"
pauseForColor(color: ColorSensorColor) {
@ -149,6 +151,7 @@ namespace sensors {
//% blockId=colorGetColor
//% parts="colorsensor"
//% blockNamespace=sensors
//% sensor.fieldEditor="ports"
//% weight=99
//% group="Color Sensor"
color(): ColorSensorColor {
@ -166,6 +169,7 @@ namespace sensors {
//% blockId=colorOnLightChanged
//% parts="colorsensor"
//% blockNamespace=sensors
//% sensor.fieldEditor="ports"
//% weight=89 blockGap=8
//% group="Color Sensor"
onLightChanged(mode: LightIntensityMode, condition: LightCondition, handler: () => void) {
@ -182,6 +186,7 @@ namespace sensors {
//% blockId=colorPauseForLight
//% parts="colorsensor"
//% blockNamespace=sensors
//% sensor.fieldEditor="ports"
//% weight=88 blockGap=8
//% group="Color Sensor"
pauseForLight(mode: LightIntensityMode, condition: LightCondition) {
@ -199,6 +204,7 @@ namespace sensors {
//% blockId=colorLight
//% parts="colorsensor"
//% blockNamespace=sensors
//% sensor.fieldEditor="ports"
//% weight=87
//% group="Color Sensor"
light(mode: LightIntensityMode) {

View File

@ -21,6 +21,7 @@
"brick.Button.pauseUntil": "Waits until the event is raised",
"brick.Button.pauseUntil|param|ev": "the event to wait for",
"brick.Button.wasPressed": "See if the button was pressed again since the last time you checked.",
"brick.batteryLevel": "Returns the current battery level",
"brick.buttonDown": "Down button on the EV3 Brick.",
"brick.buttonEnter": "Enter button on the EV3 Brick.",
"brick.buttonLeft": "Left button on the EV3 Brick.",
@ -52,9 +53,10 @@
"control.raiseEvent": "Announce that an event happened to registered handlers.",
"control.raiseEvent|param|src": "ID of the Component that generated the event",
"control.raiseEvent|param|value": "Component specific code indicating the cause of the event.",
"motors.Motor.angle": "Gets motor ration angle.",
"motors.Motor.clearCount": "Clears the motor count",
"motors.Motor.angle": "Gets motor angle.",
"motors.Motor.clearCounts": "Clears the motor count",
"motors.Motor.speed": "Gets motor actual speed.",
"motors.Motor.tacho": "Gets motor tachometer count.",
"motors.Motor.toString": "Returns the status of the motor",
"motors.MotorBase.isReady": "Returns a value indicating if the motor is still running a previous command.",
"motors.MotorBase.move": "Moves the motor by a number of rotations, degress or seconds",

View File

@ -28,6 +28,7 @@
"brick.Button.onEvent|block": "on %button|%event",
"brick.Button.pauseUntil|block": "pause until %button|%event",
"brick.Button.wasPressed|block": "%button|was pressed",
"brick.batteryLevel|block": "battery level",
"brick.buttonDown|block": "down",
"brick.buttonEnter|block": "enter",
"brick.buttonLeft|block": "left",
@ -47,7 +48,9 @@
"control.raiseEvent|block": "raise event|from %src|with value %value",
"control|block": "control",
"motors.Motor.angle|block": "%motor|angle",
"motors.Motor.clearCounts|block": "%motor|clear counts",
"motors.Motor.speed|block": "%motor|speed",
"motors.Motor.tacho|block": "%motor|tacho",
"motors.MotorBase.move|block": "move %motor|for %value|%unit|at %speed|%",
"motors.MotorBase.pauseUntilReady|block": "%motor|pause until ready",
"motors.MotorBase.setBrake|block": "set %motor|brake %brake",
@ -87,6 +90,7 @@
"{id:group}Buttons": "Buttons",
"{id:group}Chassis": "Chassis",
"{id:group}Light": "Light",
"{id:group}More": "More",
"{id:group}Motion": "Motion",
"{id:group}Screen": "Screen",
"{id:group}Sensors": "Sensors"

12
libs/core/battery.ts Normal file
View File

@ -0,0 +1,12 @@
namespace brick {
/**
* Returns the current battery level
*/
//% blockId=brickBatteryLevel block="battery level"
//% group="More"
export function batteryLevel(): number {
const info = sensors.internal.getBatteryInfo();
return info.current;
}
}

View File

@ -163,6 +163,10 @@ namespace brick {
if (sl[i])
ret |= 1 << i
}
// this needs to be done in query(), which is run without the main JS execution mutex
// otherwise, while(true){} will lock the device
if (ret & DAL.BUTTON_ID_ESCAPE)
control.reset()
return ret
}
@ -172,8 +176,6 @@ namespace brick {
if (!btnsMM) control.fail("no buttons?")
buttons = []
sensors.internal.unsafePollForChanges(50, readButtons, (prev, curr) => {
if (curr & DAL.BUTTON_ID_ESCAPE)
control.reset()
for (let b of buttons)
b._update(!!(curr & b.mask))
})

View File

@ -89,6 +89,14 @@ namespace sensors.internal {
//serial.writeLine("UART " + port + " / " + mode + " - " + info)
}
export function getBatteryInfo(): { temp: number; current: number } {
init();
return {
temp: analogMM.getNumber(NumberFormat.Int16LE, AnalogOff.BatteryTemp),
current: analogMM.getNumber(NumberFormat.Int16LE, AnalogOff.BatteryCurrent)
}
}
function detectDevices() {
let conns = analogMM.slice(AnalogOff.InConn, DAL.NUM_INPUTS)
let numChanged = 0
@ -301,6 +309,10 @@ namespace sensors.internal {
return 0
return getUartNumber(fmt, off, this._port)
}
protected reset() {
if (this.isActive()) uartReset(this._port);
}
}
function uartReset(port: number) {

View File

@ -109,7 +109,7 @@ namespace motors {
* Stops all motors
*/
//% blockId=motorStopAll block="stop all motors"
//% weight=97
//% weight=5
//% group="Motion"
export function stopAllMotors() {
const b = mkCmd(Output.ALL, DAL.opOutputStop, 0)
@ -258,7 +258,7 @@ namespace motors {
/**
* Returns a value indicating if the motor is still running a previous command.
*/
//%
//% group="Sensors"
isReady(): boolean {
this.init();
const buf = mkCmd(this._port, DAL.opOutputTest, 2);
@ -278,6 +278,7 @@ namespace motors {
* @param timeOut optional maximum pausing time in milliseconds
*/
//% blockId=motorPauseUntilRead block="%motor|pause until ready"
//% weight=97
//% group="Motion"
pauseUntilReady(timeOut?: number) {
pauseUntil(() => this.isReady(), timeOut);
@ -330,7 +331,8 @@ namespace motors {
* @param motor the port which connects to the motor
*/
//% blockId=motorSpeed block="%motor|speed"
//% weight=72 blockGap=8
//% weight=72
//% blockGap=8
//% group="Sensors"
speed(): number {
this.init();
@ -338,10 +340,10 @@ namespace motors {
}
/**
* Gets motor ration angle.
* Gets motor angle.
* @param motor the port which connects to the motor
*/
//% blockId=motorTachoCount block="%motor|angle"
//% blockId=motorAngle block="%motor|angle"
//% weight=70
//% group="Sensors"
angle(): number {
@ -349,11 +351,28 @@ namespace motors {
return getMotorData(this._port).count;
}
/**
* Gets motor tachometer count.
* @param motor the port which connects to the motor
*/
//% blockId=motorTachoCount block="%motor|tacho"
//% weight=69
//% blockGap=8
//% group="Sensors"
tacho(): number {
this.init();
return getMotorData(this._port).tachoCount;
}
/**
* Clears the motor count
*/
//% group="Motion"
clearCount() {
//% blockId=motorClearCount block="%motor|clear counts"
//% weight=68
//% blockGap=8
//% group="Sensors"
clearCounts() {
this.init();
const b = mkCmd(this._port, DAL.opOutputClearCount, 0)
writePWM(b)
@ -426,7 +445,7 @@ namespace motors {
private __setSpeed(speed: number) {
syncMotors(this._port, {
speed: speed,
turnRatio: 0,
turnRatio: 100, // same speed
useBrake: !!this._brake
})
}

View File

@ -16,6 +16,7 @@
"png.cpp",
"screen.cpp",
"screen.ts",
"battery.ts",
"output.cpp",
"output.ts",
"core.ts",

View File

@ -151,9 +151,9 @@ namespace brick {
* @param image the image
*/
//% blockId=screen_image_picker block="%image" shim=TD_ID
//% image.fieldEditor="imagedropdown"
//% image.fieldEditor="images"
//% image.fieldOptions.columns=6
//% image.fieldOptions.hasSearchBar=true
//% image.fieldOptions.width=600
//% group="Screen" weight=0 blockHidden=1
export function __imagePicker(image: Image): Image {
return image;

View File

@ -19,6 +19,10 @@ namespace sensors {
namespace motors {
}
//% labelLineWidth=0
namespace behaviors {
}
//% color="#D67923" weight=80 icon="\uf10e"
namespace music {

View File

@ -1,4 +1,5 @@
{
"sensors.GyroSensor.angle": "Get the current angle from the gyroscope.",
"sensors.GyroSensor.rate": "Get the current rotation rate from the gyroscope."
"sensors.GyroSensor.calibrate": "Forces a calibration of the gyro. Must be called when the sensor is completely still.",
"sensors.GyroSensor.rotationRate": "Get the current rotation rate from the gyroscope."
}

View File

@ -1,6 +1,7 @@
{
"sensors.GyroSensor.angle|block": "%sensor|angle",
"sensors.GyroSensor.rate|block": "%sensor|rotation rate",
"sensors.GyroSensor.calibrate|block": "%sensor|calibrate",
"sensors.GyroSensor.rotationRate|block": "%sensor|rotation rate",
"sensors.gyro1|block": "gyro 1",
"sensors.gyro2|block": "gyro 2",
"sensors.gyro3|block": "gyro 3",

View File

@ -7,8 +7,10 @@ const enum GyroSensorMode {
namespace sensors {
//% fixedInstances
export class GyroSensor extends internal.UartSensor {
private calibrating: boolean;
constructor(port: number) {
super(port)
this.calibrating = false;
}
_deviceType() {
@ -28,9 +30,13 @@ namespace sensors {
//% blockId=gyroGetAngle
//% parts="gyroscope"
//% blockNamespace=sensors
//% sensor.fieldEditor="ports"
//% weight=65 blockGap=8
//% group="Gyro Sensor"
angle(): number {
if (this.calibrating)
pauseUntil(() => !this.calibrating, 2000);
this.setMode(GyroSensorMode.Angle)
return this.getNumber(NumberFormat.Int16LE, 0)
}
@ -39,25 +45,61 @@ namespace sensors {
* Get the current rotation rate from the gyroscope.
* @param sensor the gyroscope to query the request
*/
//% help=input/gyro/rate
//% help=input/gyro/rotation-rate
//% block="%sensor|rotation rate"
//% blockId=gyroGetRate
//% parts="gyroscope"
//% blockNamespace=sensors
//% sensor.fieldEditor="ports"
//% weight=65 blockGap=8
//% group="Gyro Sensor"
rate(): number {
rotationRate(): number {
if (this.calibrating)
pauseUntil(() => !this.calibrating, 2000);
this.setMode(GyroSensorMode.Rate)
return this.getNumber(NumberFormat.Int16LE, 0)
}
}
//% fixedInstance whenUsed block="gyro 1" jres=icons.port1
export const gyro1: GyroSensor = new GyroSensor(1)
/**
* Forces a calibration of the gyro. Must be called when the sensor is completely still.
*/
//% help=input/gyro/calibrate
//% block="%sensor|calibrate"
//% blockId=gyroCalibrate
//% parts="gyroscope"
//% blockNamespace=sensors
//% sensor.fieldEditor="ports"
//% weight=65 blockGap=8
//% group="Gyro Sensor"
calibrate(): void {
if (this.calibrating) return; // already in calibration mode
this.calibrating = true;
// may be triggered by a button click, give time to settle
loops.pause(500);
// send a reset command
this.reset();
// we need to switch mode twice to perform a calibration
if (this.mode == GyroSensorMode.Rate)
this.setMode(GyroSensorMode.Angle);
else
this.setMode(GyroSensorMode.Rate);
// switch back and wait
if (this.mode == GyroSensorMode.Rate)
this.setMode(GyroSensorMode.Angle);
else
this.setMode(GyroSensorMode.Rate);
this.calibrating = false;
}
}
//% fixedInstance whenUsed block="gyro 2" weight=95 jres=icons.port2
export const gyro2: GyroSensor = new GyroSensor(2)
//% fixedInstance whenUsed block="gyro 1" jres=icons.port1
export const gyro1: GyroSensor = new GyroSensor(1)
//% fixedInstance whenUsed block="gyro 3" jres=icons.port3
export const gyro3: GyroSensor = new GyroSensor(3)

View File

@ -266,8 +266,10 @@ namespace music {
//% blockId=music_play_sound_effect_until_done block="play sound effect %sound|until done"
//% weight=98 blockGap=8
export function playSoundEffectUntilDone(sound: Sound) {
if (!sound) return;
if (!sound || numSoundsPlaying >= soundsLimit) return;
numSoundsPlaying++;
sound.play();
numSoundsPlaying--;
}
/**

View File

@ -1,9 +1,11 @@
{
"tests": "Unit tests framework",
"TestEvent": "Various test event in the execution cycle",
"tests": "A Unit tests framework",
"tests.assert": "Checks a boolean condition",
"tests.assertClose": "Checks that 2 values are close to each other",
"tests.assertClose|param|actual": "what the value was",
"tests.assertClose|param|expected": "what the value should be",
"tests.assertClose|param|tolerance": "the acceptable error margin",
"tests.assertClose|param|tolerance": "the acceptable error margin, eg: 5",
"tests.onEvent": "Registers code to be called at various points in the test execution",
"tests.test": "Registers a test to run"
}

View File

@ -1,4 +1,9 @@
{
"TestEvent.RunSetUp|block": "run setup",
"TestEvent.RunTearDown|block": "run teardown",
"TestEvent.TestSetUp|block": "test setup",
"TestEvent.TestTearDown|block": "test teardown",
"tests.assertClose|block": "assert %message|%expected|close to %actual|by %tolerance",
"tests.assert|block": "assert %message|%condition",
"tests.test|block": "test %name",
"tests|block": "tests",

View File

@ -1,14 +1,6 @@
{
"name": "tests",
"description": "A unit test library",
"files": [
"README.md",
"tests.ts"
],
"testFiles": [
],
"public": true,
"additionalFilePath": "../../node_modules/pxt-common-packages/libs/tests",
"dependencies": {
"core": "file:../core"
"core": "file:../ev3"
}
}

View File

@ -0,0 +1,12 @@
// EV3 specific test functions
tests.onEvent(TestEvent.RunSetUp, function() {
console.sendToScreen();
})
tests.onEvent(TestEvent.TestSetUp, function() {
motors.stopAllMotors();
motors.resetAllMotors();
})
tests.onEvent(TestEvent.TestTearDown, function() {
motors.stopAllMotors();
motors.resetAllMotors();
})

View File

@ -1,94 +0,0 @@
/**
* Unit tests framework
*/
//% weight=100 color=#0fbc11 icon=""
namespace tests {
class Test {
name: string;
handler: () => void;
errors: string[];
constructor(name: string, handler: () => void) {
this.name = name;
this.handler = handler;
this.errors = [];
}
reset() {
motors.stopAllMotors();
motors.resetAllMotors();
}
run() {
// clear state
this.reset();
console.log(`> ${this.name}`)
this.handler()
if (this.errors.length)
console.log('')
// ensure clean state after test
this.reset();
}
}
let _tests: Test[] = undefined;
let _currentTest: Test = undefined;
function run() {
if (!_tests) return;
const start = control.millis();
console.sendToScreen();
console.log(`${_tests.length} tests found`)
console.log(` `)
for (let i = 0; i < _tests.length; ++i) {
const t = _currentTest = _tests[i];
t.run();
_currentTest = undefined;
}
console.log(` `)
console.log(`${_tests.length} tests, ${_tests.map(t => t.errors.length).reduce((p, c) => p + c, 0)} errs in ${Math.ceil((control.millis() - start) / 1000)}s`)
}
/**
* Registers a test to run
*/
//% blockId=testtest block="test %name"
export function test(name: string, handler: () => void): void {
if (!name || !handler) return;
if (!_tests) {
_tests = [];
control.runInBackground(function () {
// should run after on start
loops.pause(100)
run()
})
}
_tests.push(new Test(name, handler));
}
/**
* Checks a boolean condition
*/
//% blockId=testAssert block="assert %message|%condition"
export function assert(message: string, condition: boolean) {
if (!condition) {
console.log(`!!! ${message || ''}`)
if (_currentTest)
_currentTest.errors.push(message);
}
}
/**
* Checks that 2 values are close to each other
* @param expected what the value should be
* @param actual what the value was
* @param tolerance the acceptable error margin
*/
export function assertClose(name: string, expected: number, actual: number, tolerance: number) {
assert(`${name} ${expected} != ${actual} +-${tolerance}`, Math.abs(expected - actual) <= tolerance);
}
}

View File

@ -45,6 +45,7 @@ namespace sensors {
//% blockId=touchEvent block="on %sensor|%event"
//% parts="touch"
//% blockNamespace=sensors
//% sensor.fieldEditor="ports"
//% weight=99 blockGap=8
//% group="Touch Sensor"
onEvent(ev: TouchSensorEvent, body: () => void) {
@ -60,6 +61,7 @@ namespace sensors {
//% blockId=touchWaitUntil block="pause until %sensor|%event"
//% parts="touch"
//% blockNamespace=sensors
//% sensor.fieldEditor="ports"
//% weight=98 blockGap=8
//% group="Touch Sensor"
pauseUntil(ev: TouchSensorEvent) {
@ -75,6 +77,7 @@ namespace sensors {
//% blockId=touchIsPressed
//% parts="touch"
//% blockNamespace=sensors
//% sensor.fieldEditor="ports"
//% weight=81
//% group="Touch Sensor"
isPressed() {
@ -90,6 +93,7 @@ namespace sensors {
//% blockId=touchWasPressed
//% parts="touch"
//% blockNamespace=sensors
//% sensor.fieldEditor="ports"
//% weight=81 blockGap=8
//% group="Touch Sensor"
wasPressed() {

View File

@ -46,6 +46,7 @@ namespace sensors {
//% block="on %sensor|%event"
//% parts="ultrasonicsensor"
//% blockNamespace=sensors
//% sensor.fieldEditor="ports"
//% weight=100 blockGap=8
//% group="Ultrasonic Sensor"
onEvent(event: UltrasonicSensorEvent, handler: () => void) {
@ -60,6 +61,7 @@ namespace sensors {
//% blockId=ultrasonicWait
//% parts="ultrasonicsensor"
//% blockNamespace=sensors
//% sensor.fieldEditor="ports"
//% weight=99 blockGap=8
//% group="Ultrasonic Sensor"
pauseUntil(event: UltrasonicSensorEvent) {
@ -75,6 +77,7 @@ namespace sensors {
//% blockId=sonarGetDistance
//% parts="ultrasonicsensor"
//% blockNamespace=sensors
//% sensor.fieldEditor="ports"
//% weight=65
//% group="Ultrasonic Sensor"
distance(): number {

2
package-lock.json generated
View File

@ -1,6 +1,6 @@
{
"name": "pxt-ev3",
"version": "0.0.49",
"version": "0.0.51",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

View File

@ -1,6 +1,6 @@
{
"name": "pxt-ev3",
"version": "0.0.49",
"version": "0.0.53",
"description": "LEGO Mindstorms EV3 for Microsoft MakeCode",
"private": true,
"keywords": [
@ -44,7 +44,7 @@
"webfonts-generator": "^0.4.0"
},
"dependencies": {
"pxt-common-packages": "0.14.13",
"pxt-common-packages": "0.15.3",
"pxt-core": "3.0.5"
},
"scripts": {

View File

@ -16,7 +16,8 @@
"libs/infrared-sensor",
"libs/gyro-sensor",
"libs/ev3",
"libs/tests"
"libs/tests",
"libs/behaviors"
],
"simulator": {
"autoRun": true,
@ -94,6 +95,7 @@
"privacyUrl": "https://go.microsoft.com/fwlink/?LinkId=521839",
"termsOfUseUrl": "https://go.microsoft.com/fwlink/?LinkID=206977",
"githubUrl": "https://github.com/Microsoft/pxt-ev3",
"betaUrl": "https://lego.makecode.com/",
"boardName": "LEGO Mindstorms EV3 Brick",
"selectLanguage": true,
"highContrast": true,

View File

@ -3,24 +3,6 @@
/// <reference path="../built/common-sim.d.ts"/>
namespace pxsim {
export enum CPlayPinName {
A0,
A1,
A2,
A3,
A4,
A5,
A6,
A7,
A8,
A9,
D4,
D5,
D6,
D7,
D8,
D13
}
export class EV3Board extends CoreBoard {
view: SVGSVGElement;
@ -36,7 +18,7 @@ namespace pxsim {
brickNode: BrickNode;
outputNodes: MotorNode[] = [];
private motorMap: pxt.Map<number> = {
public motorMap: pxt.Map<number> = {
0x01: 0,
0x02: 1,
0x04: 2,
@ -115,27 +97,40 @@ namespace pxsim {
return this.brickNode;
}
motorUsed(port:number, large: boolean) {
for(let i = 0; i < DAL.NUM_OUTPUTS; ++i) {
motorUsed(port: number, large: boolean) {
for (let i = 0; i < DAL.NUM_OUTPUTS; ++i) {
const p = 1 << i;
if (port & p) {
const motorPort = this.motorMap[p];
if (!this.outputNodes[motorPort])
this.outputNodes[motorPort] = new MotorNode(motorPort, large);
}
}
}
}
}
hasMotor(port: number) {
for (let i = 0; i < DAL.NUM_OUTPUTS; ++i) {
const p = 1 << i;
if (port & p) {
const motorPort = this.motorMap[p];
const outputNode = this.outputNodes[motorPort];
if (outputNode)
return true;
}
}
return false;
}
getMotor(port: number, large?: boolean): MotorNode[] {
const r = [];
for(let i = 0; i < DAL.NUM_OUTPUTS; ++i) {
for (let i = 0; i < DAL.NUM_OUTPUTS; ++i) {
const p = 1 << i;
if (port & p) {
const motorPort = this.motorMap[p];
const outputNode = this.outputNodes[motorPort];
if (outputNode)
r.push(outputNode);
}
}
}
return r;
}
@ -144,6 +139,10 @@ namespace pxsim {
return this.outputNodes;
}
hasSensor(port: number) {
return !!this.inputNodes[port];
}
getSensor(port: number, type: number): SensorNode {
if (!this.inputNodes[port]) {
switch (type) {
@ -168,6 +167,7 @@ namespace pxsim {
runtime.postError = (e) => {
// TODO
runtime.updateDisplay();
console.log('runtime error: ' + e);
}
}

View File

@ -24,13 +24,14 @@ namespace pxsim {
}
export class EV3AnalogState {
constructor() {
let data = new Uint8Array(5172)
MMapMethods.register("/dev/lms_analog", {
data,
beforeMemRead: () => {
//console.log("analog before read");
data[AnalogOff.BatteryTemp] = 21; // TODO simulate this
data[AnalogOff.BatteryCurrent] = 100; // TODO simulate this
const inputNodes = ev3board().getInputNodes();
for (let port = 0; port < DAL.NUM_INPUTS; port++) {
const node = inputNodes[port];

View File

@ -1,10 +1,17 @@
import lf = pxsim.localization.lf;
namespace pxsim.motors {
export function __motorUsed(port: number, large: boolean) {
//console.log("MOTOR INIT " + port);
ev3board().motorUsed(port, large);
runtime.queueDisplayUpdate();
if (!ev3board().hasMotor(port)) {
ev3board().motorUsed(port, large);
runtime.queueDisplayUpdate();
} else {
U.userError(`${lf("Multiple motors are connected to Port")} ${String.fromCharCode('A'.charCodeAt(0) + ev3board().motorMap[port])}`);
}
}
}
@ -12,7 +19,11 @@ namespace pxsim.sensors {
export function __sensorUsed(port: number, type: number) {
//console.log("SENSOR INIT " + port + ", type: " + type);
const sensor = ev3board().getSensor(port, type);
runtime.queueDisplayUpdate();
if (!ev3board().hasSensor(port)) {
const sensor = ev3board().getSensor(port, type);
runtime.queueDisplayUpdate();
} else {
U.userError(`${lf("Multiple sensors are connected to Port")} ${port + 1}`);
}
}
}

View File

@ -88,7 +88,7 @@ namespace pxsim {
}
updateState(elapsed: number) {
console.log(`motor: ${elapsed}ms - ${this.speed}% - ${this.angle}> - ${this.tacho}|`)
//console.log(`motor: ${elapsed}ms - ${this.speed}% - ${this.angle}> - ${this.tacho}|`)
const interval = Math.min(20, elapsed);
let t = 0;
while (t < elapsed) {

View File

@ -10,8 +10,6 @@ namespace pxsim.music {
}
namespace pxsim.SoundMethods {
let numSoundsPlaying = 0;
const soundsLimit = 1;
let audio: HTMLAudioElement;
export function buffer(buf: RefBuffer) {
@ -27,18 +25,15 @@ namespace pxsim.SoundMethods {
}
export function play(buf: RefBuffer, volume: number) {
if (!buf || numSoundsPlaying >= soundsLimit) {
if (!buf) {
return Promise.resolve();
}
return new Promise<void>(resolve => {
let url = "data:audio/wav;base64," + btoa(uint8ArrayToString(buf.data))
audio = new Audio(url)
audio.onended = () => {
resolve();
numSoundsPlaying--;
}
numSoundsPlaying++;
audio.play()
audio.onended = () => resolve();
audio.onpause = () => resolve();
audio.play();
})
}
@ -46,8 +41,8 @@ namespace pxsim.SoundMethods {
return new Promise<void>(resolve => {
if (audio) {
audio.pause();
numSoundsPlaying--;
}
resolve();
})
}

View File

@ -1,26 +1,47 @@
/// <reference path="./moduleView.ts" />
/// <reference path="./motorView.ts" />
namespace pxsim.visuals {
export class LargeMotorView extends ModuleView implements LayoutElement {
private static ROTATING_ECLIPSE_ID = "hole";
export class LargeMotorView extends MotorView implements LayoutElement {
constructor(port: number) {
super(LARGE_MOTOR_SVG, "large-motor", NodeType.LargeMotor, port);
super(LARGE_MOTOR_SVG, "large-motor", NodeType.LargeMotor, port, "hole");
}
private syncedMotor: MotorNode;
private syncedLabelG: SVGGElement;
updateState() {
super.updateState();
const motorState = ev3board().getMotors()[this.port];
if (!motorState) return;
const speed = motorState.getSpeed();
if (!speed) return;
this.setMotorAngle(motorState.getAngle());
const syncedMotor = motorState.getSynchedMotor();
if ((syncedMotor || this.syncedMotor) && syncedMotor != this.syncedMotor) {
this.syncedMotor = syncedMotor;
this.showSyncedLabel(motorState, syncedMotor);
}
}
private setMotorAngle(angle: number) {
const holeEl = this.content.getElementById(this.normalizeId(LargeMotorView.ROTATING_ECLIPSE_ID))
private showSyncedLabel(motorNode: MotorNode, syncedMotor: MotorNode) {
const a = String.fromCharCode('A'.charCodeAt(0) + motorNode.port);
const b = String.fromCharCode('A'.charCodeAt(0) + syncedMotor.port);
this.syncedLabelG = pxsim.svg.child(this.element, 'g', {'transform': 'scale(0.5)'}) as SVGGElement;
pxsim.svg.child(this.syncedLabelG, 'rect', {'rx': 15, 'ry': 15, 'x': 0, 'y': 0, 'width': 84, 'height': 34, 'fill': '#A8A9A8'});
pxsim.svg.child(this.syncedLabelG, 'circle', {'cx': 17, 'cy': 17, 'r': 15, 'fill': 'white'});
const leftLabel = pxsim.svg.child(this.syncedLabelG, 'text', {'transform': 'translate(11, 22)', 'style': 'isolation: isolate;font-size: 16px;fill: #A8A9A8;font-family: ArialMT, Arial'});
leftLabel.textContent = a;
pxsim.svg.child(this.syncedLabelG, 'rect', {'rx': 0, 'ry': 0, 'x': 37, 'y': 12, 'width': 10, 'height': 3, 'fill': '#ffffff'});
pxsim.svg.child(this.syncedLabelG, 'rect', {'rx': 0, 'ry': 0, 'x': 37, 'y': 18, 'width': 10, 'height': 3, 'fill': '#ffffff'});
pxsim.svg.child(this.syncedLabelG, 'circle', {'cx': 67, 'cy': 17, 'r': 15, 'fill': 'white'});
const rightLabel = pxsim.svg.child(this.syncedLabelG, 'text', {'transform': 'translate(61, 22)', 'style': 'isolation: isolate;font-size: 16px;fill: #A8A9A8;font-family: ArialMT, Arial'});
rightLabel.textContent = b;
}
protected renderMotorAngle(holeEl: Element, angle: number) {
const width = 125.92;
const height = 37.9;
const transform = `rotate(${angle} ${width / 2} ${height / 2})`;

View File

@ -2,35 +2,17 @@
namespace pxsim.visuals {
export const MOTOR_ROTATION_FPS = 32;
export class MediumMotorView extends ModuleView implements LayoutElement {
private static ROTATING_ECLIPSE_ID = "medmotor_Hole";
private hasPreviousAngle: boolean;
private previousAngle: number;
export class MediumMotorView extends MotorView implements LayoutElement {
constructor(port: number) {
super(MEDIUM_MOTOR_SVG, "medium-motor", NodeType.MediumMotor, port);
super(MEDIUM_MOTOR_SVG, "medium-motor", NodeType.MediumMotor, port, "medmotor_Hole");
}
public getPaddingRatio() {
return 1 / 5;
}
updateState() {
super.updateState();
const motorState = ev3board().getMotors()[this.port];
if (!motorState) return;
const speed = motorState.getSpeed();
if (!speed) return;
this.setMotorAngle(motorState.getAngle());
}
private setMotorAngle(angle: number) {
const holeEl = this.content.getElementById(this.normalizeId(MediumMotorView.ROTATING_ECLIPSE_ID))
protected renderMotorAngle(holeEl: Element, angle: number) {
const width = 44.45;
const height = 44.45;
const transform = `translate(2 1.84) rotate(${angle} ${width / 2} ${height / 2})`;

View File

@ -0,0 +1,33 @@
/// <reference path="./moduleView.ts" />
namespace pxsim.visuals {
export abstract class MotorView extends ModuleView implements LayoutElement {
constructor(xml: string, prefix: string, id: NodeType, port: NodeType,
protected rotating_hole_id: string) {
super(xml, prefix, id, port);
}
updateState() {
super.updateState();
const motorState = ev3board().getMotors()[this.port];
if (!motorState) return;
const speed = motorState.getSpeed();
if (!speed) return;
this.setMotorAngle(motorState.getAngle());
}
private setMotorAngle(angle: number) {
const holeEl = this.content.getElementById(this.normalizeId(this.rotating_hole_id))
this.renderMotorAngle(holeEl, angle);
}
protected abstract renderMotorAngle(holeEl: Element, angle: number): void;
getWiringRatio() {
return 0.37;
}
}
}

View File

@ -4,12 +4,7 @@
viewBox="0 0 23 23" style="enable-background:new 0 0 23 23;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FFFFFF;}
.st1{fill:none;stroke:#717171;}
</style>
<title>color</title>
<g id="menu_icn_expansions" transform="translate(-9596 11554)">
<circle id="Ellipse_34" class="st0" cx="9607.5" cy="-11542" r="9"/>
<polygon class="st1" points="9613.5,-11543 9608.5,-11543 9608.5,-11548 9606.5,-11548 9606.5,-11543 9601.5,-11543 9601.5,-11541
9606.5,-11541 9606.5,-11536 9608.5,-11536 9608.5,-11541 9613.5,-11541 "/>
</g>
<path class="st0" d="M11.5,2.5c-5,0-9,4-9,9s4,9,9,9s9-4,9-9S16.5,2.5,11.5,2.5z M17.5,12.5h-5v5h-2v-5h-5v-2h5v-5h2v5h5V12.5z"/>
</svg>

Before

Width:  |  Height:  |  Size: 802 B

After

Width:  |  Height:  |  Size: 552 B

View File

@ -19,12 +19,13 @@ webfontsGenerator({
"./categories/sensors.svg",
"./categories/text.svg",
"./categories/variables.svg",
"./categories/advancedcollapsed.svg",
"./categories/advancedexpanded.svg",
"./icons/cancel.svg",
"./icons/check.svg",
"./icons/download.svg",
"./icons/save.svg",
"./categories/advancedcollapsed.svg",
"./categories/advancedexpanded.svg"
"./icons/blocks.svg"
],
dest: '../docs/static/fonts/icons/',
round: 10

14
svgicons/icons/blocks.svg Normal file
View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 22.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="svg41" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 23 23" style="enable-background:new 0 0 23 23;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FFFFFF;}
</style>
<title>color</title>
<g id="menu_icn_blocks" transform="translate(-9594.255 11170)">
<rect id="Rectangle_271" x="9600.5" y="-11160.2" class="st0" width="16.2" height="3.5"/>
<polygon class="st0" points="9616.8,-11162.6 9616.8,-11166 9594.8,-11166 9594.8,-11164.9 9594.8,-11162.6 9594.8,-11154.4
9594.8,-11151 9616.8,-11151 9616.8,-11154.4 9598.2,-11154.4 9598.2,-11162.6 "/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 790 B

View File

@ -6,10 +6,6 @@
.st0{fill:#FFFFFF;}
</style>
<title>color</title>
<g id="icn_cancel_x" transform="translate(14923.365 9661)">
<rect id="Rectangle_277" x="-14920.9" y="-9651.1" transform="matrix(0.7071 -0.7071 0.7071 0.7071 2455.6738 -13370.6289)" class="st0" width="18" height="3"/>
<rect id="Rectangle_278" x="-14913.4" y="-9658.6" transform="matrix(0.7071 -0.7071 0.7071 0.7071 2455.6738 -13370.6299)" class="st0" width="3" height="18"/>
</g>
<polygon class="st0" points="19,6 16.9,3.9 11.6,9.2 6.3,3.9 4.2,6 9.5,11.3 4.2,16.6 6.3,18.8 11.6,13.5 16.9,18.8 19,16.6
13.7,11.3 "/>
</svg>

Before

Width:  |  Height:  |  Size: 812 B

After

Width:  |  Height:  |  Size: 562 B

View File

@ -6,10 +6,5 @@
.st0{fill:#FFFFFF;}
</style>
<title>color</title>
<g id="icn_cancel_ok" transform="translate(15066.061 9654)">
<rect id="Rectangle_275" x="-15061" y="-9644.1" transform="matrix(0.7071 -0.7071 0.7071 0.7071 2409.7021 -13467.6172)" class="st0" width="18" height="3"/>
<rect id="Rectangle_276" x="-15061.3" y="-9643.9" transform="matrix(0.7071 -0.7071 0.7071 0.7071 2405.1626 -13472.2129)" class="st0" width="3" height="9"/>
</g>
<polygon class="st0" points="19.4,4.1 8.8,14.7 4.3,10.2 2.2,12.4 6.7,16.8 6.7,16.8 8.8,18.9 21.5,6.2 "/>
</svg>

Before

Width:  |  Height:  |  Size: 810 B

After

Width:  |  Height:  |  Size: 530 B

View File

@ -4,22 +4,15 @@
viewBox="0 0 23 23" style="enable-background:new 0 0 23 23;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FFFFFF;}
.st1{fill:none;}
.st2{fill:none;stroke:#FFFFFF;}
</style>
<title>color</title>
<g id="menu_icn_savedisc" transform="translate(-8886 12201.056)">
<path id="Path_116" class="st0" d="M8887.3-12189.6v-9.5c0-0.7,0.1-0.8,0.8-0.8h4.5c0.7,0,0.8,0.1,0.8,0.8v4.6
c0,0.6,0.1,0.7,0.7,0.7h6.8c0.6,0,0.7-0.1,0.7-0.7v-4.7c0-0.6,0.1-0.7,0.7-0.7c1.4,0,1.4,0,2.3,0.9c1,1,1.9,1.9,2.9,2.9
c0.2,0.2,0.3,0.5,0.3,0.8v15.2c0,0.6-0.1,0.8-0.7,0.8H8888c-0.6,0-0.7-0.1-0.7-0.8C8887.3-12183.3,8887.3-12186.5,8887.3-12189.6z
M8897.6-12181.5h7.4c0.7,0,0.8-0.1,0.8-0.8v-8.7c0-0.6-0.1-0.8-0.8-0.8h-14.9c-0.6,0-0.8,0.1-0.8,0.8v8.7c0,0.7,0.1,0.8,0.8,0.8
H8897.6z"/>
<path id="Path_117" class="st0" d="M8900.2-12197.9c0,0.5,0,1,0,1.5c0,0.5-0.2,0.6-0.6,0.6c-0.3,0-0.7,0-0.9,0s-0.6-0.1-0.6-0.5
v-0.1v-2.9c0-0.2,0.1-0.5,0.5-0.6c0,0,0,0,0.1,0c0.3,0,0.7,0,1,0c0.3,0,0.6,0.2,0.6,0.5c0,0,0,0,0,0.1
C8900.2-12198.9,8900.2-12198.3,8900.2-12197.9z"/>
<g id="Rectangle_166" transform="translate(1.445 6.558)">
<rect x="8887.5" y="-12199" class="st1" width="17.4" height="11.6"/>
<rect x="8888.1" y="-12198.5" class="st2" width="16.3" height="10.5"/>
</g>
<path id="Path_117" class="st0" d="M8900.4-12197.9c0,0.5,0,1,0,1.5c0,0.5-0.2,0.6-0.6,0.6c-0.3,0-0.7,0-0.9,0
c-0.2,0-0.6-0.1-0.6-0.5v-0.1v-2.9c0-0.2,0.1-0.5,0.5-0.6c0,0,0,0,0.1,0c0.3,0,0.7,0,1,0c0.3,0,0.6,0.2,0.6,0.5c0,0,0,0,0,0.1
C8900.4-12198.9,8900.4-12198.3,8900.4-12197.9z"/>
<path class="st0" d="M8907.6-12196c-1-1-1.9-1.9-2.9-2.9c-0.9-0.9-0.9-0.9-2.3-0.9c-0.6,0-0.7,0.1-0.7,0.7v4.7
c0,0.6-0.1,0.7-0.7,0.7h-6.8c-0.6,0-0.7-0.1-0.7-0.7v-4.6c0-0.7-0.1-0.8-0.8-0.8h-4.5c-0.7,0-0.8,0.1-0.8,0.8v9.5
c0,3.2,0,6.3,0,9.6c0,0.7,0.1,0.8,0.7,0.8h19.1c0.6,0,0.7-0.2,0.7-0.8v-15.2C8908-12195.6,8907.8-12195.8,8907.6-12196z
M8905.4-12181.9h-15.3v-9.4h15.3V-12181.9z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -9,7 +9,7 @@
/* Fonts for toolbox icons */
@font-face {
font-family: 'legoIcons';
src: data-uri("../docs/static/fonts/icons/iconfont.woff2");
src: data-uri("../docs/static/fonts/icons/iconfont.woff");
}
.blocklyFlyoutLabel:not(.blocklyFlyoutHeading) .blocklyFlyoutLabelIcon {
@ -118,34 +118,34 @@ span.blocklyTreeIcon.blocklyTreeIconaddpackage::before {
}
span.blocklyTreeIcon.blocklyTreeIconadvancedcollapsed::before {
content: "\f116";
}
span.blocklyTreeIcon.blocklyTreeIconadvancedexpanded::before {
content: "\f117";
}
.save-editortools-btn .icon.save,
.download-button .icon.download.icon-and-text,
.button.approve.positive .i.icon.checkmark,
.button.approve.cancel .i.icon.cancel
{
font-family: 'legoIcons';
}
.save-editortools-btn .icon.save:before {
content: "\f115";
}
.download-button .icon.download.icon-and-text:before {
content: "\f114";
}
.button.approve.positive .i.icon.checkmark:before {
content: "\f113";
}
.button.approve.cancel .i.icon.cancel:before {
content: "\f112";
}
span.blocklyTreeIcon.blocklyTreeIconadvancedexpanded::before {
content: "\f113";
}
.save-editortools-btn .icon.save:before {
font-family: 'legoIcons';
content: "\f117";
}
.download-button .icon.download.icon-and-text:before {
font-family: 'legoIcons';
content: "\f116";
}
.button.approve.positive i.icon.checkmark:before {
font-family: 'legoIcons';
content: "\f115";
}
.button.approve.cancel i.icon.cancel:before {
font-family: 'legoIcons';
content: "\f114";
}
.blocks-menuitem i.icon.blocks:before {
font-family: 'legoIcons' !important;
content: "\f118" !important;
}

View File

@ -1,3 +1,79 @@
/*******************************
Site Overrides
*******************************/
.ui.loader:before {
border: none;
border-radius: 0px;
box-shadow: none;
background: transparent @loaderBackImage no-repeat center center;
background-size: 100%;
}
.ui.loader:after {
border: none;
box-shadow: none;
border-radius: 0px;
margin: 40px auto;
background: transparent @loaderImage no-repeat center center;
background-size: 100%;
-webkit-animation: @loaderAnimation @loaderSpeed infinite linear;
animation: @loaderAnimation @loaderSpeed infinite linear;
}
.ui.loader.avatar:after {
border: none;
box-shadow: none;
border-radius: 0px;
margin: 40px auto;
background: transparent @avatarImage no-repeat center center;
background-size: 100%;
-webkit-animation: @loaderAnimation @loaderSpeed infinite linear;
animation: @loaderAnimation @loaderSpeed infinite linear;
}
@-webkit-keyframes loader-pxt-ev3 {
0% {
-webkit-transform: translateX(@loaderStartPoint);
transform: translateX(@loaderStartPoint);
}
20% {
-webkit-transform: translateX(-@loaderMiddlePoint);
transform: translateX(-@loaderMiddlePoint);
}
50% {
-webkit-transform: translateX(-@loaderEndPoint);
transform: translateX(-@loaderEndPoint);
}
75% {
-webkit-transform: translateX(-@loaderMiddlePoint);
transform: translateX(-@loaderMiddlePoint);
}
100% {
-webkit-transform: translateX(@loaderStartPoint);
transform: translateX(@loaderStartPoint);
}
}
@keyframes loader-pxt-ev3 {
0% {
-webkit-transform: translateX(@loaderStartPoint);
transform: translateX(@loaderStartPoint);
}
20% {
-webkit-transform: translateX(-@loaderMiddlePoint);
transform: translateX(-@loaderMiddlePoint);
}
50% {
-webkit-transform: translateX(-@loaderEndPoint);
transform: translateX(-@loaderEndPoint);
}
75% {
-webkit-transform: translateX(-@loaderMiddlePoint);
transform: translateX(-@loaderMiddlePoint);
}
100% {
-webkit-transform: translateX(@loaderStartPoint);
transform: translateX(@loaderStartPoint);
}
}

View File

@ -1,3 +1,19 @@
/*******************************
User Variable Overrides
*******************************/
@loaderBackImageUrl: "../docs/static/loader_back.svg";
@loaderBackImage: data-uri(@loaderBackImageUrl);
@loaderImageUrl: "../docs/static/loader_front.svg";
@loaderImage: data-uri(@loaderImageUrl);
@loaderSpeed: 2s;
@loaderStartPoint: 0px;
@loaderMiddlePoint: 14px;
@loaderEndPoint: 28px;
@loaderAnimation: loader-pxt-ev3;
@large : 200px;

View File

@ -76,6 +76,12 @@
color:white !important;
}
/* lowercase menu items */
.menubar .ui.menu.fixed > .left.menu > .ui.item > .ui.text,
.menubar .ui.menu.fixed > .ui.item > span {
text-transform: lowercase;
}
/* Mobile */
@media only screen and (max-width: @largestMobileScreen) {
#filelist {