Compare commits

..

69 Commits

Author SHA1 Message Date
Peli de Halleux
8fd0bf92fa move electron to v1.1.22 2019-10-07 14:23:34 -07:00
Peli de Halleux
a33bff0520 offline template 2019-10-07 14:22:05 -07:00
Peli de Halleux
7549f865d6 move master to 1.2.22 2019-10-02 13:59:58 -07:00
Peli de Halleux
a5de9d88bb adding latest video 2019-10-02 13:53:22 -07:00
Peli de Halleux
0437df10de 1.2.22 2019-10-02 13:27:53 -07:00
Peli de Halleux
2079173dfa lock execution thread before exiting (#935) 2019-10-02 12:40:45 -07:00
Peli de Halleux
018a1e7528 added proportional line follower 2019-10-02 12:26:51 -07:00
Peli de Halleux
757f95d984 move master to v1.2.21 2019-10-02 10:59:50 -07:00
Peli de Halleux
8bc3fdc8ba 1.2.21 2019-10-01 15:56:22 -07:00
Peli de Halleux
e8a1e73cf5 1.2.20 2019-10-01 14:54:42 -07:00
Peli de Halleux
e9b2b239ad More gyro lessons (#933)
* updated pauseuntil

* updated youtube link

* updated docs
2019-10-01 14:54:17 -07:00
Peli de Halleux
5ad2288a9f updated docs 2019-10-01 13:38:33 -07:00
Peli de Halleux
92d13ef343 updated bluetooth info 2019-10-01 13:36:53 -07:00
Peli de Halleux
471ca5d915 1.2.19 2019-10-01 13:28:01 -07:00
Peli de Halleux
f745079728 Don't reset threashold when resetting color mode (#932)
* don't reset threshold when changing modes

* updated docs
2019-10-01 13:27:37 -07:00
Peli de Halleux
d179f18ef3 added gyro movies 2019-10-01 11:05:44 -07:00
Peli de Halleux
805fc6c787 1.2.18 2019-10-01 10:12:21 -07:00
Peli de Halleux
374bbb8304 Drift-compensated angle in gyro (#931)
* compute angle based on undrifted rate

* add is calibrating function

* fix integrator

* added example

* docs

* poll faster
2019-10-01 10:11:58 -07:00
Peli de Halleux
25452efc92 1.2.17 2019-09-30 22:44:09 -07:00
Peli de Halleux
80b9c715b2 Gyro tutorials (#930)
* gyro tutorials

* tutorials

* fix gyro simulator

* images

* updated image

* fix svg errors
2019-09-30 22:43:50 -07:00
Peli
cb816c91ad updated drift 2019-09-30 14:33:57 -07:00
Peli
3012068986 1.2.16 2019-09-30 13:44:27 -07:00
Peli de Halleux
4c9c7d6a69 add delay to fix mac deployment (#929) 2019-09-30 13:43:12 -07:00
Peli
ad3652c290 bump master to 1.2.15 2019-09-30 13:03:48 -07:00
Peli de Halleux
d8971829e3 1.2.15 2019-09-30 11:13:45 -07:00
Peli de Halleux
8ca4558fc2 web serial cleanup (#928)
* updated ticks

* fix enable and compile

* add debug mode

* don't try to change UI while downloading

* cleanup

* don't double open

* use cu

* add delay before reconnecting

* support for 200 ragne in field turn ratio

* updated docs

* fix docs
2019-09-30 11:13:28 -07:00
Peli de Halleux
d85b5c5129 updated info for macs (#925) 2019-09-30 10:30:21 -07:00
Peli de Halleux
95b1c6a50f 1.2.14 2019-09-29 23:09:07 -07:00
Peli de Halleux
4dc2872286 Better bt download flow (#927)
* round the drif

* restart compile automatically

* add settle
2019-09-29 23:08:46 -07:00
Peli de Halleux
6c9ff804c8 1.2.13 2019-09-29 09:49:35 -07:00
Peli de Halleux
7581b5af9e Drift support (#926)
* upgrading drift support

* updated showports

* typos
2019-09-29 09:49:13 -07:00
Peli de Halleux
07504027f9 1.2.12 2019-09-27 16:46:20 -07:00
Peli de Halleux
a0e133864a background show ports (#924)
* background show ports

* render once at least

* better rendering

* bumped up scale
2019-09-27 16:45:57 -07:00
Peli de Halleux
0285711954 added automation package 2019-09-27 16:08:54 -07:00
Peli de Halleux
91be998d7e 1.2.11 2019-09-27 15:49:43 -07:00
Peli de Halleux
e862869327 Snap backwards (#923)
* snap to multiple of 5

* normalize negative values
2019-09-27 15:49:21 -07:00
Peli de Halleux
8047cb2612 beta notation 2019-09-27 11:45:25 -07:00
Peli de Halleux
8e2ffefd2c adding walkthrough video 2019-09-27 11:43:04 -07:00
Peli de Halleux
092e7b0522 1.2.10 2019-09-27 11:15:29 -07:00
Peli de Halleux
42454ecd57 graceful handling of bluetooth connectivity issues (#922)
* more cleanup

* cleanup life cycle

* more error checking
2019-09-27 11:15:10 -07:00
Peli de Halleux
2563fd6031 move image 2019-09-27 09:36:25 -07:00
Peli de Halleux
e0c1f2dca0 added other issue 2019-09-27 09:25:46 -07:00
Peli de Halleux
c80574ed3f 1.2.9 2019-09-27 09:16:45 -07:00
Peli de Halleux
b28b5cb6b7 better flow + handle unclosed connection (#921)
* better flow + handle unclosed connection

* more checks
2019-09-27 09:16:27 -07:00
Peli de Halleux
d1f11059db updated text 2019-09-27 08:46:50 -07:00
Peli de Halleux
6de5c6afdf added beta note 2019-09-27 06:56:03 -07:00
Peli de Halleux
b72c7c0c4f 1.2.8 2019-09-27 06:54:03 -07:00
Peli de Halleux
352c1ca5ec Experiment BT support using Chrome web serial (#920)
* plumbing

* plumbing

* logging

* more notes

* fixing typing

* more plumbing

* more plumbing

* different baud rate

* talking to the brick

* first over the air drop

* fix buffer

* tweak paraetmers

* formatting fixing double upload

* reduce console.log

* cleanup

* add BLE button to download dialog

* changed label

* recover from broken COM port

* fix function call

* reduce log level

* adding ticks

* some help

* updated support matrix

* more docs

* updated browser help

* more docs

* add link

* add device

* added image
2019-09-27 06:53:26 -07:00
Peli de Halleux
6d940a9ec7 other color snesor tutorial (#919) 2019-09-25 22:35:10 -07:00
Peli de Halleux
c070173346 adding moods 2019-09-25 21:12:45 -07:00
Peli de Halleux
6fcf68f174 fix tutorial 2019-09-25 21:08:44 -07:00
Peli de Halleux
02f2a85d28 added images 2019-09-25 15:16:36 -07:00
Peli de Halleux
f63196908e updated robot 1 2019-09-25 13:20:20 -07:00
Peli de Halleux
ad48ee12ca 1.2.7 2019-09-24 21:45:20 -07:00
Peli de Halleux
83aeb24a98 broadcast project (#918)
* broadcast project

* revert chaanges
2019-09-24 21:44:59 -07:00
Peli de Halleux
fc5ecd9f10 added pause-on-start uttorial 2019-09-24 20:47:29 -07:00
Peli de Halleux
0b3b840ac1 1.2.6 2019-09-17 15:14:32 -07:00
Peli de Halleux
60c09809e7 Stall (#897)
* stall detection

* arrange blocks

* updated blocks

* tested on hw
2019-09-17 15:14:14 -07:00
Peli de Halleux
148067a143 1.2.5 2019-09-17 14:30:20 -07:00
Peli de Halleux
6f34887c94 Safepolling (#915)
* headstart on safe polling

* poke in sensors

* more poking

* typo
2019-09-17 14:30:02 -07:00
Peli de Halleux
64a9930c2e 1.2.4 2019-09-17 12:36:35 -07:00
Peli de Halleux
5815e16647 update pxt reference (#914) 2019-09-17 12:36:11 -07:00
Peli de Halleux
c4f5e425c2 1.2.3 2019-09-17 11:26:52 -07:00
Peli de Halleux
361ae7a2d2 adding a few pause to allow motors to settle 2019-09-17 11:23:40 -07:00
Peli de Halleux
3769402ade 1.2.2 2019-09-17 10:52:14 -07:00
Peli de Halleux
5c3c83eb52 add a run to avoid tight loop hogging (#913) 2019-09-17 10:52:00 -07:00
Peli de Halleux
0f07a89981 updated docs on offline-app 2019-09-11 19:37:26 -07:00
Abhijith Chatra
11d887a213 Releaseing offline app version 1.1.20 (#912) 2019-09-11 18:24:47 -07:00
Peli de Halleux
8150b2dbb0 fix turtle link 2019-09-11 13:23:09 -07:00
67 changed files with 1951 additions and 650 deletions

View File

@@ -6,13 +6,13 @@
<a class="item" href="https://makecode.com/privacy" target="_blank" rel="noopener">Privacy &amp; Cookies</a> <a class="item" href="https://makecode.com/privacy" target="_blank" rel="noopener">Privacy &amp; Cookies</a>
<a class="item" href="https://makecode.com/termsofuse" target="_blank" rel="noopener"> Terms Of Use</a> <a class="item" href="https://makecode.com/termsofuse" target="_blank" rel="noopener"> Terms Of Use</a>
<a class="item" href="https://makecode.com/trademarks" target="_blank" rel="noopener">Trademarks</a> <a class="item" href="https://makecode.com/trademarks" target="_blank" rel="noopener">Trademarks</a>
<div class="item">© 2018 Microsoft</div> <div class="item">©2019 Microsoft</div>
</div> </div>
<div class="ui container horizontal small divided link list"> <div class="ui container horizontal small divided link list">
<a class="ui centered item" href="https://makecode.com/" title="Microsoft MakeCode" target="_blank" rel="noopener">Powered by Microsoft MakeCode</a> <a class="ui centered item" href="https://makecode.com/" title="Microsoft MakeCode" target="_blank" rel="noopener">Powered by Microsoft MakeCode</a>
</div> </div>
<div class="ui centered container small list"> <div class="ui centered container small list">
<p class="item">LEGO, the LEGO logo, MINDSTORMS and the MINDSTORMS EV3 logo are trademarks and/ or copyrights of the LEGO Group. ©2018 The LEGO Group. All rights reserved.</p> <p class="item">@copyrightText@</p>
</div> </div>
</div> </div>
</footer> </footer>

View File

@@ -4,6 +4,7 @@
* [Troubleshoot](/troubleshoot) * [Troubleshoot](/troubleshoot)
* [EV3 Manager](https://ev3manager.education.lego.com/) * [EV3 Manager](https://ev3manager.education.lego.com/)
* [Bluetooth](/bluetooth)
* [Forum](https://forum.makecode.com) * [Forum](https://forum.makecode.com)
* [LEGO Support](https://www.lego.com/service/) * [LEGO Support](https://www.lego.com/service/)
* [FIRST LEGO League](/fll) * [FIRST LEGO League](/fll)

View File

@@ -28,7 +28,7 @@ program to a **.uf2** file, which you then copy to the **@drivename@** drive. Th
### ~ hint ### ~ hint
Not seeing the **@drivename@** drive? Make sure to upgrade your firmware at https://ev3manager.education.lego.com/. Try these [troubleshooting](/troubleshoot) tips if you still have trouble getting the drive to appear. **Experimental support** for Bluetooth download is now available. Please read the [Bluetooth](/bluetooth) page for more information.
### ~ ### ~

62
docs/bluetooth.md Normal file
View File

@@ -0,0 +1,62 @@
# Bluetooth
This page describes the procedure to download MakeCode program to the EV3 brick
over Bluetooth.
## ~ hint
### WARNING: EXPERIMENTAL FEATURES AHEAD!
Support for Bluetooth download relies on [Web Serial](https://wicg.github.io/serial/),
an experimental browser feature. Web Serial is a work [in progress](https://www.chromestatus.com/feature/6577673212002304);
it may change or be removed in future versions without notice.
By enabling these experimental browser features, you could lose browser data or compromise your device security
or privacy.
## ~
https://youtu.be/VIq8-6Egtqs
## Supported browsers
* Chrome desktop, version 77 and higher, Windows 10 or Mac OS.
* [Edge Insider desktop](https://www.microsoftedgeinsider.com), version 77 and higher, Windows 10 or Mac OS.
To make sure your browser is up to date, go to the '...' menu, click "Help" then "About".
Next you need to enable the experimental features (this may change in the future)
* go to **chrome://flags/#enable-experimental-web-platform-features** and **enable**
**Experimental Web Platform features**
![A screenshot of the flags page in chrome](/static/bluetooth/experimental.png)
## Machine Setup
* pair your EV3 brick with your computer over Bluetooth. This is the usual pairing procedure.
## Download over Bluetooth
* go to https://makecode.mindstorms.com/
* click on **Download** to start a file download as usual
* on the download dialog, you should see a **Bluetooth** button. Click on the
**Bluetooth** button to enable the mode.
* **make sure the EV3 brick is not running a program**
* click on **Download** again to download over bluetooth.
## Choosing the correct serial port
Unfortunately, the browser dialog does not make it easy to select which serial port is the brick.
* On Windows, choose ``Standard Serial over Bluetooth``. There might be multiple of those but only one works. Try your luck! Once you know the COM port number, remember it for the next time around.
* On Mac OS, choose ``cu.BRICKNAME-SerialPort``
## Known issues
* We do not detect properly that the program is running on the brick. Make sure to stop the program before starting the download procedure.
* The list of programs on the brick screen is not updated when uploading via bluetooth.
## Feedback
Please send us your feedback through https://forum.makecode.com.

View File

@@ -19,6 +19,7 @@ If you found a bug, please try if it hasn't been fixed yet! Go to https://makeco
* You will need to install the latest EV3 firmware on your brick. Instructions on how to do that are located here: https://makecode.mindstorms.com/troubleshoot. * You will need to install the latest EV3 firmware on your brick. Instructions on how to do that are located here: https://makecode.mindstorms.com/troubleshoot.
* You will need a computer with a USB port to connect to the EV3 in order to download your programs. * You will need a computer with a USB port to connect to the EV3 in order to download your programs.
* You will need internet access and a browser on your computer to get to https://makecode.mindstorms.com. * You will need internet access and a browser on your computer to get to https://makecode.mindstorms.com.
* You can [install the app](/offline-app) to use the editor offline.
### I know LabView, how is MakeCode different? ### I know LabView, how is MakeCode different?
@@ -28,12 +29,16 @@ We have compiled a guide for EV3 LabView users at https://makecode.mindstorms.co
Go to https://makecode.mindstorms.com. The home screen is filled with videos, tutorials and examples that might be relevant for your missions. Go to https://makecode.mindstorms.com. The home screen is filled with videos, tutorials and examples that might be relevant for your missions.
On the home page, scroll down to the **FLL / City Shaper / Crane Mission** section for specific lessons related to Mission 2. On the home page, scroll down to the **FLL / City Shaper** section for specific lessons related to Mission 2.
### Can I load both LEGO MINDSTORMS EV3 Software and MakeCode programs onto my EV3? ### Can I load both LEGO MINDSTORMS EV3 Software and MakeCode programs onto my EV3?
Yes. Yes.
### Does it work without internet?
No, the editor is cached in your browser cache. However, you can also download the [offline app](/offline-app) in case you need to install it on a computer.
### How do I figure out what a block does? ### How do I figure out what a block does?
You can right-click on any block and select “Help” in the context menu to open the documentation page describing what that block does. You can right-click on any block and select “Help” in the context menu to open the documentation page describing what that block does.
@@ -87,6 +92,12 @@ You can share your projects by clicking on the **share** button in the top left
Sharing programs is also shown in the [Tips and Tricks](https://legoeducation.videomarketingplatform.co/v.ihtml/player.html?token=5c594c2373367f7870196f519f3bfc7a&source=embed&photo%5fid=35719472) video. Sharing programs is also shown in the [Tips and Tricks](https://legoeducation.videomarketingplatform.co/v.ihtml/player.html?token=5c594c2373367f7870196f519f3bfc7a&source=embed&photo%5fid=35719472) video.
### Can I use Bluetooth to transfer my program?
The official answer is currently no. That being said, we have **Experimental support** for Bluetooth download. Please read the [Bluetooth](/bluetooth) page for more information.
https://youtu.be/VIq8-6Egtqs
### Why can't I delete my program (*.uf2) files from the Brick? ### Why can't I delete my program (*.uf2) files from the Brick?
There's a bug in the firmware which prevents you from deleting the programs (``*.uf2`` files) from your EV3 Brick. There isn't a firmware update to fix this yet. There's a bug in the firmware which prevents you from deleting the programs (``*.uf2`` files) from your EV3 Brick. There isn't a firmware update to fix this yet.

View File

@@ -1,3 +1,3 @@
{ {
"appref": "v1.1.20" "appref": "v1.2.22"
} }

View File

@@ -61,6 +61,12 @@ motors.largeBC.steer(0, 50, 1, MoveUnit.Rotations);
motors.largeBC.stop(); motors.largeBC.stop();
``` ```
### ~ hint
The **turn ratio range is -200, 200** unlike LabView who used -100,100.
### ~
## Tank ## Tank
The **tank** blocks control the speed of two motors. These are commonly used for a differential drive robot. The blocks can also specify the duration, angle, or number of rotations. The **tank** blocks control the speed of two motors. These are commonly used for a differential drive robot. The blocks can also specify the duration, angle, or number of rotations.

View File

@@ -3,11 +3,20 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>LEGO® MINDSTORMS® Education EV3 Offline App</title> <title>@name@ Offline App</title>
<meta name="Description" content="A MakeCode for LEGO® MINDSTORMS® Education EV3 offline app" /> <meta name="Description" content="A MakeCode for @name@ offline app" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- @include indexhead.html --> <link rel="stylesheet" data-rtl="/blb/rtlsemantic.css" href="/doccdn/semantic.css" />
<link rel="stylesheet" href="/docfiles/style.css" />
<link rel="stylesheet" href="/docfiles/target.css" />
<script src="/doccdn/jquery.js" type="text/javascript"></script>
<script src="/doccdn/semantic.js" type="text/javascript"></script>
<script src="/docfiles/target.js" type="text/javascript"></script>
<style>
@targetstyle@
</style>
<style> <style>
p.item { p.item {
color: rgba(0, 0, 0, 0.4); color: rgba(0, 0, 0, 0.4);
@@ -55,6 +64,10 @@
background-color: rgb(250, 250, 250); background-color: rgb(250, 250, 250);
} }
#legal-segment {
background:white;
}
@media only screen and (max-width: 800px) { @media only screen and (max-width: 800px) {
.grid .column .image { .grid .column .image {
display: none; display: none;
@@ -368,8 +381,8 @@
</style> </style>
<script> <script>
$(document).ready(function () { var electronLatestVersion = "";
tickEvent = function (id, data) { function tickEvent(id, data) {
if (!pxt.aiTrackEvent) return; if (!pxt.aiTrackEvent) return;
if (!data) pxt.aiTrackEvent(id); if (!data) pxt.aiTrackEvent(id);
else { else {
@@ -381,21 +394,48 @@
pxt.aiTrackEvent(id, props, measures); pxt.aiTrackEvent(id, props, measures);
} }
} }
});
function agreeCheckboxChanged() { function agreeCheckboxChanged() {
var downloadSegment = document.getElementById("download-segment"); showDownloads();
downloadSegment.classList.toggle("hidden"); }
function showAgree() {
$("#agree-segment").removeClass("hidden");
$("#read-segment").removeClass("hidden");
$("#legal-segment").removeClass("hidden");
}
function showNoDownloads() {
$("#no-download-segment").removeClass("hidden");
$("#read-segment").addClass("hidden");
$("#legal-segment").addClass("hidden");
}
function showDownloads() {
$("#download-win64").attr("href", "https://makecode.com/api/release/@targetid@/" + electronLatestVersion + "/win64");
$("#download-mac64").attr("href", "https://makecode.com/api/release/@targetid@/" + electronLatestVersion + "/mac64");
$("#download-segment").removeClass("hidden");
} }
function downloadWin64() { function downloadWin64() {
// TODO: Keep this link up-to-date with the desired release version tickEvent("offlineapp.download", { "target": "@targetid@", "platform": "win64" });
window.open("https://makecode.com/api/release/ev3/v1.0.11/win64");
tickEvent("offlineapp.download", { "target": "ev3", "platform": "win64" });
} }
function downloadMac64() { function downloadMac64() {
// TODO: Keep this link up-to-date with the desired release version tickEvent("offlineapp.download", { "target": "@targetid@", "platform": "mac64" });
window.open("https://makecode.com/api/release/ev3/v1.0.11/mac64");
tickEvent("offlineapp.download", { "target": "ev3", "platform": "mac64" });
} }
$(function () {
$.getJSON("https://makecode.com/api/config/@targetid@/targetconfig")
.then(function (data) {
if (data && data.electronManifest && data.electronManifest.latest) {
electronLatestVersion = data.electronManifest.latest;
showAgree();
} else {
showNoDownloads();
}
})
.catch(function () {
showNoDownloads();
})
});
</script> </script>
</head> </head>
@@ -406,35 +446,39 @@
<div class="ui grid topbar"> <div class="ui grid topbar">
<div class="three wide column"> <div class="three wide column">
<img class="ui small image left" src="/static//lego_education_logo_white.png" /> <img class="ui small image left" src="@cardLogo@" />
</div> </div>
<div class="ten wide column"> <div class="ten wide column">
<h1 class="ui inverted welcomeheader">MakeCode Offline App</h1> <h1 class="ui inverted welcomeheader">@name@ Offline App</h1>
</div> </div>
<div class="three wide column"> <div class="three wide column">
<img class="ui small image right" src="/static//Microsoft-logo_rgb_c-white.png" /> <img class="ui small image right" src="/static/Microsoft-logo_rgb_c-white.png" />
</div> </div>
</div> </div>
<div class="ui compact segments terms-container"> <div id="segments" class="ui compact segments terms-container">
<div class="ui secondary center aligned segment"> <div id="read-segment" class="ui secondary center aligned segment hidden">
Please read and accept the following terms to download the app. Please read and accept the following terms to download the app.
</div> </div>
<div class="ui left aligned segment terms"> <div id="legal-segment" class="ui left aligned segment terms hidden">
<div id="loader" class="ui active loader"></div>
<div class="c17"> <div class="c17">
<p class="c11"> <p class="c11">
<span class="c4 c1">MICROSOFT PRE-RELEASE SOFTWARE LICENSE TERMS</span> <span class="c4 c1">MICROSOFT PRE-RELEASE SOFTWARE LICENSE TERMS</span>
</p> </p>
<p class="c11"> <p class="c11">
<span class="c4 c1">MICROSOFT MAKECODE FOR LEGO MINDSTORMS EDUCATION EV3</span> <span class="c4 c1">MICROSOFT MAKECODE SOFTWARE FOR @name@</span>
</p> </p>
<p class="c7"> <p class="c7">
<span class="c4 c1"></span> <span class="c4 c1"></span>
</p> </p>
<p class="c11"> <p class="c11">
<span class="c3 c1">These license terms are an agreement between Microsoft Corporation (or based on where you live, one <span class="c3 c1">These license terms are an agreement between Microsoft Corporation (or based
of its affiliates) and you. They apply to the software named above. The terms also apply to any on where you live, one
Microsoft services or updates for the software, except to the extent those have additional terms.</span> of its affiliates) and you. They apply to the software named above. The terms also apply to
any
Microsoft services or updates for the software, except to the extent those have additional
terms.</span>
</p> </p>
<p class="c7"> <p class="c7">
<span class="c3 c1"></span> <span class="c3 c1"></span>
@@ -446,26 +490,34 @@
<span class="c5 c1">1.</span> <span class="c5 c1">1.</span>
<span class="c1">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span> <span class="c1">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>
<span class="c5 c1">INSTALLATION AND USE RIGHTS. </span> <span class="c5 c1">INSTALLATION AND USE RIGHTS. </span>
<span class="c3 c1">You may install and use any number of copies of the software to evaluate it as you develop and test <span class="c3 c1">You may install and use any number of copies of the software to evaluate it
your software applications for use with Lego Mindstorms Education EV3 hardware.</span> as you develop and test
your software applications for use with @name@ hardware.</span>
</p> </p>
<p class="c2"> <p class="c2">
<span class="c5 c1">2.</span> <span class="c5 c1">2.</span>
<span class="c1">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span> <span class="c1">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>
<span class="c5 c1">PRE-RELEASE SOFTWARE. </span> <span class="c5 c1">PRE-RELEASE SOFTWARE. </span>
<span class="c3 c1">The software is a pre-release version. It may not work the way a final version of the software will. <span class="c3 c1">The software is a pre-release version. It may not work the way a final
Microsoft may change it for the final, commercial version. We also may not release a commercial version of the software will.
version. Microsoft is not obligated to provide maintenance, technical support or updates to you Microsoft may change it for the final, commercial version. We also may not release a
commercial
version. Microsoft is not obligated to provide maintenance, technical support or updates to
you
for the software.</span> for the software.</span>
</p> </p>
<p class="c2"> <p class="c2">
<span class="c5 c1">3.</span> <span class="c5 c1">3.</span>
<span class="c1">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span> <span class="c1">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>
<span class="c5 c1">ASSOCIATED ONLINE SERVICES.</span> <span class="c5 c1">ASSOCIATED ONLINE SERVICES.</span>
<span class="c1">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Some features of the software provide access <span class="c1">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Some features of the software
to, or rely on, online services to provide you information about updates to the software or extensions, provide access
or to enable you to retrieve content, collaborate with others, or otherwise supplement your development to, or rely on, online services to provide you information about updates to the software or
experience. As used throughout these license terms, the term <q>software</q> includes these online extensions,
or to enable you to retrieve content, collaborate with others, or otherwise supplement your
development
experience. As used throughout these license terms, the term <q>software</q> includes these
online
services and features. By using these online services and features you consent to the to the services and features. By using these online services and features you consent to the to the
transmission of information as described in Section 5, DATA. transmission of information as described in Section 5, DATA.
</span> </span>
@@ -474,9 +526,11 @@
<span class="c5 c1">4.</span> <span class="c5 c1">4.</span>
<span class="c1">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span> <span class="c1">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>
<span class="c5 c1">LICENSES FOR OTHER COMPONENTS.</span> <span class="c5 c1">LICENSES FOR OTHER COMPONENTS.</span>
<span class="c3 c1">&nbsp;The software may include third party components with separate legal notices or governed by <span class="c3 c1">&nbsp;The software may include third party components with separate legal
notices or governed by
other agreements, as described in the ThirdPartyNotices file accompanying the software. Even other agreements, as described in the ThirdPartyNotices file accompanying the software. Even
if such components are governed by other agreements, the disclaimers and the limitations on and if such components are governed by other agreements, the disclaimers and the limitations on
and
exclusions of damages below also apply.</span> exclusions of damages below also apply.</span>
</p> </p>
<p class="c2"> <p class="c2">
@@ -488,26 +542,35 @@
<span class="c5 c1">a.</span> <span class="c5 c1">a.</span>
<span class="c1">&nbsp; &nbsp;</span> <span class="c1">&nbsp; &nbsp;</span>
<span class="c1 c5">Data Collection. </span> <span class="c1 c5">Data Collection. </span>
<span class="c1">The software may collect information about you and your use of the software, and send that to Microsoft. <span class="c1">The software may collect information about you and your use of the software,
Microsoft may use this information to provide services and improve our products and services. and send that to Microsoft.
You may opt out of many of these scenarios, but not all, as described in the product documentation. Microsoft may use this information to provide services and improve our products and
In using the software, you must comply with applicable law. You can learn more about data collection services.
You may opt out of many of these scenarios, but not all, as described in the product
documentation.
In using the software, you must comply with applicable law. You can learn more about data
collection
and use in the help documentation and the privacy statement at </span> and use in the help documentation and the privacy statement at </span>
<span class="c14 c1"> <span class="c14 c1">
<a class="c9" href="http://go.microsoft.com/fwlink/?LinkId=398505">http://go.microsoft.com/fwlink/?LinkId=398505</a> <a class="c9"
href="http://go.microsoft.com/fwlink/?LinkId=398505">http://go.microsoft.com/fwlink/?LinkId=398505</a>
</span> </span>
<span class="c1">.</span> <span class="c1">.</span>
<span class="c3 c1">&nbsp;Your use of the software operates as your consent to these practices.</span> <span class="c3 c1">&nbsp;Your use of the software operates as your consent to these
practices.</span>
</p> </p>
<p class="c8"> <p class="c8">
<span class="c5 c1">b.</span> <span class="c5 c1">b.</span>
<span class="c1">&nbsp; &nbsp;</span> <span class="c1">&nbsp; &nbsp;</span>
<span class="c5 c1">Processing of Personal Data. </span> <span class="c5 c1">Processing of Personal Data. </span>
<span class="c1">To the extent Microsoft is a processor or subprocessor of personal data in connection with the software, <span class="c1">To the extent Microsoft is a processor or subprocessor of personal data in
Microsoft makes the commitments in the European Union General Data Protection Regulation Terms connection with the software,
Microsoft makes the commitments in the European Union General Data Protection Regulation
Terms
of the Online Services Terms to all customers effective May 25, 2018, at </span> of the Online Services Terms to all customers effective May 25, 2018, at </span>
<span class="c1 c14"> <span class="c1 c14">
<a class="c9" href="http://go.microsoft.com/?linkid=9840733">http://go.microsoft.com/?linkid=9840733</a> <a class="c9"
href="http://go.microsoft.com/?linkid=9840733">http://go.microsoft.com/?linkid=9840733</a>
</span> </span>
<span class="c3 c1">.</span> <span class="c3 c1">.</span>
</p> </p>
@@ -515,48 +578,62 @@
<span class="c5 c1">6.</span> <span class="c5 c1">6.</span>
<span class="c1">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span> <span class="c1">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>
<span class="c5 c1">FEEDBACK. </span> <span class="c5 c1">FEEDBACK. </span>
<span class="c3 c1">If you give feedback about the software to Microsoft, you give to Microsoft, without charge, the <span class="c3 c1">If you give feedback about the software to Microsoft, you give to Microsoft,
without charge, the
right to use, share and commercialize your feedback in any way and for any purpose. You will right to use, share and commercialize your feedback in any way and for any purpose. You will
not give feedback that is subject to a license that requires Microsoft to license its software not give feedback that is subject to a license that requires Microsoft to license its
or documentation to third parties because we include your feedback in them. These rights survive software
or documentation to third parties because we include your feedback in them. These rights
survive
this agreement.</span> this agreement.</span>
</p> </p>
<p class="c2"> <p class="c2">
<span class="c5 c1">7.</span> <span class="c5 c1">7.</span>
<span class="c1">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span> <span class="c1">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>
<span class="c5 c1">SCOPE OF LICENSE.</span> <span class="c5 c1">SCOPE OF LICENSE.</span>
<span class="c3 c1">&nbsp;The software is licensed, not sold. This agreement only gives you some rights to use the software. <span class="c3 c1">&nbsp;The software is licensed, not sold. This agreement only gives you some
Microsoft reserves all other rights. Unless applicable law gives you more rights despite this rights to use the software.
Microsoft reserves all other rights. Unless applicable law gives you more rights despite
this
limitation, you may use the software only as expressly permitted in this agreement. &nbsp;In limitation, you may use the software only as expressly permitted in this agreement. &nbsp;In
doing so, you must comply with any technical limitations in the software that only allow you doing so, you must comply with any technical limitations in the software that only allow you
to use it in certain ways. You may not:</span> to use it in certain ways. You may not:</span>
</p> </p>
<p class="c8"> <p class="c8">
<span class="c3 c1">- &nbsp; &nbsp; work around any technical limitations in the software;</span> <span class="c3 c1">- &nbsp; &nbsp; work around any technical limitations in the
software;</span>
</p> </p>
<p class="c8"> <p class="c8">
<span class="c3 c1">- &nbsp; &nbsp; reverse engineer, decompile or disassemble the software, or attempt to do so, except <span class="c3 c1">- &nbsp; &nbsp; reverse engineer, decompile or disassemble the software, or
attempt to do so, except
and only to the extent required by third party licensing terms governing use of certain open and only to the extent required by third party licensing terms governing use of certain open
source components that may be included with the software;</span> source components that may be included with the software;</span>
</p> </p>
<p class="c8"> <p class="c8">
<span class="c3 c1">- &nbsp; &nbsp; remove, minimize, block or modify any notices of Microsoft or its suppliers in the <span class="c3 c1">- &nbsp; &nbsp; remove, minimize, block or modify any notices of Microsoft
or its suppliers in the
software; software;
</span> </span>
</p> </p>
<p class="c8"> <p class="c8">
<span class="c3 c1">- &nbsp; &nbsp; use the software in any way that is against the law; or</span> <span class="c3 c1">- &nbsp; &nbsp; use the software in any way that is against the law;
or</span>
</p> </p>
<p class="c8"> <p class="c8">
<span class="c3 c1">- &nbsp; &nbsp; share, publish, rent or lease the software, or provide the software as a stand-alone <span class="c3 c1">- &nbsp; &nbsp; share, publish, rent or lease the software, or provide the
software as a stand-alone
offering for others to use.</span> offering for others to use.</span>
</p> </p>
<p class="c2"> <p class="c2">
<span class="c5 c1">8. &nbsp; UPDATES. </span> <span class="c5 c1">8. &nbsp; UPDATES. </span>
<span class="c3 c1">The software may periodically check for updates and download and install them for you. You may obtain <span class="c3 c1">The software may periodically check for updates and download and install
updates only from Microsoft or authorized sources. Microsoft may need to update your system to them for you. You may obtain
provide you with updates. You agree to receive these automatic updates without any additional updates only from Microsoft or authorized sources. Microsoft may need to update your system
notice. Updates may not include or support all existing software features, services, or peripheral to
provide you with updates. You agree to receive these automatic updates without any
additional
notice. Updates may not include or support all existing software features, services, or
peripheral
devices. devices.
</span> </span>
</p> </p>
@@ -564,57 +641,74 @@
<span class="c5 c1">9.</span> <span class="c5 c1">9.</span>
<span class="c1">&nbsp; &nbsp;</span> <span class="c1">&nbsp; &nbsp;</span>
<span class="c5 c1">EXPORT RESTRICTIONS.</span> <span class="c5 c1">EXPORT RESTRICTIONS.</span>
<span class="c3 c1">&nbsp;You must comply with all domestic and international export laws and regulations that apply <span class="c3 c1">&nbsp;You must comply with all domestic and international export laws and
to the software, which include restrictions on destinations, end users and end use. For further regulations that apply
to the software, which include restrictions on destinations, end users and end use. For
further
information on export restrictions, visit (aka.ms/exporting).</span> information on export restrictions, visit (aka.ms/exporting).</span>
</p> </p>
<p class="c2"> <p class="c2">
<span class="c5 c1">10.</span> <span class="c5 c1">10.</span>
<span class="c1">&nbsp;</span> <span class="c1">&nbsp;</span>
<span class="c5 c1">SUPPORT SERVICES. </span> <span class="c5 c1">SUPPORT SERVICES. </span>
<span class="c3 c1">Because the software is &ldquo;as is,&rdquo; we may not provide support services for it.</span> <span class="c3 c1">Because the software is &ldquo;as is,&rdquo; we may not provide support
services for it.</span>
</p> </p>
<p class="c2"> <p class="c2">
<span class="c5 c1">11.</span> <span class="c5 c1">11.</span>
<span class="c1">&nbsp;</span> <span class="c1">&nbsp;</span>
<span class="c5 c1">ENTIRE AGREEMENT.</span> <span class="c5 c1">ENTIRE AGREEMENT.</span>
<span class="c3 c1">&nbsp;This agreement, and the terms for supplements, updates, Internet-based services and support <span class="c3 c1">&nbsp;This agreement, and the terms for supplements, updates, Internet-based
services that you use, are the entire agreement for the software and support services.</span> services and support
services that you use, are the entire agreement for the software and support
services.</span>
</p> </p>
<p class="c2"> <p class="c2">
<span class="c5 c1">12.</span> <span class="c5 c1">12.</span>
<span class="c1">&nbsp;</span> <span class="c1">&nbsp;</span>
<span class="c5 c1">APPLICABLE LAW. </span> <span class="c5 c1">APPLICABLE LAW. </span>
<span class="c3 c1">If you acquired the software in the United States, Washington State law applies to interpretation <span class="c3 c1">If you acquired the software in the United States, Washington State law
of and claims for breach of this agreement, and the laws of the state where you live apply to applies to interpretation
of and claims for breach of this agreement, and the laws of the state where you live apply
to
all other claims. If you acquired the software in any other country, its laws apply.</span> all other claims. If you acquired the software in any other country, its laws apply.</span>
</p> </p>
<p class="c2"> <p class="c2">
<span class="c5 c1">13.</span> <span class="c5 c1">13.</span>
<span class="c1">&nbsp;</span> <span class="c1">&nbsp;</span>
<span class="c5 c1">CONSUMER RIGHTS; REGIONAL VARIATIONS. </span> <span class="c5 c1">CONSUMER RIGHTS; REGIONAL VARIATIONS. </span>
<span class="c3 c1">This agreement describes certain legal rights. You may have other rights, including consumer rights, <span class="c3 c1">This agreement describes certain legal rights. You may have other rights,
under the laws of your state or country. Separate and apart from your relationship with Microsoft, including consumer rights,
you may also have rights with respect to the party from which you acquired the software. This under the laws of your state or country. Separate and apart from your relationship with
agreement does not change those other rights if the laws of your state or country do not permit Microsoft,
it to do so. For example, if you acquired the software in one of the below regions, or mandatory you may also have rights with respect to the party from which you acquired the software.
This
agreement does not change those other rights if the laws of your state or country do not
permit
it to do so. For example, if you acquired the software in one of the below regions, or
mandatory
country law applies, then the following provisions apply to you:</span> country law applies, then the following provisions apply to you:</span>
</p> </p>
<p class="c8"> <p class="c8">
<span class="c5 c1">a.</span> <span class="c5 c1">a.</span>
<span class="c1">&nbsp; &nbsp;</span> <span class="c1">&nbsp; &nbsp;</span>
<span class="c5 c1">Australia. </span> <span class="c5 c1">Australia. </span>
<span class="c3 c1">You have statutory guarantees under the Australian Consumer Law and nothing in this agreement is <span class="c3 c1">You have statutory guarantees under the Australian Consumer Law and nothing
in this agreement is
intended to affect those rights.</span> intended to affect those rights.</span>
</p> </p>
<p class="c8"> <p class="c8">
<span class="c5 c1">b.</span> <span class="c5 c1">b.</span>
<span class="c1">&nbsp; &nbsp;</span> <span class="c1">&nbsp; &nbsp;</span>
<span class="c5 c1">Canada. </span> <span class="c5 c1">Canada. </span>
<span class="c3 c1">If you acquired the software in Canada, you may stop receiving updates by turning off the automatic <span class="c3 c1">If you acquired the software in Canada, you may stop receiving updates by
update feature, disconnecting your device from the Internet (if and when you re-connect to the turning off the automatic
Internet, however, the software will resume checking for and installing updates), or uninstalling update feature, disconnecting your device from the Internet (if and when you re-connect to
the software. The product documentation, if any, may also specify how to turn off updates for the
Internet, however, the software will resume checking for and installing updates), or
uninstalling
the software. The product documentation, if any, may also specify how to turn off updates
for
your specific device or software.</span> your specific device or software.</span>
</p> </p>
<p class="c8"> <p class="c8">
@@ -627,8 +721,10 @@
<span class="c5 c1">(i)</span> <span class="c5 c1">(i)</span>
<span class="c1">&nbsp; &nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span> <span class="c1">&nbsp; &nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>
<span class="c5 c1">Warranty</span> <span class="c5 c1">Warranty</span>
<span class="c3 c1">. The properly licensed software will perform substantially as described in any Microsoft materials <span class="c3 c1">. The properly licensed software will perform substantially as described in
that accompany the software. However, Microsoft gives no contractual guarantee in relation to any Microsoft materials
that accompany the software. However, Microsoft gives no contractual guarantee in relation
to
the licensed software.</span> the licensed software.</span>
</p> </p>
<p class="c6"> <p class="c6">
@@ -638,74 +734,103 @@
<span class="c5 c1">(ii)</span> <span class="c5 c1">(ii)</span>
<span class="c1">&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span> <span class="c1">&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>
<span class="c5 c1">Limitation of Liability</span> <span class="c5 c1">Limitation of Liability</span>
<span class="c3 c1">. In case of intentional conduct, gross negligence, claims based on the Product Liability Act, as <span class="c3 c1">. In case of intentional conduct, gross negligence, claims based on the
well as, in case of death or personal or physical injury, Microsoft is liable according to the Product Liability Act, as
well as, in case of death or personal or physical injury, Microsoft is liable according to
the
statutory law.</span> statutory law.</span>
</p> </p>
<p class="c10"> <p class="c10">
<span class="c3 c1">Subject to the foregoing clause (ii), Microsoft will only be liable for slight negligence if Microsoft <span class="c3 c1">Subject to the foregoing clause (ii), Microsoft will only be liable for
is in breach of such material contractual obligations, the fulfillment of which facilitate the slight negligence if Microsoft
due performance of this agreement, the breach of which would endanger the purpose of this agreement is in breach of such material contractual obligations, the fulfillment of which facilitate
and the compliance with which a party may constantly trust in (so-called &quot;cardinal obligations&quot;). the
In other cases of slight negligence, Microsoft will not be liable for slight negligence.</span> due performance of this agreement, the breach of which would endanger the purpose of this
agreement
and the compliance with which a party may constantly trust in (so-called &quot;cardinal
obligations&quot;).
In other cases of slight negligence, Microsoft will not be liable for slight
negligence.</span>
</p> </p>
<p class="c2"> <p class="c2">
<span class="c5 c1">14.</span> <span class="c5 c1">14.</span>
<span class="c1">&nbsp;</span> <span class="c1">&nbsp;</span>
<span class="c5 c1">LEGAL EFFECT.</span> <span class="c5 c1">LEGAL EFFECT.</span>
<span class="c3 c1">&nbsp;This agreement describes certain legal rights. You may have other rights under the laws of <span class="c3 c1">&nbsp;This agreement describes certain legal rights. You may have other
your country. You may also have rights with respect to the party from whom you acquired the software. rights under the laws of
This agreement does not change your rights under the laws of your country if the laws of your your country. You may also have rights with respect to the party from whom you acquired the
software.
This agreement does not change your rights under the laws of your country if the laws of
your
country do not permit it to do so.</span> country do not permit it to do so.</span>
</p> </p>
<p class="c2"> <p class="c2">
<span class="c5 c1">15.</span> <span class="c5 c1">15.</span>
<span class="c1">&nbsp;</span> <span class="c1">&nbsp;</span>
<span class="c4 c1">DISCLAIMER OF WARRANTY. THE SOFTWARE IS LICENSED &ldquo;AS-IS.&rdquo; &nbsp;YOU BEAR THE RISK OF <span class="c4 c1">DISCLAIMER OF WARRANTY. THE SOFTWARE IS LICENSED &ldquo;AS-IS.&rdquo;
USING IT. MICROSOFT GIVES NO EXPRESS WARRANTIES, GUARANTEES OR CONDITIONS. TO THE EXTENT PERMITTED &nbsp;YOU BEAR THE RISK OF
USING IT. MICROSOFT GIVES NO EXPRESS WARRANTIES, GUARANTEES OR CONDITIONS. TO THE EXTENT
PERMITTED
UNDER YOUR LOCAL LAWS, MICROSOFT EXCLUDES THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS UNDER YOUR LOCAL LAWS, MICROSOFT EXCLUDES THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.</span> FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.</span>
</p> </p>
<p class="c2"> <p class="c2">
<span class="c5 c1">16.</span> <span class="c5 c1">16.</span>
<span class="c1">&nbsp;</span> <span class="c1">&nbsp;</span>
<span class="c4 c1">LIMITATION ON AND EXCLUSION OF DAMAGES. YOU CAN RECOVER FROM MICROSOFT AND ITS SUPPLIERS ONLY DIRECT <span class="c4 c1">LIMITATION ON AND EXCLUSION OF DAMAGES. YOU CAN RECOVER FROM MICROSOFT AND
DAMAGES UP TO U.S. $5.00. YOU CANNOT RECOVER ANY OTHER DAMAGES, INCLUDING CONSEQUENTIAL, LOST ITS SUPPLIERS ONLY DIRECT
DAMAGES UP TO U.S. $5.00. YOU CANNOT RECOVER ANY OTHER DAMAGES, INCLUDING CONSEQUENTIAL,
LOST
PROFITS, SPECIAL, INDIRECT OR INCIDENTAL DAMAGES.</span> PROFITS, SPECIAL, INDIRECT OR INCIDENTAL DAMAGES.</span>
</p> </p>
<p class="c0"> <p class="c0">
<span class="c3 c1">This limitation applies to (a) anything related to the software, services, content (including code) <span class="c3 c1">This limitation applies to (a) anything related to the software, services,
on third party Internet sites, or third party programs; and (b) claims for breach of contract, content (including code)
breach of warranty, guarantee or condition, strict liability, negligence, or other tort to the on third party Internet sites, or third party programs; and (b) claims for breach of
contract,
breach of warranty, guarantee or condition, strict liability, negligence, or other tort to
the
extent permitted by applicable law.</span> extent permitted by applicable law.</span>
</p> </p>
<p class="c0"> <p class="c0">
<span class="c3 c1">It also applies even if Microsoft knew or should have known about the possibility of the damages. <span class="c3 c1">It also applies even if Microsoft knew or should have known about the
The above limitation or exclusion may not apply to you because your country may not allow the possibility of the damages.
The above limitation or exclusion may not apply to you because your country may not allow
the
exclusion or limitation of incidental, consequential or other damages.</span> exclusion or limitation of incidental, consequential or other damages.</span>
</p> </p>
<p class="c12"> <p class="c12">
<span class="c4 c1">Please note: As the software is distributed in Quebec, Canada, some of the clauses in this agreement <span class="c4 c1">Please note: As the software is distributed in Quebec, Canada, some of the
clauses in this agreement
are provided below in French.</span> are provided below in French.</span>
</p> </p>
<p class="c12"> <p class="c12">
<span class="c4 c1">Remarque : Ce logiciel &eacute;tant distribu&eacute; au Qu&eacute;bec, Canada, certaines des clauses <span class="c4 c1">Remarque : Ce logiciel &eacute;tant distribu&eacute; au Qu&eacute;bec,
Canada, certaines des clauses
dans ce contrat sont fournies ci-dessous en fran&ccedil;ais.</span> dans ce contrat sont fournies ci-dessous en fran&ccedil;ais.</span>
</p> </p>
<p class="c11"> <p class="c11">
<span class="c5 c1">EXON&Eacute;RATION DE GARANTIE.</span> <span class="c5 c1">EXON&Eacute;RATION DE GARANTIE.</span>
<span class="c1 c3">&nbsp;Le logiciel vis&eacute; par une licence est offert &laquo; tel quel &raquo;. Toute utilisation <span class="c1 c3">&nbsp;Le logiciel vis&eacute; par une licence est offert &laquo; tel quel
de ce logiciel est &agrave; votre seule risque et p&eacute;ril. Microsoft n&rsquo;accorde aucune &raquo;. Toute utilisation
autre garantie expresse. Vous pouvez b&eacute;n&eacute;ficier de droits additionnels en vertu de ce logiciel est &agrave; votre seule risque et p&eacute;ril. Microsoft n&rsquo;accorde
du droit local sur la protection des consommateurs, que ce contrat ne peut modifier. La ou elles aucune
sont permises par le droit locale, les garanties implicites de qualit&eacute; marchande, d&rsquo;ad&eacute;quation autre garantie expresse. Vous pouvez b&eacute;n&eacute;ficier de droits additionnels en
vertu
du droit local sur la protection des consommateurs, que ce contrat ne peut modifier. La ou
elles
sont permises par le droit locale, les garanties implicites de qualit&eacute; marchande,
d&rsquo;ad&eacute;quation
&agrave; un usage particulier et d&rsquo;absence de contrefa&ccedil;on sont exclues. &agrave; un usage particulier et d&rsquo;absence de contrefa&ccedil;on sont exclues.
</span> </span>
</p> </p>
<p class="c11"> <p class="c11">
<span class="c5 c1">LIMITATION DES DOMMAGES-INT&Eacute;R&Ecirc;TS ET EXCLUSION DE RESPONSABILIT&Eacute; POUR LES DOMMAGES.</span> <span class="c5 c1">LIMITATION DES DOMMAGES-INT&Eacute;R&Ecirc;TS ET EXCLUSION DE
<span class="c3 c1">&nbsp;Vous pouvez obtenir de Microsoft et de ses fournisseurs une indemnisation en cas de dommages RESPONSABILIT&Eacute; POUR LES DOMMAGES.</span>
directs uniquement &agrave; hauteur de 5,00 $ US. Vous ne pouvez pr&eacute;tendre &agrave; aucune <span class="c3 c1">&nbsp;Vous pouvez obtenir de Microsoft et de ses fournisseurs une
indemnisation en cas de dommages
directs uniquement &agrave; hauteur de 5,00 $ US. Vous ne pouvez pr&eacute;tendre &agrave;
aucune
indemnisation pour les autres dommages, y compris les dommages sp&eacute;ciaux, indirects ou indemnisation pour les autres dommages, y compris les dommages sp&eacute;ciaux, indirects ou
accessoires et pertes de b&eacute;n&eacute;fices.</span> accessoires et pertes de b&eacute;n&eacute;fices.</span>
</p> </p>
@@ -713,26 +838,36 @@
<span class="c3 c1">Cette limitation concerne :</span> <span class="c3 c1">Cette limitation concerne :</span>
</p> </p>
<p class="c2"> <p class="c2">
<span class="c3 c1">- &nbsp; &nbsp; &nbsp; &nbsp; tout ce qui est reli&eacute; au logiciel, aux services ou au contenu <span class="c3 c1">- &nbsp; &nbsp; &nbsp; &nbsp; tout ce qui est reli&eacute; au logiciel, aux
(y compris le code) figurant sur des sites Internet tiers ou dans des programmes tiers ; et</span> services ou au contenu
(y compris le code) figurant sur des sites Internet tiers ou dans des programmes tiers ;
et</span>
</p> </p>
<p class="c2"> <p class="c2">
<span class="c3 c1">- &nbsp; &nbsp; &nbsp; &nbsp; les r&eacute;clamations au titre de violation de contrat ou de garantie, <span class="c3 c1">- &nbsp; &nbsp; &nbsp; &nbsp; les r&eacute;clamations au titre de violation
ou au titre de responsabilit&eacute; stricte, de n&eacute;gligence ou d&rsquo;une autre faute de contrat ou de garantie,
ou au titre de responsabilit&eacute; stricte, de n&eacute;gligence ou d&rsquo;une autre
faute
dans la limite autoris&eacute;e par la loi en vigueur.</span> dans la limite autoris&eacute;e par la loi en vigueur.</span>
</p> </p>
<p class="c12"> <p class="c12">
<span class="c3 c1">Elle s&rsquo;applique &eacute;galement, m&ecirc;me si Microsoft connaissait ou devrait conna&icirc;tre <span class="c3 c1">Elle s&rsquo;applique &eacute;galement, m&ecirc;me si Microsoft connaissait
ou devrait conna&icirc;tre
l&rsquo;&eacute;ventualit&eacute; d&rsquo;un tel dommage. Si votre pays n&rsquo;autorise pas l&rsquo;&eacute;ventualit&eacute; d&rsquo;un tel dommage. Si votre pays n&rsquo;autorise pas
l&rsquo;exclusion ou la limitation de responsabilit&eacute; pour les dommages indirects, accessoires l&rsquo;exclusion ou la limitation de responsabilit&eacute; pour les dommages indirects,
ou de quelque nature que ce soit, il se peut que la limitation ou l&rsquo;exclusion ci-dessus accessoires
ou de quelque nature que ce soit, il se peut que la limitation ou l&rsquo;exclusion
ci-dessus
ne s&rsquo;appliquera pas &agrave; votre &eacute;gard.</span> ne s&rsquo;appliquera pas &agrave; votre &eacute;gard.</span>
</p> </p>
<p class="c16"> <p class="c16">
<span class="c5 c1">EFFET JURIDIQUE.</span> <span class="c5 c1">EFFET JURIDIQUE.</span>
<span class="c3 c1">&nbsp;Le pr&eacute;sent contrat d&eacute;crit certains droits juridiques. Vous pourriez avoir d&rsquo;autres <span class="c3 c1">&nbsp;Le pr&eacute;sent contrat d&eacute;crit certains droits juridiques.
droits pr&eacute;vus par les lois de votre pays. Le pr&eacute;sent contrat ne modifie pas les Vous pourriez avoir d&rsquo;autres
droits que vous conf&egrave;rent les lois de votre pays si celles-ci ne le permettent pas.</span> droits pr&eacute;vus par les lois de votre pays. Le pr&eacute;sent contrat ne modifie pas
les
droits que vous conf&egrave;rent les lois de votre pays si celles-ci ne le permettent
pas.</span>
</p> </p>
<p class="c15"> <p class="c15">
<span class="c3 c1"></span> <span class="c3 c1"></span>
@@ -740,9 +875,9 @@
<p class="c16"> <p class="c16">
<span class="c3 c1">&nbsp;</span> <span class="c3 c1">&nbsp;</span>
</p> </p>
<p class="c11"> <p class="c11">
<span class="c3 c1">LEGO, the LEGO logo, MINDSTORMS and the MINDSTORMS EV3 logo are trademarks and/ or copyrights of <span class="c3 c1">@copyrightText@</span>
the LEGO Group. &copy;2018 The LEGO Group. All rights reserved.</span>
</p> </p>
<p class="c11"> <p class="c11">
<span class="c3 c1">&nbsp;</span> <span class="c3 c1">&nbsp;</span>
@@ -755,27 +890,30 @@
</p> </p>
</div> </div>
</div> </div>
<div class="ui center aligned segment"> <div id="agree-segment" class="ui center aligned segment hidden">
<input id="agree-checkbox" type="checkbox" autocomplete="off" onchange="agreeCheckboxChanged(this)"> <input id="agree-checkbox" type="checkbox" autocomplete="off" onchange="agreeCheckboxChanged(this)">
<label for="agree-checkbox">I agree to these Microsoft Software License Terms and to the <label for="agree-checkbox">I agree to these Microsoft Software License Terms and to the
<a href="https://privacy.microsoft.com/en-us/privacystatement">Microsoft Privacy Statement.</a> <a href="https://privacy.microsoft.com/en-us/privacystatement">Microsoft Privacy Statement.</a>
</label> </label>
</div> </div>
<div id="no-download-segment" class="ui center aligned segment hidden">
<p>Sorry, there is no Offline App available for this editor.</p>
</div>
<div id="download-segment" class="ui center aligned segment hidden"> <div id="download-segment" class="ui center aligned segment hidden">
<div class="ui grid"> <div class="ui grid">
<div class="eight wide column"> <div class="eight wide column">
<h3 class="ui">Windows</h3> <h3 class="ui">Windows</h3>
<button class="ui icon button" onclick="downloadWin64()"> <a id="download-win64" class="ui icon button" onclick="downloadWin64()">
<i class="download icon"></i> <i class="download icon"></i>
makecode-ev3-setup-win64.exe makecode-@targetid@-setup-win64.exe
</button> </a>
</div> </div>
<div class="eight wide column"> <div class="eight wide column">
<h3 class="ui">Mac OS</h3> <h3 class="ui">Mac OS</h3>
<button class="ui icon button" onclick="downloadMac64()"> <a id="download-mac64" class="ui icon button" onclick="downloadMac64()">
<i class="download icon"></i> <i class="download icon"></i>
makecode-ev3-mac64.zip makecode-@targetid@-mac64.zip
</button> </a>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -3,9 +3,3 @@
## Offline app #target-app ## Offline app #target-app
The MakeCode editor is available as app which you can install on a computer with Windows or Mac OS. Once installed, the **[MakeCode Offline App](/offline-app)** lets you create, run, and download your projects to the @boardname@. It works the same as the Web application does in your browser but it's a stand-alone application that will work when a connection to the internet is restricted or not available. The MakeCode editor is available as app which you can install on a computer with Windows or Mac OS. Once installed, the **[MakeCode Offline App](/offline-app)** lets you create, run, and download your projects to the @boardname@. It works the same as the Web application does in your browser but it's a stand-alone application that will work when a connection to the internet is restricted or not available.
### ~ hint
The [MakeCode Offline App](/offline-app) is currently in development and is made available as a **pre-release** version.
### ~

View File

@@ -10,12 +10,12 @@ You can find out what's connected to the ports on the brick and show its status.
## Example ## Example
Show the status of the ports on the brick when the ``enter`` button is pressed. Show the status of the ports on the brick. Resets all motors when ENTER is pressed.
```blocks ```blocks
brick.showString("Press ENTER for port status", 1) brick.showPorts()
brick.buttonEnter.onEvent(ButtonEvent.Pressed, function () { brick.buttonEnter.onEvent(ButtonEvent.Pressed, function () {
brick.showPorts() motors.resetAll()
}) })
``` ```

View File

@@ -0,0 +1,25 @@
# reset All Motors
Reset all motors currently running on the brick.
```sig
motors.resetAll();
```
The motors counters are resetted.
## Example
Tank the EV3 Brick forward at half speed for 5 seconds and then stop.
```blocks
motors.largeAB.tank(50, 50);
pause(5000);
motors.stopAll();
motors.resetAll();
```
## See also
[stop all](/reference/motors/motor/stop-all),
[reset](/reference/motors/motor/reset)

View File

@@ -22,4 +22,5 @@ motors.stopAll();
[stop](/reference/motors/motor/stop), [stop](/reference/motors/motor/stop),
[reset](/reference/motors/motor/reset), [reset](/reference/motors/motor/reset),
[reset-all](/reference/motors/motor/reset-all),
[set brake](/reference/motors/motor/set-brake) [set brake](/reference/motors/motor/set-brake)

View File

@@ -22,7 +22,7 @@ If you decide to use a **value** of rotation distance, you need to choose a type
## Parameters ## Parameters
* **turnRatio**: a [number](/types/number) that is the percentage of speed of the drive motor. The follower motor runs at this speed. A negative number steers to the left and a positive number steers to the right. This is a number between `-100` and `100`. * **turnRatio**: a [number](/types/number) that is the percentage of speed of the drive motor. The follower motor runs at this speed. A negative number steers to the left and a positive number steers to the right. This is a number between `-200` and `200`.
* **speed**: a [number](/types/number) that is the percentage of full speed. A negative value runs the motors in the reverse direction. This is the speed that the drive motor runs at. * **speed**: a [number](/types/number) that is the percentage of full speed. A negative value runs the motors in the reverse direction. This is the speed that the drive motor runs at.
* **value**: the [number](/types/number) of movement units to rotate for. A value of `0` means run the motor continuously. * **value**: the [number](/types/number) of movement units to rotate for. A value of `0` means run the motor continuously.
* **unit**: the movement unit of rotation. This can be `milliseconds`, `seconds`, `degrees`, or `rotations`. If the number for **value** is `0`, this parameter isn't used. * **unit**: the movement unit of rotation. This can be `milliseconds`, `seconds`, `degrees`, or `rotations`. If the number for **value** is `0`, this parameter isn't used.

BIN
docs/static/bluetooth/experimental.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

BIN
docs/static/tutorials/calibrate-gyro.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

BIN
docs/static/tutorials/drifter.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
docs/static/tutorials/move-to-color.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
docs/static/tutorials/pause-on-start.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

BIN
docs/static/tutorials/turn-with-gyro.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

@@ -25,6 +25,11 @@ Step by step guides to coding your @boardname@.
"description": "Use the color sensor to follow line or detect colors", "description": "Use the color sensor to follow line or detect colors",
"url":"/tutorials/color-sensor", "url":"/tutorials/color-sensor",
"imageUrl":"/static/tutorials/what-color.png" "imageUrl":"/static/tutorials/what-color.png"
}, {
"name": "Gyro",
"description": "Drive straight or turn more precisely with the gyro",
"url":"/tutorials/gyro",
"imageUrl":"/static/tutorials/calibrate-gyro.png"
}, { }, {
"name": "Ultrasonic Sensor", "name": "Ultrasonic Sensor",
"description": "Use the ultrasonic sensor to detect obstacles", "description": "Use the ultrasonic sensor to detect obstacles",

View File

@@ -27,6 +27,12 @@
"cardType": "tutorial", "cardType": "tutorial",
"url":"/tutorials/music-brick", "url":"/tutorials/music-brick",
"imageUrl":"/static/tutorials/music-brick.png" "imageUrl":"/static/tutorials/music-brick.png"
}, {
"name": "Pause On Start",
"description": "Don't start running immediately!",
"cardType": "tutorial",
"url":"/tutorials/pause-on-start",
"imageUrl":"/static/tutorials/pause-on-start.png"
}] }]
``` ```

View File

@@ -0,0 +1,37 @@
# Calibrate Gyro
## Introduction @fullscreen
The gyroscope is a very useful sensor in the EV3 system. It detects the rotation rate
which can be very useful to correct the trajectory of the robot and do precise turns.
However, the sensor can be imprecise and subject to drifting. It is recommend to
calibrate your sensor at least once after starting your brick. You don't have to
recalibrate on every run.
* [EV3 Driving Base](https://le-www-live-s.legocdn.com/sc/media/lessons/mindstorms-ev3/building-instructions/ev3-rem-driving-base-79bebfc16bd491186ea9c9069842155e.pdf)
* [EV3 Driving Base with Gyro](https://le-www-live-s.legocdn.com/sc/media/lessons/mindstorms-ev3/building-instructions/ev3-gyro-sensor-driving-base-a521f8ebe355c281c006418395309e15.pdf)
## Step 1 Show ports
Add the ``||brick:show ports||`` to see the status of the gyroscope.
```blocks
brick.showPorts()
```
## Step 2 Calibration
Add a ``||sensors:calibrate gyro||`` block to calibrate the gyro. The block
detects if the sensor is present and does a full reset of the sensor if necessary.
```blocks
brick.showPorts()
sensors.gyro2.calibrate()
```
## Step 3 Download and run @fullscreen
Download this program to your brick and press the ENTER button.

View File

@@ -0,0 +1,54 @@
# City Shaper
## Tutorials
```codecard
[
{
"name": "Crane Mission / Robot 1",
"description": "Learn the basics and build your first robot driving base.",
"cardType": "tutorial",
"url":"/tutorials/city-shaper/robot-1",
"imageUrl": "/static/tutorials/city-shaper/robot1.jpg"
}, {
"name": "Crane Mission / Robot 2",
"description": "Program your robot to move in different ways.",
"cardType": "tutorial",
"url":"/tutorials/city-shaper/robot-2",
"imageUrl": "/static/tutorials/city-shaper/robot2.jpg"
}, {
"name": "Crane Mission / Video 1",
"description": "A video of the Robot 1 tutorial",
"youTubeId": "IqL0Pyeu5Ng"
}, {
"name": "Bluetooth download (beta)",
"description": "EXPERIMENTAL! Learn how to download code via Bluetooth.",
"youTubeId": "VIq8-6Egtqs"
}, {
"name": "Turn with Gyro",
"description": "Use the gyro for precise turns.",
"youTubeId": "I7ncuXAfBwk"
}, {
"name": "Moving with Gyro",
"description": "Use the gyro for correct the robot trajectory.",
"youTubeId": "ufiOPvW37xc"
}, {
"name": "Line following with 1 color sensor",
"description": "Simple line following using the color sensor.",
"youTubeId": "_LeduyKQVjg"
}, {
"name": "Proportional line following with 1 color sensor",
"description": "Proportional line following using the color sensor.",
"youTubeId": "-AirqwC9DL4"
}, {
"name": "Proportional line following with 2 color sensors",
"description": "Proportional line following using two color sensor.",
"youTubeId": "QWOflBuu9Oo"
}]
```
## See Also
[Robot 1](/tutorials/city-shaper/robot-1),
[Robot 2](/tutorials/city-shaper/robot-2)

View File

@@ -2,9 +2,10 @@
## Step 1 - Build Your Driving Base Robot @unplugged ## Step 1 - Build Your Driving Base Robot @unplugged
Build the robot driving base: Build the medium motor robot driving base:
[![EV3 Driving Base](/static/lessons/common/ev3-driving-base.jpg)](https://le-www-live-s.legocdn.com/sc/media/lessons/mindstorms-ev3/building-instructions/ev3-rem-driving-base-79bebfc16bd491186ea9c9069842155e.pdf) * [Driving base](https://le-www-live-s.legocdn.com/sc/media/lessons/mindstorms-ev3/building-instructions/ev3-rem-driving-base-79bebfc16bd491186ea9c9069842155e.pdf)
* [Medium motor driving base](https://le-www-live-s.legocdn.com/sc/media/lessons/mindstorms-ev3/building-instructions/ev3-medium-motor-driving-base-e66e2fc0d917485ef1aa023e8358e7a7.pdf)
If clicking on the image above doesn't open the instructions, right-click on the image and choose "Save link as..." to download the PDF. If clicking on the image above doesn't open the instructions, right-click on the image and choose "Save link as..." to download the PDF.
@@ -14,7 +15,7 @@ At first, it's nice to know that your program is running. Plug in a ``||brick:sh
into the ``||loops:on start||`` block. Change the image to something else if you want! into the ``||loops:on start||`` block. Change the image to something else if you want!
```blocks ```blocks
brick.showMood(moods.neutral) brick.showMood(moods.sleeping)
``` ```
## Step 3 - Try your code @fullscreen ## Step 3 - Try your code @fullscreen
@@ -22,17 +23,29 @@ brick.showMood(moods.neutral)
Look at the simulator and check that your image is showing on the screen. When you are ready, press the **DOWNLOAD** button Look at the simulator and check that your image is showing on the screen. When you are ready, press the **DOWNLOAD** button
and follow the instructions to transfer your code on the brick. and follow the instructions to transfer your code on the brick.
## Step 4 - Steer motors @fullscreen ## Step 4 - Pause on Start @fullscreen
As you may have noticed, the code starts running immediately once you download it to the brick. To prevent the robot
from rolling away, add a ``||brick:pause until enter pressed||`` button to wait for the user to press enter.
```blocks
brick.showMood(moods.sleeping)
brick.buttonEnter.pauseUntil(ButtonEvent.Pressed)
```
## Step 5 - Steer motors @fullscreen
Drag a ``||motors:steer motors||`` block from the **MOTORS** toolbox drawer and snap it in under ``||brick:show mood||``. Drag a ``||motors:steer motors||`` block from the **MOTORS** toolbox drawer and snap it in under ``||brick:show mood||``.
Click on the **(+)** symbol and make sure to tell your motors to turn **1** rotation. Click on the **(+)** symbol and make sure to tell your motors to turn **1** rotation.
```blocks ```blocks
brick.showMood(moods.sleeping)
brick.buttonEnter.pauseUntil(ButtonEvent.Pressed)
brick.showMood(moods.neutral) brick.showMood(moods.neutral)
motors.largeBC.steer(0, 50, 1, MoveUnit.Rotations) motors.largeBC.steer(0, 50, 1, MoveUnit.Rotations)
``` ```
## Step 5 - Try your code @fullscreen ## Step 6 - Try your code @fullscreen
Whenever you make a code change, the simulator will restart so you can see what your latest change will do. Whenever you make a code change, the simulator will restart so you can see what your latest change will do.
When you are ready, click **DOWNLOAD** and follow the instructions to transfer the code into your brick. When you are ready, click **DOWNLOAD** and follow the instructions to transfer the code into your brick.

View File

@@ -25,6 +25,7 @@ After downloading your code, press **UP** to move the robot.
```blocks ```blocks
brick.buttonUp.onEvent(ButtonEvent.Pressed, function () { brick.buttonUp.onEvent(ButtonEvent.Pressed, function () {
brick.showMood(moods.awake)
motors.largeBC.steer(0, 50, 1, MoveUnit.Rotations) motors.largeBC.steer(0, 50, 1, MoveUnit.Rotations)
}) })
brick.showMood(moods.neutral) brick.showMood(moods.neutral)
@@ -38,6 +39,7 @@ Drag a ``||motors:set brake||`` block into the ``||loops:on start||`` and set it
```blocks ```blocks
brick.buttonUp.onEvent(ButtonEvent.Pressed, function () { brick.buttonUp.onEvent(ButtonEvent.Pressed, function () {
brick.showMood(moods.awake)
motors.largeBC.steer(0, 50, 1, MoveUnit.Rotations) motors.largeBC.steer(0, 50, 1, MoveUnit.Rotations)
}) })
brick.showMood(moods.neutral) brick.showMood(moods.neutral)
@@ -53,9 +55,11 @@ Put a ``||motors:steer motors||`` in for that button and set the turn ratio to d
```blocks ```blocks
brick.buttonLeft.onEvent(ButtonEvent.Pressed, function () { brick.buttonLeft.onEvent(ButtonEvent.Pressed, function () {
brick.showMood(moods.middleLeft)
motors.largeBC.steer(-50, 50, 1, MoveUnit.Rotations) motors.largeBC.steer(-50, 50, 1, MoveUnit.Rotations)
}) })
brick.buttonRight.onEvent(ButtonEvent.Pressed, function () { brick.buttonRight.onEvent(ButtonEvent.Pressed, function () {
brick.showMood(moods.middleRight)
motors.largeBC.steer(50, 50, 1, MoveUnit.Rotations) motors.largeBC.steer(50, 50, 1, MoveUnit.Rotations)
}) })
``` ```
@@ -67,6 +71,7 @@ Add a ``||motors:steer motors||`` to an ``||brick:on button||`` block and change
```blocks ```blocks
brick.buttonDown.onEvent(ButtonEvent.Pressed, function () { brick.buttonDown.onEvent(ButtonEvent.Pressed, function () {
brick.showMood(moods.knockedOut)
motors.largeBC.steer(0, -50, 1, MoveUnit.Rotations) motors.largeBC.steer(0, -50, 1, MoveUnit.Rotations)
}) })
``` ```
@@ -85,6 +90,7 @@ Create a program that moves the Driving Base and makes it stop 6 cm from the Cub
```blocks ```blocks
brick.buttonEnter.onEvent(ButtonEvent.Pressed, function () { brick.buttonEnter.onEvent(ButtonEvent.Pressed, function () {
brick.showMood(moods.dizzy)
motors.largeBC.steer(0, 50) motors.largeBC.steer(0, 50)
pauseUntil(() => sensors.ultrasonic4.distance() < 6) pauseUntil(() => sensors.ultrasonic4.distance() < 6)
motors.largeBC.stop() motors.largeBC.stop()

View File

@@ -21,6 +21,12 @@
"cardType": "tutorial", "cardType": "tutorial",
"url":"/tutorials/redlight-greenlight", "url":"/tutorials/redlight-greenlight",
"imageUrl":"/static/tutorials/redlight-greenlight.png" "imageUrl":"/static/tutorials/redlight-greenlight.png"
}, {
"name": "Move To Color",
"description": "Move straight until a color is detected.",
"cardType": "tutorial",
"url":"/tutorials/move-to-color",
"imageUrl":"/static/tutorials/move-to-color.png"
}, { }, {
"name": "Reflected Light Measure", "name": "Reflected Light Measure",
"description": "Teach the sensor what light or dark is.", "description": "Teach the sensor what light or dark is.",

34
docs/tutorials/drifter.md Normal file
View File

@@ -0,0 +1,34 @@
# Drifter
Use this program to try out the gyro sensor and the effect of drifting.
```typescript
// this loop shows the rate, angle and drift of the robot
forever(() => {
brick.showValue("rate", sensors.gyro2.rate(), 1)
brick.showValue("angle", sensors.gyro2.angle(), 2)
brick.showValue("drift", sensors.gyro2.drift(), 3)
})
// this loop shows wheter the sensor is calibrating
forever(() => {
brick.showString(sensors.gyro2.isCalibrating() ? "calibrating..." : "", 4)
})
// instructions on how to use the buttons
brick.showString("ENTER: calibrate", 7)
brick.showString(" (reset+drift)", 8)
brick.showString("LEFT: reset", 9)
brick.showString("RIGHT: compute drift", 10)
// enter -> calibrate
brick.buttonEnter.onEvent(ButtonEvent.Pressed, function () {
sensors.gyro2.calibrate()
})
// right -> compute drift
brick.buttonRight.onEvent(ButtonEvent.Pressed, function () {
sensors.gyro2.computeDrift()
})
// left -> reset
brick.buttonLeft.onEvent(ButtonEvent.Pressed, function () {
sensors.gyro2.reset()
})
```

31
docs/tutorials/gyro.md Normal file
View File

@@ -0,0 +1,31 @@
# Gyro tutorials
## Tutorials
```codecard
[{
"name": "Calibrate",
"description": "Make sure you gyro sensor is ready to use",
"cardType": "tutorial",
"url":"/tutorials/calibrate-gyro",
"imageUrl":"/static/tutorials/calibrate-gyro.png"
}, {
"name": "Turn",
"description": "Use the gyro to turn precisely",
"cardType": "tutorial",
"url":"/tutorials/turn-with-gyro",
"imageUrl":"/static/tutorials/turn-with-gyro.png"
}, {
"name": "Move Straight",
"description": "Use the gyro to correct the trajectory of the robot",
"cardType": "tutorial",
"url":"/tutorials/move-straight-with-gyro",
"imageUrl":"/static/tutorials/move-straight-with-gyro.png"
}, {
"name": "Drifter",
"description": "Explore how the gyro is drifting",
"cardType": "example",
"url":"/tutorials/drifter",
"imageUrl":"/static/tutorials/drifter.png"
}]
```

View File

@@ -42,7 +42,7 @@
}, { }, {
"name": "Turtle", "name": "Turtle",
"description": "Encode moves and run them on a driving base", "description": "Encode moves and run them on a driving base",
"url":"/examples/turtle", "url":"/tutorials/turtle",
"cardType": "example" "cardType": "example"
}] }]
``` ```

View File

@@ -0,0 +1,61 @@
# Move Straight With Gyro
## Introduction @fullscreen
Rotating using a wheel is not precise. The wheel can slip or the motors
can be slightly different.
With the help of the gyro you can detect and correct deviations in your trajectory.
* [EV3 Driving Base](https://le-www-live-s.legocdn.com/sc/media/lessons/mindstorms-ev3/building-instructions/ev3-rem-driving-base-79bebfc16bd491186ea9c9069842155e.pdf)
* [EV3 Driving Base with Gyro](https://le-www-live-s.legocdn.com/sc/media/lessons/mindstorms-ev3/building-instructions/ev3-gyro-sensor-driving-base-a521f8ebe355c281c006418395309e15.pdf)
## Step 1 Calibration
Add a ``||sensors:calibrate gyro||`` block in a ``||brick:on button enter pressed||`` block so that you can manually start a calibration process. Run the calibration
at least once after connecting the gyro.
```blocks
brick.showPorts()
sensors.gyro2.calibrate()
```
## Step 2 Compute the error
Make a new **error** variable and drag the ``||sensors:gyro rate||``
and multiply it by -1. Since the rate shows the rotation rate, we will
counter it by negating it.
```blocks
let error = 0
brick.showPorts()
sensors.gyro2.calibrate()
while (true) {
error = sensors.gyro2.rate() * -1
}
```
## Step 3 Steer with feedback
Drag a ``||motors:steer motors||`` block under the variable and pass
the **error** variable into the turn ratio section.
If the robot is turning right, the gyro will report a positive rotation rate
and the turn ratio will be negative which will the turn the robot left!
```blocks
let error = 0
brick.showPorts()
sensors.gyro2.calibrate()
while (true) {
error = sensors.gyro2.rate() * -1
motors.largeBC.steer(error, 50)
}
```
## Step 4 Run it!
Download to your brick and test out if the robot is going straight.
This kind of technique is called a proportional controller;
it corrects the inputs (motor speed) with a feedback proportional to the output (rotation rate).

View File

@@ -0,0 +1,65 @@
# Move To Color
## Introduction @fullscreen
This tutorial shows how to move the EV3 driving base until the color sensor detects a color.
Here are the building instructions for the robot:
* [EV3 driving base](https://le-www-live-s.legocdn.com/sc/media/lessons/mindstorms-ev3/building-instructions/ev3-rem-driving-base-79bebfc16bd491186ea9c9069842155e.pdf)
* [Color sensor down](https://le-www-live-s.legocdn.com/sc/media/lessons/mindstorms-ev3/building-instructions/ev3-rem-color-sensor-down-driving-base-d30ed30610c3d6647d56e17bc64cf6e2.pdf)
## Step 1 Run code on button pressed
Drag ``||brick:on button pressed||`` block so that your code starts when the enter button is pressed (and not at the start). We are also using the ``||brick:show string||``
message easily diagnose if the program does not work.
```blocks
brick.buttonEnter.onEvent(ButtonEvent.Pressed, function () {
brick.showString("started", 1)
})
```
## Step 2 Turn on the motors
Drag a ``||motors:steer motors B+C||`` block under the button pressed event. This will turn on both motors.
```blocks
brick.buttonEnter.onEvent(ButtonEvent.Pressed, function () {
brick.showString("started", 1)
motors.largeBC.steer(0, 50)
})
```
## Step 3 Pause until color
Drag a ``||sensors:pause until color detected||`` block after the steer and select the color you want to detect. This block will stop the program until the color is detected.
```blocks
brick.buttonEnter.onEvent(ButtonEvent.Pressed, function () {
brick.showString("started", 1)
motors.largeBC.steer(0, 50)
brick.showString("looking for red", 1)
sensors.color3.pauseUntilColorDetected(ColorSensorColor.Red)
})
```
### Step 4 Stop the motors!
Once the color is detected, the program will continue to run blocks. Drag a ``||motors:stop B+C motor||`` so that both motors stop.
```blocks
brick.buttonEnter.onEvent(ButtonEvent.Pressed, function () {
brick.showString("started", 1)
motors.largeBC.steer(0, 50)
brick.showString("looking for red", 1)
sensors.color3.pauseUntilColorDetected(ColorSensorColor.Red)
brick.showString("stop", 1)
motors.largeBC.stop()
})
```
## Step 5
Download your program to your brick and try it out!

View File

@@ -0,0 +1,33 @@
# Pause On Start
## Introduction @unplugged
Sometimes you don't want your program to run right away... you can use a button to wait before moving the motors.
## Step 1
Let's start by showing an image on the screen so the user knows that the robot is ready and waiting.
```blocks
brick.showImage(images.informationStop1)
```
## Step 2
Drag the ``||brick:pause until enter pressed||`` button to wait for the user to press the Enter button.
```blocks
brick.showImage(images.informationStop1)
brick.buttonEnter.pauseUntil(ButtonEvent.Pressed)
```
## Step 3
Add all the motor and sensor code you want after those blocks!
```blocks
brick.showImage(images.informationStop1)
brick.buttonEnter.pauseUntil(ButtonEvent.Pressed)
brick.showImage(images.expressionsBigSmile)
motors.largeBC.tank(50, 50, 1, MoveUnit.Seconds)
```

View File

@@ -0,0 +1,43 @@
# Turn With Gyro
## Introduction @fullscreen
Use the gyro to measure how much the robot is turning, regardless if your wheels are slipping.
## Step 1 Calibrate
Add the ``||sensors:calibrate gyro||`` block to make sure your gyro is ready to use.
```blocks
sensors.gyro2.calibrate()
```
## Step 2 Turn
Use motor blocks to make the robot turn. Don't go too fast!
```blocks
sensors.gyro2.calibrate()
motors.largeBC.steer(200, 20)
```
## Step 3 Pause for turn
Use the ``||sensors:pause until rotated||`` block to wait until the desired amount of rotation has occured.
```blocks
sensors.gyro2.calibrate()
motors.largeBC.steer(200, 20)
sensors.gyro2.pauseUntilRotated(90)
```
## Step 4 Stop
Stop the motors!
```blocks
sensors.gyro2.calibrate()
motors.largeBC.steer(200, 20)
sensors.gyro2.pauseUntilRotated(90)
motors.stopAll()
```

View File

@@ -2,72 +2,236 @@
/// <reference path="../node_modules/pxt-core/built/pxtsim.d.ts"/> /// <reference path="../node_modules/pxt-core/built/pxtsim.d.ts"/>
import UF2 = pxtc.UF2; import UF2 = pxtc.UF2;
import { Ev3Wrapper } from "./wrap";
export let ev3: pxt.editor.Ev3Wrapper export let ev3: Ev3Wrapper;
let confirmAsync: (options: any) => Promise<number>;
export function setConfirmAsync(cf: (options: any) => Promise<number>) {
confirmAsync = cf;
}
export function debug() { export function debug() {
return initAsync() return initHidAsync()
.then(w => w.downloadFileAsync("/tmp/dmesg.txt", v => console.log(pxt.Util.uint8ArrayToString(v)))) .then(w => w.downloadFileAsync("/tmp/dmesg.txt", v => console.log(pxt.Util.uint8ArrayToString(v))))
} }
// Web Serial API https://wicg.github.io/serial/
// chromium bug https://bugs.chromium.org/p/chromium/issues/detail?id=884928
// Under experimental features in Chrome Desktop 77+
enum ParityType {
"none",
"even",
"odd",
"mark",
"space"
}
declare interface SerialOptions {
baudrate?: number;
databits?: number;
stopbits?: number;
parity?: ParityType;
buffersize?: number;
rtscts?: boolean;
xon?: boolean;
xoff?: boolean;
xany?: boolean;
}
type SerialPortInfo = pxt.Map<string>;
type SerialPortRequestOptions = any;
declare class SerialPort {
open(options?: SerialOptions): Promise<void>;
close(): void;
readonly readable: any;
readonly writable: any;
//getInfo(): SerialPortInfo;
}
declare interface Serial extends EventTarget {
onconnect: any;
ondisconnect: any;
getPorts(): Promise<SerialPort[]>
requestPort(options: SerialPortRequestOptions): Promise<SerialPort>;
}
class WebSerialPackageIO implements pxt.HF2.PacketIO {
onData: (v: Uint8Array) => void;
onError: (e: Error) => void;
onEvent: (v: Uint8Array) => void;
onSerial: (v: Uint8Array, isErr: boolean) => void;
sendSerialAsync: (buf: Uint8Array, useStdErr: boolean) => Promise<void>;
private _reader: any;
private _writer: any;
constructor(private port: SerialPort, private options: SerialOptions) {
}
async readSerialAsync() {
this._reader = this.port.readable.getReader();
let buffer: Uint8Array;
const reader = this._reader;
while (reader === this._reader) { // will change if we recycle the connection
const { done, value } = await this._reader.read()
if (!buffer) buffer = value;
else { // concat
let tmp = new Uint8Array(buffer.length + value.byteLength)
tmp.set(buffer, 0)
tmp.set(value, buffer.length)
buffer = tmp;
}
if (buffer && buffer.length >= 6) {
this.onData(new Uint8Array(buffer));
buffer = undefined;
}
}
}
static isSupported(): boolean {
return !!(<any>navigator).serial;
}
static async mkPacketIOAsync(): Promise<pxt.HF2.PacketIO> {
const serial = (<any>navigator).serial;
if (serial) {
try {
const requestOptions: SerialPortRequestOptions = {};
const port = await serial.requestPort(requestOptions);
const options: SerialOptions = {
baudrate: 460800,
buffersize: 4096
};
return new WebSerialPackageIO(port, options);
} catch (e) {
console.log(`connection error`, e)
}
}
throw new Error("could not open serial port");
}
error(msg: string): any {
console.error(msg);
throw new Error(lf("error on brick ({0})", msg))
}
private openAsync() {
console.log(`serial: opening port`)
if (!!this._reader) return Promise.resolve();
this._reader = undefined;
this._writer = undefined;
return this.port.open(this.options)
.then(() => {
this.readSerialAsync();
return Promise.resolve();
});
}
private closeAsync() {
console.log(`serial: closing port`);
this.port.close();
this._reader = undefined;
this._writer = undefined;
return Promise.delay(500);
}
reconnectAsync(): Promise<void> {
return this.openAsync();
}
disconnectAsync(): Promise<void> {
return this.closeAsync();
}
sendPacketAsync(pkt: Uint8Array): Promise<void> {
if (!this._writer)
this._writer = this.port.writable.getWriter();
return this._writer.write(pkt);
}
}
function hf2Async() { function hf2Async() {
return pxt.HF2.mkPacketIOAsync() const pktIOAsync: Promise<pxt.HF2.PacketIO> = useWebSerial
.then(h => { ? WebSerialPackageIO.mkPacketIOAsync() : pxt.HF2.mkPacketIOAsync()
let w = new pxt.editor.Ev3Wrapper(h) return pktIOAsync.then(h => {
let w = new Ev3Wrapper(h)
ev3 = w ev3 = w
return w.reconnectAsync(true) return w.reconnectAsync(true)
.then(() => w) .then(() => w)
}) })
} }
let noHID = false let useHID = false;
let useWebSerial = false;
let initPromise: Promise<pxt.editor.Ev3Wrapper> export function initAsync(): Promise<void> {
export function initAsync() {
if (initPromise)
return initPromise
let canHID = false
if (pxt.U.isNodeJS) { if (pxt.U.isNodeJS) {
// doesn't seem to work ATM // doesn't seem to work ATM
canHID = false useHID = false
} else { } else {
const forceHexDownload = /forceHexDownload/i.test(window.location.href); const nodehid = /nodehid/i.test(window.location.href);
if (pxt.Cloud.isLocalHost() && pxt.Cloud.localToken && !forceHexDownload) if (pxt.Cloud.isLocalHost() && pxt.Cloud.localToken && nodehid)
canHID = true useHID = true;
} }
if (noHID) if (WebSerialPackageIO.isSupported())
canHID = false pxt.tickEvent("webserial.supported");
if (canHID) { return Promise.resolve();
initPromise = hf2Async() }
export function canUseWebSerial() {
return WebSerialPackageIO.isSupported();
}
export function enableWebSerialAsync() {
initPromise = undefined;
useWebSerial = WebSerialPackageIO.isSupported();
useHID = useWebSerial;
if (useWebSerial)
return initHidAsync().then(() => { });
else return Promise.resolve();
}
function cleanupAsync() {
if (ev3) {
console.log('cleanup previous port')
return ev3.disconnectAsync()
.catch(e => { })
.finally(() => { ev3 = undefined; });
}
return Promise.resolve();
}
let initPromise: Promise<Ev3Wrapper>
function initHidAsync() { // needs to run within a click handler
if (initPromise)
return initPromise
if (useHID) {
initPromise = cleanupAsync()
.then(() => hf2Async())
.catch(err => { .catch(err => {
console.error(err);
initPromise = null initPromise = null
noHID = true useHID = false;
return Promise.reject(err) useWebSerial = false;
return Promise.reject(err);
}) })
} else { } else {
noHID = true useHID = false
useWebSerial = false;
initPromise = Promise.reject(new Error("no HID")) initPromise = Promise.reject(new Error("no HID"))
} }
return initPromise;
return initPromise
} }
// this comes from aux/pxt.lms // this comes from aux/pxt.lms
const fspath = "../prjs/BrkProg_SAVE/"
const rbfTemplate = ` const rbfTemplate = `
4c45474f580000006d000100000000001c000000000000000e000000821b038405018130813e8053 4c45474f580000006d000100000000001c000000000000000e000000821b038405018130813e8053
74617274696e672e2e2e0084006080XX00448581644886488405018130813e80427965210084000a 74617274696e672e2e2e0084006080XX00448581644886488405018130813e80427965210084000a
` `
export function deployCoreAsync(resp: pxtc.CompileResult) { export function deployCoreAsync(resp: pxtc.CompileResult) {
let w: pxt.editor.Ev3Wrapper
let filename = resp.downloadFileBaseName || "pxt" let filename = resp.downloadFileBaseName || "pxt"
filename = filename.replace(/^lego-/, "") filename = filename.replace(/^lego-/, "")
let fspath = "../prjs/BrkProg_SAVE/"
let elfPath = fspath + filename + ".elf" let elfPath = fspath + filename + ".elf"
let rbfPath = fspath + filename + ".rbf" let rbfPath = fspath + filename + ".rbf"
@@ -107,27 +271,54 @@ export function deployCoreAsync(resp: pxtc.CompileResult) {
return Promise.resolve(); return Promise.resolve();
} }
if (noHID) return saveUF2Async() if (!useHID) return saveUF2Async()
return initAsync() pxt.tickEvent("webserial.flash");
let w: Ev3Wrapper;
return initHidAsync()
.then(w_ => { .then(w_ => {
w = w_ w = w_
if (w.isStreaming) if (w.isStreaming)
pxt.U.userError("please stop the program first") pxt.U.userError("please stop the program first")
return w.stopAsync() return w.reconnectAsync(false)
.catch(e => {
// user easily forgets to stop robot
if (confirmAsync)
return confirmAsync({
header: lf("Bluetooth download failed..."),
htmlBody:
`<ul>
<li>${lf("Make sure to stop your program or exit portview on the EV3.")}</li>
<li>${lf("Check your battery level.")}</li>
<li>${lf("Close EV3 LabView or other MakeCode editor tabs.")}
</ul>`,
hasCloseIcon: true,
hideCancel: true,
hideAgree: false,
agreeLbl: lf("Try again"),
}).then(() => w.disconnectAsync())
.then(() => Promise.delay(1000))
.then(() => w.reconnectAsync());
// nothing we can do
return Promise.reject(e);
}) })
})
.then(() => w.stopAsync())
.then(() => w.rmAsync(elfPath)) .then(() => w.rmAsync(elfPath))
.then(() => w.flashAsync(elfPath, UF2.readBytes(origElfUF2, 0, origElfUF2.length * 256))) .then(() => w.flashAsync(elfPath, UF2.readBytes(origElfUF2, 0, origElfUF2.length * 256)))
.then(() => w.flashAsync(rbfPath, rbfBIN)) .then(() => w.flashAsync(rbfPath, rbfBIN))
.then(() => w.runAsync(rbfPath)) .then(() => w.runAsync(rbfPath))
.then(() => Promise.delay(500))
.then(() => { .then(() => {
pxt.tickEvent("webserial.success");
return w.disconnectAsync() return w.disconnectAsync()
//return Promise.delay(1000).then(() => w.dmesgAsync()) //return Promise.delay(1000).then(() => w.dmesgAsync())
}).catch(e => { }).catch(e => {
// if we failed to initalize, retry pxt.tickEvent("webserial.fail");
if (noHID) useHID = false;
return saveUF2Async() useWebSerial = false;
else // if we failed to initalize, tell the user to retry
return Promise.reject(e) return Promise.reject(e)
}) })
} }

View File

@@ -1,28 +1,28 @@
/// <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"/> /// <reference path="../node_modules/pxt-core/built/pxtsim.d.ts"/>
import { deployCoreAsync, initAsync } from "./deploy"; import { deployCoreAsync, initAsync, canUseWebSerial, enableWebSerialAsync, setConfirmAsync } from "./deploy";
let bluetoothDialogShown = false;
pxt.editor.initExtensionsAsync = function (opts: pxt.editor.ExtensionOptions): Promise<pxt.editor.ExtensionResult> { pxt.editor.initExtensionsAsync = function (opts: pxt.editor.ExtensionOptions): Promise<pxt.editor.ExtensionResult> {
const projectView = opts.projectView;
pxt.debug('loading pxt-ev3 target extensions...') pxt.debug('loading pxt-ev3 target extensions...')
function enableWebSerialAndCompileAsync() {
return enableWebSerialAsync()
.then(() => Promise.delay(500))
.then(() => projectView.compile());
}
const res: pxt.editor.ExtensionResult = { const res: pxt.editor.ExtensionResult = {
deployCoreAsync, deployCoreAsync,
showUploadInstructionsAsync: (fn: string, url: string, confirmAsync: (options: any) => Promise<number>) => { showUploadInstructionsAsync: (fn: string, url: string, confirmAsync: (options: any) => Promise<number>) => {
let resolve: (thenableOrResult?: void | PromiseLike<void>) => void; setConfirmAsync(confirmAsync);
let reject: (error: any) => void;
const deferred = new Promise<void>((res, rej) => {
resolve = res;
reject = rej;
});
const boardName = pxt.appTarget.appTheme.boardName || "???";
const boardDriveName = pxt.appTarget.appTheme.driveDisplayName || pxt.appTarget.compile.driveName || "???";
// https://msdn.microsoft.com/en-us/library/cc848897.aspx // https://msdn.microsoft.com/en-us/library/cc848897.aspx
// "For security reasons, data URIs are restricted to downloaded resources. // "For security reasons, data URIs are restricted to downloaded resources.
// Data URIs cannot be used for navigation, for scripting, or to populate frame or iframe elements" // Data URIs cannot be used for navigation, for scripting, or to populate frame or iframe elements"
const downloadAgain = !pxt.BrowserUtils.isIE() && !pxt.BrowserUtils.isEdge(); const downloadAgain = !pxt.BrowserUtils.isIE() && !pxt.BrowserUtils.isEdge();
const docUrl = pxt.appTarget.appTheme.usbDocs; const docUrl = pxt.appTarget.appTheme.usbDocs;
const saveAs = pxt.BrowserUtils.hasSaveAs();
const htmlBody = ` const htmlBody = `
<div class="ui grid stackable"> <div class="ui grid stackable">
@@ -84,7 +84,38 @@ pxt.editor.initExtensionsAsync = function (opts: pxt.editor.ExtensionOptions): P
hideAgree: false, hideAgree: false,
agreeLbl: lf("I got it"), agreeLbl: lf("I got it"),
className: 'downloaddialog', className: 'downloaddialog',
buttons: [downloadAgain ? { buttons: [canUseWebSerial() ? {
label: lf("Bluetooth"),
icon: "bluetooth",
className: "bluetooth focused",
onclick: () => {
pxt.tickEvent("bluetooth.enable");
if (bluetoothDialogShown) {
enableWebSerialAndCompileAsync().done();
} else {
bluetoothDialogShown = true;
confirmAsync({
header: lf("Bluetooth pairing"),
hasCloseIcon: true,
hideCancel: true,
buttons: [{
label: lf("Help"),
icon: "question circle",
className: "lightgrey",
url: "/bluetooth"
}],
htmlBody: `<p>
${lf("You will be prompted to select a serial port.")}
${pxt.BrowserUtils.isWindows()
? lf("Look for 'Standard Serial over Bluetooth link'.")
: lf("Loop for 'cu.EV3-SerialPort'.")}
${lf("If you have paired multiple EV3, you might have to try out multiple ports until you find the correct one.")}
</p>
`
}).then(() => enableWebSerialAndCompileAsync())
}
}
} : undefined, downloadAgain ? {
label: fn, label: fn,
icon: "download", icon: "download",
className: "lightgrey focused", className: "lightgrey focused",

View File

@@ -1,26 +1,30 @@
namespace pxt.editor { /**
import HF2 = pxt.HF2 * See https://www.lego.com/cdn/cs/set/assets/blt6879b00ae6951482/LEGO_MINDSTORMS_EV3_Communication_Developer_Kit.pdf
import U = pxt.U * https://github.com/mindboards/ev3sources/blob/master/lms2012/lms2012/source/bytecodes.h#L146
*/
import HF2 = pxt.HF2
import U = pxt.U
function log(msg: string) { function log(msg: string) {
pxt.log("EWRAP: " + msg) pxt.log("serial: " + msg)
} }
export interface DirEntry { export interface DirEntry {
name: string; name: string;
md5?: string; md5?: string;
size?: number; size?: number;
} }
const runTemplate = "C00882010084XX0060640301606400" const runTemplate = "C00882010084XX0060640301606400"
const usbMagic = 0x3d3f const usbMagic = 0x3d3f
const DIRECT_COMMAND_NO_REPLY = 0x80
export class Ev3Wrapper { export class Ev3Wrapper {
msgs = new U.PromiseBuffer<Uint8Array>() msgs = new U.PromiseBuffer<Uint8Array>()
private cmdSeq = U.randomUint32() & 0xffff; private cmdSeq = U.randomUint32() & 0xffff;
private lock = new U.PromiseQueue(); private lock = new U.PromiseQueue();
isStreaming = false; isStreaming = false;
dataDump = false; dataDump = /talkdbg=1/.test(window.location.href);
constructor(public io: pxt.HF2.PacketIO) { constructor(public io: pxt.HF2.PacketIO) {
io.onData = buf => { io.onData = buf => {
@@ -30,8 +34,8 @@ namespace pxt.editor {
let payload = buf.slice(8) let payload = buf.slice(8)
if (code == 1) { if (code == 1) {
let str = U.uint8ArrayToString(payload) let str = U.uint8ArrayToString(payload)
if (Util.isNodeJS) if (U.isNodeJS)
console.log("SERIAL: " + str.replace(/\n+$/, "")) pxt.debug("SERIAL: " + str.replace(/\n+$/, ""))
else else
window.postMessage({ window.postMessage({
type: 'serial', type: 'serial',
@@ -39,7 +43,7 @@ namespace pxt.editor {
data: str data: str
}, "*") }, "*")
} else } else
console.log("Magic: " + code + ": " + U.toHex(payload)) pxt.debug("Magic: " + code + ": " + U.toHex(payload))
return return
} }
if (this.dataDump) if (this.dataDump)
@@ -90,7 +94,7 @@ namespace pxt.editor {
runAsync(path: string) { runAsync(path: string) {
let codeHex = runTemplate.replace("XX", U.toHex(U.stringToUint8Array(path))) let codeHex = runTemplate.replace("XX", U.toHex(U.stringToUint8Array(path)))
let code = U.fromHex(codeHex) let code = U.fromHex(codeHex)
let pkt = this.allocCore(2 + code.length, 0) let pkt = this.allocCore(2 + code.length, DIRECT_COMMAND_NO_REPLY)
HF2.write16(pkt, 5, 0x0800) HF2.write16(pkt, 5, 0x0800)
U.memcpy(pkt, 7, code) U.memcpy(pkt, 7, code)
log(`run ${path}`) log(`run ${path}`)
@@ -279,7 +283,4 @@ namespace pxt.editor {
log(`disconnect`); log(`disconnect`);
return this.io.disconnectAsync() return this.io.disconnectAsync()
} }
}
} }

View File

@@ -76,9 +76,10 @@ export class FieldSpeed extends Blockly.FieldSlider implements Blockly.FieldCust
}; };
setReadout_(readout: Element, value: string) { setReadout_(readout: Element, value: string) {
this.updateSpeed(parseFloat(value)); let x = parseFloat(value) || 0;
this.updateSpeed(x);
// Update reporter // Update reporter
this.reporter.textContent = `${value}%`; this.reporter.textContent = `${x}%`;
} }
private updateSpeed(speed: number) { private updateSpeed(speed: number) {

View File

@@ -23,7 +23,7 @@ export class FieldTurnRatio extends Blockly.FieldSlider implements Blockly.Field
* @constructor * @constructor
*/ */
constructor(value_: any, params: FieldTurnRatioOptions, opt_validator?: Function) { constructor(value_: any, params: FieldTurnRatioOptions, opt_validator?: Function) {
super(String(value_), '-100', '100', null, '10', 'TurnRatio', opt_validator); super(String(value_), '-200', '200', null, '10', 'TurnRatio', opt_validator);
this.params = params; this.params = params;
(this as any).sliderColor_ = '#a8aaa8'; (this as any).sliderColor_ = '#a8aaa8';
} }
@@ -76,26 +76,26 @@ export class FieldTurnRatio extends Blockly.FieldSlider implements Blockly.Field
if (!this.path_) { if (!this.path_) {
return; return;
} }
let v = goog.math.clamp(parseFloat(this.getText()), -100, 100); let v = goog.math.clamp(parseFloat(this.getText()), -200, 200);
if (isNaN(v)) { if (isNaN(v)) {
v = 0; v = 0;
} }
const x = v / 100;
const x = goog.math.clamp(parseFloat(this.getText()), -100, 100) / 100; const nx = Math.max(-1, Math.min(1, x));
const theta = x * Math.PI / 2; const theta = Math.max(nx) * Math.PI / 2;
const cx = FieldTurnRatio.HALF; const r = FieldTurnRatio.RADIUS - 6;
const cy = FieldTurnRatio.HALF - 14; let cx = FieldTurnRatio.HALF;
const gamma = Math.PI - 2 * theta; const cy = FieldTurnRatio.HALF - 22;
const r = FieldTurnRatio.RADIUS; if (Math.abs(x) > 1) {
const alpha = 0.2 + Math.abs(x) * 0.5; cx -= (x - (x > 0 ? 1 : -1)) * r / 2; // move center of circle
const x1 = 0; }
const alpha = 0.2 + Math.abs(nx) * 0.5;
const y1 = r * alpha; const y1 = r * alpha;
const y2 = r * Math.sin(Math.PI / 2 - theta); const y2 = r * Math.sin(Math.PI / 2 - theta);
const x2 = r * Math.cos(Math.PI / 2 - theta); const x2 = r * Math.cos(Math.PI / 2 - theta);
const y3 = y2 - r * alpha * Math.cos(2 * theta); const y3 = y2 - r * alpha * Math.cos(2 * theta);
const x3 = x2 - r * alpha * Math.sin(2 * theta); const x3 = x2 - r * alpha * Math.sin(2 * theta);
const d = `M ${cx} ${cy} C ${cx} ${cy - y1} ${cx + x3} ${cy - y3} ${cx + x2} ${cy - y2}`; const d = `M ${cx} ${cy} C ${cx} ${cy - y1} ${cx + x3} ${cy - y3} ${cx + x2} ${cy - y2}`;
this.path_.setAttribute('d', d); this.path_.setAttribute('d', d);

View File

@@ -0,0 +1,66 @@
/**
* Message broadcasting
*/
//% weight=70
//% color="#58AB41"
namespace broadcast {
const broadcastEventId = control.allocateNotifyEvent();
const broadcastDoneEventId = control.allocateNotifyEvent();
function normalizeId(id: number) {
// upper ids are reserved for answer
return ((id + 1) | 0) & 0xffff;
}
/**
* An enum shim
*/
//% shim=ENUM_GET
//% blockId=msg_enum_shim
//% block="$arg"
//% enumName="Messages"
//% enumMemberName="message"
//% enumPromptHint="e.g. Move, Turn, ..."
//% enumInitialMembers="message1"
//% blockHidden=1
//% enumIsHash=1
export function __messageShim(arg: number) {
// This function should do nothing, but must take in a single
// argument of type number and return a number value.
return arg;
}
/**
* Register code to run when a message is received
*/
//% block="on %id=msg_enum_shim|received"
//% blockId=broadcastonreceived draggableParameters
export function onMessageReceived(message: number, body: () => void) {
const messageid = normalizeId(message);
control.onEvent(broadcastEventId, messageid, function () {
body();
control.raiseEvent(broadcastDoneEventId, messageid);
})
}
/**
* Sends a message to activate code
*/
//% block="send %id=msg_enum_shim"
//% blockId=broadcastsend draggableParameters
export function sendMessage(message: number) {
const messageid = normalizeId(message);
control.raiseEvent(broadcastEventId, messageid);
}
/**
* Sends a message and pauses until the handler to finishes.
*/
//% block="send %id=msg_enum_shim| and pause"
//% blockId=broadcastsendpause
export function sendMessageAndPause(message: number) {
const messageid = normalizeId(message);
control.raiseEvent(broadcastEventId, messageid);
control.waitForEvent(broadcastDoneEventId, messageid);
}
}

11
libs/broadcast/pxt.json Normal file
View File

@@ -0,0 +1,11 @@
{
"name": "broadcast",
"description": "Broadcasting messages - beta",
"files": [
"broadcast.ts"
],
"public": true,
"dependencies": {
"core": "file:../core"
}
}

View File

@@ -73,13 +73,7 @@ namespace sensors {
} }
setMode(m: ColorSensorMode) { setMode(m: ColorSensorMode) {
if (m == ColorSensorMode.AmbientLightIntensity) { // don't change threshold after initialization
this.thresholdDetector.setLowThreshold(5);
this.thresholdDetector.setHighThreshold(20);
} else {
this.thresholdDetector.setLowThreshold(20);
this.thresholdDetector.setHighThreshold(80);
}
this._setMode(m) this._setMode(m)
} }
@@ -111,6 +105,9 @@ namespace sensors {
"red", "red",
"white", "white",
"brown"][this._query()]; "brown"][this._query()];
case ColorSensorMode.AmbientLightIntensity:
case ColorSensorMode.ReflectedLightIntensity:
return `${this._query()}%`;
default: default:
return this._query().toString(); return this._query().toString();
} }
@@ -179,6 +176,7 @@ namespace sensors {
//% group="Color Sensor" //% group="Color Sensor"
//% blockGap=8 //% blockGap=8
color(): ColorSensorColor { color(): ColorSensorColor {
this.poke();
this.setMode(ColorSensorMode.Color) this.setMode(ColorSensorMode.Color)
return this.getNumber(NumberFormat.UInt8LE, 0) return this.getNumber(NumberFormat.UInt8LE, 0)
} }
@@ -196,6 +194,7 @@ namespace sensors {
//% group="Color Sensor" //% group="Color Sensor"
//% blockGap=8 //% blockGap=8
rgbRaw(): number[] { rgbRaw(): number[] {
this.poke();
this.setMode(ColorSensorMode.RgbRaw); this.setMode(ColorSensorMode.RgbRaw);
return [this.getNumber(NumberFormat.UInt16LE, 0), this.getNumber(NumberFormat.UInt16LE, 2), this.getNumber(NumberFormat.UInt16LE, 4)]; return [this.getNumber(NumberFormat.UInt16LE, 0), this.getNumber(NumberFormat.UInt16LE, 2), this.getNumber(NumberFormat.UInt16LE, 4)];
} }
@@ -249,8 +248,9 @@ namespace sensors {
//% weight=87 blockGap=8 //% weight=87 blockGap=8
//% group="Color Sensor" //% group="Color Sensor"
light(mode: LightIntensityMode) { light(mode: LightIntensityMode) {
this.poke();
this.setMode(<ColorSensorMode><number>mode) this.setMode(<ColorSensorMode><number>mode)
switch(mode) { switch (mode) {
case LightIntensityMode.ReflectedRaw: case LightIntensityMode.ReflectedRaw:
return this.reflectedLightRaw(); return this.reflectedLightRaw();
default: default:
@@ -279,6 +279,7 @@ namespace sensors {
*/ */
//% //%
reflectedLightRaw(): number { reflectedLightRaw(): number {
this.poke();
this.setMode(ColorSensorMode.RefRaw); this.setMode(ColorSensorMode.RefRaw);
return this.getNumber(NumberFormat.UInt16LE, 0); return this.getNumber(NumberFormat.UInt16LE, 0);
} }

View File

@@ -55,6 +55,10 @@ namespace brick {
this._wasPressed = false this._wasPressed = false
} }
protected poke() {
}
//% hidden //% hidden
_update(curr: boolean) { _update(curr: boolean) {
if (this == null) return if (this == null) return
@@ -85,6 +89,7 @@ namespace brick {
//% group="Buttons" //% group="Buttons"
//% button.fieldEditor="brickbuttons" //% button.fieldEditor="brickbuttons"
isPressed() { isPressed() {
this.poke();
return this._isPressed return this._isPressed
} }
@@ -102,6 +107,7 @@ namespace brick {
//% group="Buttons" //% group="Buttons"
//% button.fieldEditor="brickbuttons" //% button.fieldEditor="brickbuttons"
wasPressed() { wasPressed() {
this.poke();
const r = this._wasPressed const r = this._wasPressed
this._wasPressed = false this._wasPressed = false
return r return r
@@ -144,6 +150,7 @@ namespace brick {
namespace brick { namespace brick {
let btnsMM: MMap let btnsMM: MMap
let buttons: DevButton[] let buttons: DevButton[]
let buttonPoller: sensors.internal.Poller;
export namespace internal { export namespace internal {
export function getBtnsMM() { export function getBtnsMM() {
@@ -167,7 +174,7 @@ namespace brick {
btnsMM = control.mmap("/dev/lms_ui", DAL.NUM_BUTTONS, 0) btnsMM = control.mmap("/dev/lms_ui", DAL.NUM_BUTTONS, 0)
if (!btnsMM) control.fail("no buttons?") if (!btnsMM) control.fail("no buttons?")
buttons = [] buttons = []
sensors.internal.unsafePollForChanges(50, readButtons, (prev, curr) => { buttonPoller = new sensors.internal.Poller(50, readButtons, (prev, curr) => {
for (let b of buttons) for (let b of buttons)
b._update(!!(curr & b.mask)) b._update(!!(curr & b.mask))
}) })
@@ -182,6 +189,10 @@ namespace brick {
initBtns() initBtns()
buttons.push(this) buttons.push(this)
} }
protected poke() {
buttonPoller.poke();
}
} }
initBtns() // always ON as it handles ESCAPE button initBtns() // always ON as it handles ESCAPE button

View File

@@ -1,29 +1,52 @@
namespace sensors.internal { namespace sensors.internal {
//% shim=pxt::unsafePollForChanges export class Poller {
export function unsafePollForChanges( private query: () => number;
periodMs: number, private update: (previous: number, current: number) => void;
query: () => number, public interval: number;
changeHandler: (prev: number, curr: number) => void
) {
// This is implemented in C++ without blocking the regular JS when query() is runnning
// which is generally unsafe. Query should not update globally visible state, and cannot
// call any yielding functions, like sleep().
// This is implementation for the simulator. private previousValue: number;
private currentValue: number;
private lastQuery: number; // track down the last time we did a query/update cycle
private lastPause: number; // track down the last time we pause in the sensor polling loop
control.runInParallel(() => { constructor(interval: number, query: () => number, update: (previous: number, current: number) => void) {
let prev = query() this.interval = interval | 0;
changeHandler(prev, prev) this.query = query;
while (true) { this.update = update;
pause(periodMs)
let curr = query() this.poll();
if (prev !== curr) {
changeHandler(prev, curr)
prev = curr
} }
poke(): void {
const now = control.millis();
if (now - this.lastQuery >= this.interval * 2)
this.queryAndUpdate(); // sensor poller is not allowed to run
if (now - this.lastPause >= this.interval * 5)
pause(1); // allow events to trigger
}
private queryAndUpdate() {
this.lastQuery = control.millis();
this.currentValue = this.query();
if (this.previousValue != this.currentValue) {
this.update(this.previousValue, this.currentValue);
this.previousValue = this.currentValue;
}
}
private poll() {
control.runInBackground(() => {
this.lastQuery = this.lastPause = control.millis();
this.previousValue = this.currentValue = this.query();
this.update(this.previousValue, this.currentValue);
while (true) {
this.lastPause = control.millis();
pause(this.interval);
this.queryAndUpdate();
} }
}) })
} }
}
export function bufferToString(buf: Buffer): string { export function bufferToString(buf: Buffer): string {
let s = '' let s = ''
@@ -38,6 +61,7 @@ namespace sensors.internal {
let IICMM: MMap let IICMM: MMap
let powerMM: MMap let powerMM: MMap
let devcon: Buffer let devcon: Buffer
let devPoller: Poller
let sensorInfos: SensorInfo[]; let sensorInfos: SensorInfo[];
let batteryInfo: { let batteryInfo: {
@@ -55,6 +79,7 @@ namespace sensors.internal {
connType: number connType: number
devType: number devType: number
iicid: string iicid: string
poller: Poller;
constructor(p: number) { constructor(p: number) {
this.port = p this.port = p
@@ -62,6 +87,20 @@ namespace sensors.internal {
this.devType = DAL.DEVICE_TYPE_NONE this.devType = DAL.DEVICE_TYPE_NONE
this.iicid = '' this.iicid = ''
this.sensors = [] this.sensors = []
this.poller = new Poller(25, () => this.query(), (prev, curr) => this.update(prev, curr));
}
poke() {
this.poller.poke();
}
private query() {
if (this.sensor) return this.sensor._query();
return 0;
}
private update(prev: number, curr: number) {
if (this.sensor) this.sensor._update(prev, curr)
} }
} }
@@ -82,19 +121,10 @@ namespace sensors.internal {
powerMM = control.mmap("/dev/lms_power", 2, 0) powerMM = control.mmap("/dev/lms_power", 2, 0)
unsafePollForChanges(500, devPoller = new Poller(250, () => { return hashDevices(); },
() => { return hashDevices(); },
(prev, curr) => { (prev, curr) => {
detectDevices(); detectDevices();
}); });
sensorInfos.forEach(info => {
unsafePollForChanges(50, () => {
if (info.sensor) return info.sensor._query()
return 0
}, (prev, curr) => {
if (info.sensor) info.sensor._update(prev, curr)
})
})
} }
export function getActiveSensors(): Sensor[] { export function getActiveSensors(): Sensor[] {
@@ -280,7 +310,7 @@ void cUiUpdatePower(void)
// TODO? for now assume touch // TODO? for now assume touch
sensorInfo.devType = DAL.DEVICE_TYPE_TOUCH sensorInfo.devType = DAL.DEVICE_TYPE_TOUCH
} else if (newConn == DAL.CONN_NONE || newConn == 0) { } else if (newConn == DAL.CONN_NONE || newConn == 0) {
control.dmesg(`disconnect at port ${sensorInfo.port}`) //control.dmesg(`disconnect at port ${sensorInfo.port}`)
} else { } else {
control.dmesg(`unknown connection type: ${newConn} at ${sensorInfo.port}`) control.dmesg(`unknown connection type: ${newConn} at ${sensorInfo.port}`)
} }
@@ -298,7 +328,7 @@ void cUiUpdatePower(void)
if (numChanged == 0 && nonActivated == 0) if (numChanged == 0 && nonActivated == 0)
return return
control.dmesg(`updating sensor status`) //control.dmesg(`updating sensor status`)
nonActivated = 0; nonActivated = 0;
for (const sensorInfo of sensorInfos) { for (const sensorInfo of sensorInfos) {
if (sensorInfo.devType == DAL.DEVICE_TYPE_IIC_UNKNOWN) { if (sensorInfo.devType == DAL.DEVICE_TYPE_IIC_UNKNOWN) {
@@ -337,6 +367,11 @@ void cUiUpdatePower(void)
this.markUsed(); this.markUsed();
} }
poke() {
if (this.isActive())
sensorInfos[this._port].poke();
}
markUsed() { markUsed() {
sensors.__sensorUsed(this._port, this._deviceType()); sensors.__sensorUsed(this._port, this._deviceType());
} }
@@ -779,10 +814,10 @@ namespace sensors {
export class ThresholdDetector { export class ThresholdDetector {
public id: number; public id: number;
public min: number; private min: number;
public max: number; private max: number;
public lowThreshold: number; private lowThreshold: number;
public highThreshold: number; private highThreshold: number;
public level: number; public level: number;
public state: ThresholdState; public state: ThresholdState;

26
libs/core/integrator.ts Normal file
View File

@@ -0,0 +1,26 @@
namespace control {
export class EulerIntegrator {
public value: number;
private t: number;
private v: number;
constructor() {
this.reset();
}
public integrate(derivative: number): void {
let now = control.millis();
let dt = (now -this.t) / 1000.0;
this.value += dt * (this.v + derivative) / 2;
this.t = now;
this.v = derivative;
}
public reset() {
this.value = 0;
this.v = 0;
this.t = control.millis();
}
}
}

View File

@@ -446,11 +446,6 @@ static void runPoller(Thread *thr) {
// disposeThread(thr); // disposeThread(thr);
} }
//%
void unsafePollForChanges(int ms, Action query, Action handler) {
setupThread(handler, 0, runPoller, query, fromInt(ms));
}
uint32_t afterProgramPage() { uint32_t afterProgramPage() {
return 0; return 0;
} }
@@ -529,6 +524,7 @@ void stopProgram() {
} }
extern "C" void target_reset() { extern "C" void target_reset() {
pthread_mutex_trylock(&execMutex);
stopMotors(); stopMotors();
stopProgram(); stopProgram();
if (lmsPid) if (lmsPid)

View File

@@ -62,7 +62,7 @@ namespace motors {
motorMM = control.mmap("/dev/lms_motor", MotorDataOff.Size * DAL.NUM_OUTPUTS, 0) motorMM = control.mmap("/dev/lms_motor", MotorDataOff.Size * DAL.NUM_OUTPUTS, 0)
if (!motorMM) control.fail("no motor file") if (!motorMM) control.fail("no motor file")
resetAllMotors() resetAll()
const buf = output.createBuffer(1) const buf = output.createBuffer(1)
buf[0] = DAL.opProgramStart buf[0] = DAL.opProgramStart
@@ -118,20 +118,25 @@ namespace motors {
* Stops all motors * Stops all motors
*/ */
//% blockId=motorStopAll block="stop all motors" //% blockId=motorStopAll block="stop all motors"
//% weight=1 //% weight=2
//% group="Move" //% group="Move"
//% help=motors/stop-all //% help=motors/stop-all
export function stopAll() { export function stopAll() {
const b = mkCmd(Output.ALL, DAL.opOutputStop, 0) const b = mkCmd(Output.ALL, DAL.opOutputStop, 0)
writePWM(b) writePWM(b);
pause(1);
} }
/** /**
* Resets all motors * Resets all motors
*/ */
//% blockId=motorResetAll block="reset all motors"
//% weight=1
//% group="Move" //% group="Move"
export function resetAllMotors() { //% help=motors/reset-all
export function resetAll() {
reset(Output.ALL) reset(Output.ALL)
pause(1);
} }
interface MoveSchedule { interface MoveSchedule {
@@ -259,6 +264,9 @@ namespace motors {
// allow 500ms for robot to settle // allow 500ms for robot to settle
if (this._brake && this._brakeSettleTime > 0) if (this._brake && this._brakeSettleTime > 0)
pause(this._brakeSettleTime); pause(this._brakeSettleTime);
else {
pause(1);
}
} }
protected pauseOnRun(stepsOrTime: number) { protected pauseOnRun(stepsOrTime: number) {
@@ -267,6 +275,8 @@ namespace motors {
this.pauseUntilReady(); this.pauseUntilReady();
// allow robot to settle // allow robot to settle
this.settle(); this.settle();
} else {
pause(1);
} }
} }
@@ -294,9 +304,17 @@ namespace motors {
case MoveUnit.Rotations: case MoveUnit.Rotations:
scale = 360; scale = 360;
r.useSteps = true; r.useSteps = true;
if (r.steps[1] < 0) {
r.speed = -r.speed;
r.steps[1] = -r.steps[1];
}
break; break;
case MoveUnit.Degrees: case MoveUnit.Degrees:
r.useSteps = true; r.useSteps = true;
if (r.steps[1] < 0) {
r.speed = -r.speed;
r.steps[1] = -r.steps[1];
}
break; break;
case MoveUnit.Seconds: case MoveUnit.Seconds:
scale = 1000; scale = 1000;
@@ -334,6 +352,7 @@ namespace motors {
// special: 0 is infinity // special: 0 is infinity
if (schedule.steps[0] + schedule.steps[1] + schedule.steps[2] == 0) { if (schedule.steps[0] + schedule.steps[1] + schedule.steps[2] == 0) {
this._run(schedule.speed); this._run(schedule.speed);
pause(1);
return; return;
} }
@@ -494,7 +513,7 @@ namespace motors {
*/ */
//% blockId=motorPauseUntilRead block="pause until %motor|ready" //% blockId=motorPauseUntilRead block="pause until %motor|ready"
//% motor.fieldEditor="motors" //% motor.fieldEditor="motors"
//% weight=90 //% weight=90 blockGap=8
//% group="Move" //% group="Move"
pauseUntilReady(timeOut?: number) { pauseUntilReady(timeOut?: number) {
pauseUntil(() => this.isReady(), timeOut); pauseUntil(() => this.isReady(), timeOut);
@@ -602,6 +621,33 @@ namespace motors {
toString(): string { toString(): string {
return `${this._large ? "" : "M"}${this._portName} ${this.speed()}% ${this.angle()}>`; return `${this._large ? "" : "M"}${this._portName} ${this.speed()}% ${this.angle()}>`;
} }
/**
* Pauses the program until the motor is stalled.
*/
//% blockId=motorPauseUntilStall block="pause until %motor|stalled"
//% motor.fieldEditor="motors"
//% weight=89
//% group="Move"
//% help=motors/motor/pause-until-stalled
pauseUntilStalled(timeOut?: number): void {
// let it start
pause(50);
let previous = this.angle();
let stall = 0;
pauseUntil(() => {
let current = this.angle();
if (Math.abs(current - previous) < 1) {
if (stall++ > 2) {
return true; // not moving
}
} else {
stall = 0;
previous = current;
}
return false;
}, timeOut)
}
} }
//% whenUsed fixedInstance block="large motor A" jres=icons.portA //% whenUsed fixedInstance block="large motor A" jres=icons.portA
@@ -697,7 +743,7 @@ namespace motors {
this.init(); this.init();
speed = Math.clamp(-100, 100, speed >> 0); speed = Math.clamp(-100, 100, speed >> 0);
if (!speed) { if (!speed) {
stop(this._port, this._brake); this.stop();
return; return;
} }
@@ -706,10 +752,18 @@ namespace motors {
let stepsOrTime: number; let stepsOrTime: number;
switch (unit) { switch (unit) {
case MoveUnit.Rotations: case MoveUnit.Rotations:
if (value < 0) {
value = -value;
speed = -speed;
}
stepsOrTime = (value * 360) >> 0; stepsOrTime = (value * 360) >> 0;
useSteps = true; useSteps = true;
break; break;
case MoveUnit.Degrees: case MoveUnit.Degrees:
if (value < 0) {
value = -value;
speed = -speed;
}
stepsOrTime = value >> 0; stepsOrTime = value >> 0;
useSteps = true; useSteps = true;
break; break;

View File

@@ -25,7 +25,8 @@
"dal.d.ts", "dal.d.ts",
"icons.jres", "icons.jres",
"ns.ts", "ns.ts",
"platform.h" "platform.h",
"integrator.ts"
], ],
"testFiles": [ "testFiles": [
"test.ts" "test.ts"

View File

@@ -1,6 +1,6 @@
# calibrate # calibrate
Reset the zero reference for the gyro to current position of the brick. Detects if the gyro is drifting and performs a full reset if needed.
```sig ```sig
sensors.gyro2.calibrate() sensors.gyro2.calibrate()

View File

@@ -0,0 +1,37 @@
# compute Drift
Called when the sensor is completely still, computes the current rate drift
```sig
sensors.gyro2.computeDrift()
```
The gyroscope sensor is subject to rate drifting. This means that the measurement reported by the sensor is off by a few degrees per second over time: it is drifting.
To counter the effect of drifting, call the ``||sensors:compute drift||`` block when the sensor is still to compute the current drift. The rate meansurements will automatically be corrected based on that drift.
## Example
This example uses a gyro sensor to
```blocks
let error = 0
sensors.gyro2.computeDrift()
while (sensors.color3.color() != ColorSensorColor.White) {
error = sensors.gyro2.rate() * -1
motors.largeBC.steer(error, 50)
}
motors.stopAll()
pause(1000)
sensors.gyro2.computeDrift()
while (sensors.color3.color() != ColorSensorColor.Blue) {
error = sensors.gyro2.rate() * -1
motors.largeBC.steer(error, 50)
}
motors.stopAll()
```
## See Also
[rate](/reference/sensors/gyro/rate),
[compute drift](/reference/sensors/gyro/compute-drift)

View File

@@ -0,0 +1,39 @@
# drift
Get the computed rate drift
```sig
sensors.gyro2.drift()
```
The gyroscope sensor is subject to rate drifting. This means that the measurement reported by the sensor is off by a few degrees per second over time: it is drifting.
To counter the effect of drifting, call the ``||sensors:compute drift||`` block when the sensor is still to compute the current drift. The rate meansurements will automatically be corrected based on that drift.
## Example
This example uses a gyro sensor to drive straight until while color is detected.
The robot is stopped, the drift is computed and another movement is done.
```blocks
let error = 0
sensors.gyro2.computeDrift()
while (sensors.color3.color() != ColorSensorColor.White) {
error = sensors.gyro2.rate() * -1
motors.largeBC.steer(error, 50)
}
motors.stopAll()
pause(1000)
sensors.gyro2.computeDrift()
while (sensors.color3.color() != ColorSensorColor.Blue) {
error = sensors.gyro2.rate() * -1
motors.largeBC.steer(error, 50)
}
motors.stopAll()
```
## See Also
[rate](/reference/sensors/gyro/rate),
[compute drift](/reference/sensors/gyro/compute-drift)

View File

@@ -0,0 +1,21 @@
# Pause Until Rotated
Pauses the program until the gyro sensors detect that the desired amount of rotation
has been acheived.
```
sensors.gyro2.pauseUntilRotated(90)
```
## Example
This program performs a square turn left, then right.
```blocks
sensors.gyro2.calibrate()
motors.largeBC.steer(200, 10)
sensors.gyro2.pauseUntilRotated(90)
motors.largeBC.steer(-200, 10)
sensors.gyro2.pauseUntilRotated(-90)
motors.largeBC.stop()
```

View File

@@ -7,14 +7,15 @@ const enum GyroSensorMode {
namespace sensors { namespace sensors {
//% fixedInstances //% fixedInstances
export class GyroSensor extends internal.UartSensor { export class GyroSensor extends internal.UartSensor {
private calibrating: boolean; private _calibrating: boolean;
private _drift: number; private _drift: number;
private _driftCorrection: boolean; private _angle: control.EulerIntegrator;
constructor(port: number) { constructor(port: number) {
super(port) super(port)
this.calibrating = false; this._calibrating = false;
this._drift = 0; this._drift = 0;
this._driftCorrection = false; this._angle = new control.EulerIntegrator();
this._setMode(GyroSensorMode.Rate);
this.setMode(GyroSensorMode.Rate); this.setMode(GyroSensorMode.Rate);
} }
@@ -23,13 +24,17 @@ namespace sensors {
} }
_query(): number { _query(): number {
return this.getNumber(NumberFormat.Int16LE, 0); const v = this.getNumber(NumberFormat.Int16LE, 0);
this._angle.integrate(v - this._drift);
return v;
} }
setMode(m: GyroSensorMode) { setMode(m: GyroSensorMode) {
if (m == GyroSensorMode.Rate && this.mode != m) // decrecated
this._drift = 0; }
this._setMode(m)
isCalibrating(): boolean {
return this._calibrating;
} }
/** /**
@@ -45,11 +50,11 @@ namespace sensors {
//% weight=64 blockGap=8 //% weight=64 blockGap=8
//% group="Gyro Sensor" //% group="Gyro Sensor"
angle(): number { angle(): number {
if (this.calibrating) this.poke();
pauseUntil(() => !this.calibrating, 2000); if (this._calibrating)
pauseUntil(() => !this._calibrating, 2000);
this.setMode(GyroSensorMode.Angle); return Math.round(this._angle.value);
return this._query();
} }
/** /**
@@ -65,21 +70,14 @@ namespace sensors {
//% weight=65 blockGap=8 //% weight=65 blockGap=8
//% group="Gyro Sensor" //% group="Gyro Sensor"
rate(): number { rate(): number {
if (this.calibrating) this.poke();
pauseUntil(() => !this.calibrating, 2000); if (this._calibrating)
pauseUntil(() => !this._calibrating, 2000);
this.setMode(GyroSensorMode.Rate); return this._query() - this._drift;
let curr = this._query();
if (Math.abs(curr) < 4 && this._driftCorrection) {
const p = 0.01;
this._drift = (1 - p) * this._drift + p * curr;
curr = Math.round(curr - this._drift);
}
return curr;
} }
/** /**
* Forces a calibration of the with light progress indicators. * Detects if calibration is necessary and performs a full reset, drift computation.
* Must be called when the sensor is completely still. * Must be called when the sensor is completely still.
*/ */
//% help=sensors/gyro/calibrate //% help=sensors/gyro/calibrate
@@ -91,16 +89,30 @@ namespace sensors {
//% weight=51 blockGap=8 //% weight=51 blockGap=8
//% group="Gyro Sensor" //% group="Gyro Sensor"
calibrate(): void { calibrate(): void {
if (this.calibrating) return; // already in calibration mode if (this._calibrating) return; // already in calibration mode
const statusLight = brick.statusLight(); // save current status light const statusLight = brick.statusLight(); // save current status light
brick.setStatusLight(StatusLight.Orange); brick.setStatusLight(StatusLight.Orange);
this.calibrating = true; this._calibrating = true;
// may be triggered by a button click, // may be triggered by a button click,
// give time for robot to settle // give time for robot to settle
pause(700); pause(700);
// compute drift
this.computeDriftNoCalibration();
if (Math.abs(this.drift()) < 0.1) {
// no drift, skipping calibration
brick.setStatusLight(StatusLight.Green); // success
pause(1000);
brick.setStatusLight(statusLight); // resture previous light
// and we're done
this._angle.reset();
this._calibrating = false;
return;
}
// calibrating // calibrating
brick.setStatusLight(StatusLight.OrangePulse); brick.setStatusLight(StatusLight.OrangePulse);
@@ -109,37 +121,31 @@ namespace sensors {
// wait till sensor is live // wait till sensor is live
pauseUntil(() => this.isActive(), 7000); pauseUntil(() => this.isActive(), 7000);
// mode toggling // mode toggling
this.setMode(GyroSensorMode.Rate); this._setMode(GyroSensorMode.Rate);
this.setMode(GyroSensorMode.Angle); this._setMode(GyroSensorMode.Angle);
// switch back to the desired mode this._setMode(GyroSensorMode.Rate);
this.setMode(this.mode);
// check sensor is ready // check sensor is ready
if (!this.isActive()) { if (!this.isActive()) {
brick.setStatusLight(StatusLight.RedFlash); // didn't work brick.setStatusLight(StatusLight.RedFlash); // didn't work
pause(2000); pause(2000);
brick.setStatusLight(statusLight); // restore previous light brick.setStatusLight(statusLight); // restore previous light
this.calibrating = false; this._angle.reset();
this._calibrating = false;
return; return;
} }
// compute drift // switch to rate mode
this._drift = 0; this.computeDriftNoCalibration();
if (this._driftCorrection && this.mode == GyroSensorMode.Rate) {
const n = 100;
for (let i = 0; i < n; ++i) {
this._drift += this._query();
pause(4);
}
this._drift /= n;
}
// and done
brick.setStatusLight(StatusLight.Green); // success brick.setStatusLight(StatusLight.Green); // success
pause(1000); pause(1000);
brick.setStatusLight(statusLight); // resture previous light brick.setStatusLight(statusLight); // resture previous light
// and we're done // and we're done
this.calibrating = false; this._angle.reset();
this._calibrating = false;
} }
/** /**
@@ -151,34 +157,117 @@ namespace sensors {
//% parts="gyroscope" //% parts="gyroscope"
//% blockNamespace=sensors //% blockNamespace=sensors
//% this.fieldEditor="ports" //% this.fieldEditor="ports"
//% weight=50 //% weight=50 blockGap=8
//% group="Gyro Sensor" //% group="Gyro Sensor"
reset(): void { reset(): void {
if (this.calibrating) return; // already in calibration mode if (this._calibrating) return; // already in calibration mode
this._calibrating = true;
const statusLight = brick.statusLight(); // save current status light
brick.setStatusLight(StatusLight.Orange);
this.calibrating = true;
// send a reset command // send a reset command
super.reset(); super.reset();
this._drift = 0;
this._angle.reset();
pauseUntil(() => this.isActive(), 7000);
// check sensor is ready
if (!this.isActive()) {
brick.setStatusLight(StatusLight.RedFlash); // didn't work
pause(2000);
brick.setStatusLight(statusLight); // restore previous light
this._angle.reset();
this._calibrating = false;
return;
}
this._setMode(GyroSensorMode.Rate);
// and done // and done
this.calibrating = false; brick.setStatusLight(StatusLight.Green); // success
pause(1000);
brick.setStatusLight(statusLight); // resture previous light
// and done
this._angle.reset();
this._calibrating = false;
} }
/** /**
* Gets the computed rate drift * Gets the computed rate drift
*/ */
//% //% help=sensors/gyro/drift
//% block="**gyro** %this|drift"
//% blockId=gyroDrift
//% parts="gyroscope"
//% blockNamespace=sensors
//% this.fieldEditor="ports"
//% weight=9 blockGap=8
//% group="Gyro Sensor"
drift(): number { drift(): number {
return this._drift; return this._drift;
} }
/** /**
* Enables or disable drift correction * Computes the current sensor drift when using rate measurements.
* @param enabled
*/ */
//% //% help=sensors/gyro/compute-drift
setDriftCorrection(enabled: boolean) { //% block="compute **gyro** %this|drift"
this._driftCorrection = enabled; //% blockId=gyroComputeDrift
//% parts="gyroscope"
//% blockNamespace=sensors
//% this.fieldEditor="ports"
//% weight=10 blockGap=8
//% group="Gyro Sensor"
computeDrift() {
if (this._calibrating)
pauseUntil(() => !this._calibrating, 2000);
pause(1000); // let the robot settle
this.computeDriftNoCalibration();
}
/**
* Pauses the program until the gyro detected
* that the angle changed by the desired amount of degrees.
* @param degrees the degrees to turn
*/
//% help=sensors/gyro/pause-until-rotated
//% block="pause until **gyro** %this|rotated %degrees=rotationPicker|degrees"
//% blockId=gyroPauseUntilRotated
//% parts="gyroscope"
//% blockNamespace=sensors
//% this.fieldEditor="ports"
//% degrees.defl=90
//% weight=63
//% group="Gyro Sensor"
pauseUntilRotated(degrees: number, timeOut?: number): void {
let a = this.angle();
const end = a + degrees;
const direction = (end - a) > 0 ? 1 : -1;
pauseUntil(() => (end - this.angle()) * direction <= 0, timeOut);
}
private computeDriftNoCalibration() {
// clear drift
this._drift = 0; this._drift = 0;
const n = 10;
let d = 0;
for (let i = 0; i < n; ++i) {
d += this._query();
pause(20);
}
this._drift = d / n;
this._angle.reset();
}
_info(): string {
if (this._calibrating)
return "cal...";
let r = `${this._query()}r`;
if (this._drift != 0)
r += `-${this._drift | 0}`;
return r;
} }
} }
@@ -193,4 +282,17 @@ namespace sensors {
//% fixedInstance whenUsed block="4" jres=icons.port4 //% fixedInstance whenUsed block="4" jres=icons.port4
export const gyro4: GyroSensor = new GyroSensor(4) export const gyro4: GyroSensor = new GyroSensor(4)
/**
* Get the rotation angle field editor
* @param degrees angle in degrees, eg: 90
*/
//% blockId=rotationPicker block="%degrees"
//% blockHidden=true shim=TD_ID
//% colorSecondary="#FFFFFF"
//% degrees.fieldEditor="numberdropdown" degrees.fieldOptions.decompileLiterals=true
//% degrees.fieldOptions.data='[["30", 30], ["45", 45], ["60", 60], ["90", 90], ["180", 180], ["-30", -30], ["-45", -45], ["-60", -60], ["-90", -90], ["-180", -180]]'
export function __rotationPicker(degrees: number): number {
return degrees;
}
} }

View File

@@ -235,6 +235,7 @@ namespace sensors {
//% group="Infrared Sensor" //% group="Infrared Sensor"
//% this.fieldEditor="ports" //% this.fieldEditor="ports"
proximity(): number { proximity(): number {
this.poke();
this._setMode(InfraredSensorMode.Proximity) this._setMode(InfraredSensorMode.Proximity)
return this.getNumber(NumberFormat.UInt8LE, 0) return this.getNumber(NumberFormat.UInt8LE, 0)
} }
@@ -284,6 +285,7 @@ namespace sensors {
// TODO // TODO
private getDirectionAndDistance() { private getDirectionAndDistance() {
this.poke();
this._setMode(InfraredSensorMode.Seek) this._setMode(InfraredSensorMode.Seek)
return this.getNumber(NumberFormat.UInt16LE, this._channel * 2) return this.getNumber(NumberFormat.UInt16LE, this._channel * 2)
} }

View File

@@ -26,6 +26,7 @@ namespace brick {
None, None,
ShowLines, ShowLines,
Image, Image,
Ports,
Custom Custom
} }
let screenMode = ScreenMode.None; let screenMode = ScreenMode.None;
@@ -124,15 +125,30 @@ namespace brick {
//% help=brick/show-ports blockGap=8 //% help=brick/show-ports blockGap=8
//% weight=10 group="Screen" //% weight=10 group="Screen"
export function showPorts() { export function showPorts() {
screenMode = ScreenMode.Custom; if (screenMode == ScreenMode.Ports) return;
screenMode = ScreenMode.Ports;
renderPorts();
control.runInParallel(function() {
while(screenMode == ScreenMode.Ports) {
renderPorts();
pause(50);
}
})
}
function renderPorts() {
const col = 44; const col = 44;
const lineHeight8 = image.font8.charHeight + 2; const lineHeight8 = image.font8.charHeight + 2;
clearScreen(); clearScreen();
function scale(x: number) { function scale(x: number) {
if (Math.abs(x) > 1000) return Math.round(x / 100) / 10 + "k"; if (Math.abs(x) >= 5000) {
return ("" + (x >> 0)); const k = Math.floor(x / 1000);
const r = Math.round((x - 1000 * k) / 100);
return `${k}.${r}k`
}
return ("" + (x || 0));
} }
// motors // motors
@@ -143,7 +159,7 @@ namespace brick {
const x = i * col; const x = i * col;
screen.print("ABCD"[i], x + 2, 1 * lineHeight8, 1, image.font8) screen.print("ABCD"[i], x + 2, 1 * lineHeight8, 1, image.font8)
screen.print(`${scale(data.actualSpeed)}%`, x + 2, 3 * lineHeight8, 1, image.font8) screen.print(`${scale(data.actualSpeed)}%`, x + 2, 3 * lineHeight8, 1, image.font8)
screen.print(`${scale(data.count)}>`, x + 2, 4 * lineHeight8, 1, image.font5) screen.print(`${scale(data.count)}>`, x + 2, 4 * lineHeight8, 1, image.font8)
} }
screen.drawLine(0, 5 * lineHeight8, screen.width, 5 * lineHeight8, 1); screen.drawLine(0, 5 * lineHeight8, screen.width, 5 * lineHeight8, 1);

View File

@@ -4,9 +4,9 @@ tests.onEvent(TestEvent.RunSetUp, function() {
}) })
tests.onEvent(TestEvent.TestSetUp, function() { tests.onEvent(TestEvent.TestSetUp, function() {
motors.stopAll(); motors.stopAll();
motors.resetAllMotors(); motors.resetAll();
}) })
tests.onEvent(TestEvent.TestTearDown, function() { tests.onEvent(TestEvent.TestTearDown, function() {
motors.stopAll(); motors.stopAll();
motors.resetAllMotors(); motors.resetAll();
}) })

View File

@@ -73,6 +73,7 @@ namespace sensors {
//% weight=81 blockGap=8 //% weight=81 blockGap=8
//% group="Touch Sensor" //% group="Touch Sensor"
isPressed() { isPressed() {
this.poke();
return this.button.isPressed(); return this.button.isPressed();
} }
@@ -90,6 +91,7 @@ namespace sensors {
//% weight=81 //% weight=81
//% group="Touch Sensor" //% group="Touch Sensor"
wasPressed() { wasPressed() {
this.poke();
return this.button.wasPressed(); return this.button.wasPressed();
} }
} }

View File

@@ -84,6 +84,7 @@ namespace sensors {
//% weight=65 //% weight=65
//% group="Ultrasonic Sensor" //% group="Ultrasonic Sensor"
distance(): number { distance(): number {
this.poke();
// it supposedly also has an inch mode, but we stick to cm // it supposedly also has an inch mode, but we stick to cm
this._setMode(0) this._setMode(0)
return this._query(); return this._query();

View File

@@ -1,6 +1,6 @@
{ {
"name": "pxt-ev3", "name": "pxt-ev3",
"version": "1.2.1", "version": "1.2.22",
"description": "LEGO MINDSTORMS EV3 for Microsoft MakeCode", "description": "LEGO MINDSTORMS EV3 for Microsoft MakeCode",
"private": false, "private": false,
"keywords": [ "keywords": [
@@ -40,7 +40,7 @@
}, },
"dependencies": { "dependencies": {
"pxt-common-packages": "0.23.61", "pxt-common-packages": "0.23.61",
"pxt-core": "4.0.9" "pxt-core": "4.0.11"
}, },
"scripts": { "scripts": {
"test": "node node_modules/pxt-core/built/pxt.js travis" "test": "node node_modules/pxt-core/built/pxt.js travis"

View File

@@ -17,7 +17,8 @@
"libs/gyro-sensor", "libs/gyro-sensor",
"libs/screen", "libs/screen",
"libs/ev3", "libs/ev3",
"libs/storage" "libs/storage",
"libs/broadcast"
], ],
"simulator": { "simulator": {
"autoRun": true, "autoRun": true,

View File

@@ -1,5 +1,5 @@
namespace pxsim { namespace pxsim {
const enum GyroSensorMode { export const enum GyroSensorMode {
None = -1, None = -1,
Angle = 0, Angle = 0,
Rate = 1, Rate = 1,
@@ -8,7 +8,6 @@ namespace pxsim {
export class GyroSensorNode extends UartSensorNode { export class GyroSensorNode extends UartSensorNode {
id = NodeType.GyroSensor; id = NodeType.GyroSensor;
private angle: number = 0;
private rate: number = 0; private rate: number = 0;
constructor(port: number) { constructor(port: number) {
@@ -19,23 +18,20 @@ namespace pxsim {
return DAL.DEVICE_TYPE_GYRO; return DAL.DEVICE_TYPE_GYRO;
} }
setAngle(angle: number) {
if (this.angle != angle) {
this.angle = angle;
this.setChangedState();
}
}
setRate(rate: number) { setRate(rate: number) {
rate = rate | 0;
if (this.rate != rate) { if (this.rate != rate) {
this.rate = rate; this.rate = rate;
this.setChangedState(); this.setChangedState();
} }
} }
getRate() {
return this.rate;
}
getValue() { getValue() {
return this.mode == GyroSensorMode.Angle ? this.angle : return this.getRate();
this.mode == GyroSensorMode.Rate ? this.rate : 0;
} }
} }
} }

View File

@@ -49,11 +49,13 @@ namespace pxsim.visuals {
} }
private updateDimensions(width: number, height: number, strict?: boolean) { private updateDimensions(width: number, height: number, strict?: boolean) {
width = Math.max(0, width);
height = Math.max(0, height);
if (this.content) { if (this.content) {
const currentWidth = this.getInnerWidth(); const currentWidth = this.getInnerWidth();
const currentHeight = this.getInnerHeight(); const currentHeight = this.getInnerHeight();
const newHeight = currentHeight / currentWidth * width; const newHeight = Math.max(0, currentHeight / currentWidth * width);
const newWidth = currentWidth / currentHeight * height; const newWidth = Math.max(0, currentWidth / currentHeight * height);
if (strict) { if (strict) {
this.content.setAttribute('width', `${width}`); this.content.setAttribute('width', `${width}`);
this.content.setAttribute('height', `${height}`); this.content.setAttribute('height', `${height}`);

View File

@@ -1,13 +1,15 @@
namespace pxsim.visuals { namespace pxsim.visuals {
const MAX_RATE = 40;
export class RotationSliderControl extends ControlView<GyroSensorNode> { export class RotationSliderControl extends ControlView<GyroSensorNode> {
private group: SVGGElement; private group: SVGGElement;
private slider: SVGGElement; private slider: SVGGElement;
private rateText: SVGTextElement;
private static SLIDER_WIDTH = 70; private static SLIDER_WIDTH = 70;
private static SLIDER_HEIGHT = 78; //private static SLIDER_HEIGHT = 78;
getInnerView(parent: SVGSVGElement, globalDefs: SVGDefsElement) { getInnerView(parent: SVGSVGElement, globalDefs: SVGDefsElement) {
this.group = svg.elt("g") as SVGGElement; this.group = svg.elt("g") as SVGGElement;
@@ -23,6 +25,14 @@ namespace pxsim.visuals {
pxsim.svg.child(this.slider, "circle", { 'cx': 9, 'cy': 50, 'r': 13, 'style': 'fill: #f12a21' }); pxsim.svg.child(this.slider, "circle", { 'cx': 9, 'cy': 50, 'r': 13, 'style': 'fill: #f12a21' });
pxsim.svg.child(this.slider, "circle", { 'cx': 9, 'cy': 50, 'r': 12.5, 'style': 'fill: none;stroke: #b32e29' }); pxsim.svg.child(this.slider, "circle", { 'cx': 9, 'cy': 50, 'r': 12.5, 'style': 'fill: none;stroke: #b32e29' });
this.rateText = pxsim.svg.child(this.group, "text", {
'x': this.getInnerWidth() / 2,
'y': RotationSliderControl.SLIDER_WIDTH * 1.2,
'text-anchor': 'middle', 'dominant-baseline': 'middle',
'style': 'font-size: 16px',
'class': 'sim-text inverted number'
}) as SVGTextElement;
const dragSurface = svg.child(this.group, "rect", { const dragSurface = svg.child(this.group, "rect", {
x: 0, x: 0,
y: 0, y: 0,
@@ -61,7 +71,10 @@ namespace pxsim.visuals {
return; return;
} }
const node = this.state; const node = this.state;
const percentage = node.getValue(); const rate = node.getRate();
this.rateText.textContent = `${rate}°/s`
// cap rate at 40deg/s
const percentage = 50 + Math.sign(rate) * Math.min(MAX_RATE, Math.abs(rate)) / MAX_RATE * 50;
const x = RotationSliderControl.SLIDER_WIDTH * percentage / 100; const x = RotationSliderControl.SLIDER_WIDTH * percentage / 100;
const y = Math.abs((percentage - 50) / 50) * 10; const y = Math.abs((percentage - 50) / 50) * 10;
this.slider.setAttribute("transform", `translate(${x}, ${y})`); this.slider.setAttribute("transform", `translate(${x}, ${y})`);
@@ -73,8 +86,10 @@ namespace pxsim.visuals {
const bBox = this.content.getBoundingClientRect(); const bBox = this.content.getBoundingClientRect();
let t = Math.max(0, Math.min(1, (width + bBox.left / this.scaleFactor - cur.x / this.scaleFactor) / width)) let t = Math.max(0, Math.min(1, (width + bBox.left / this.scaleFactor - cur.x / this.scaleFactor) / width))
t = -(t - 0.5) * 2; // [-1,1]
const state = this.state; const state = this.state;
state.setRate((1 - t) * (100)); state.setRate(MAX_RATE * t);
} }
} }

View File

@@ -5,6 +5,10 @@
"LEGO" "LEGO"
], ],
"approvedRepos": [ "approvedRepos": [
"microsoft/pxt-automation"
],
"preferredRepos": [
"microsoft/pxt-automation"
] ]
}, },
"galleries": { "galleries": {
@@ -14,14 +18,15 @@
"Touch Sensor Tutorials": "tutorials/touch-sensor", "Touch Sensor Tutorials": "tutorials/touch-sensor",
"Color Sensor Tutorials": "tutorials/color-sensor", "Color Sensor Tutorials": "tutorials/color-sensor",
"Ultrasonic Sensor Tutorials": "tutorials/ultrasonic-sensor", "Ultrasonic Sensor Tutorials": "tutorials/ultrasonic-sensor",
"Gyro Tutorials": "tutorials/gyro",
"Infrared Sensor Tutorials": "tutorials/infrared-sensor", "Infrared Sensor Tutorials": "tutorials/infrared-sensor",
"FLL / City Shaper / Crane Mission": "tutorials/city-shaper/crane-mission", "FLL / City Shaper": "tutorials/city-shaper",
"Design Engineering": "design-engineering", "Design Engineering": "design-engineering",
"Coding": "coding", "Coding": "coding",
"Maker": "maker", "Maker": "maker",
"Videos": "videos" "Videos": "videos"
}, },
"electronManifest": { "electronManifest": {
"latest": "v1.0.11" "latest": "v1.1.22"
} }
} }

View File

@@ -185,3 +185,8 @@
font-family: 'legoIcons' !important; font-family: 'legoIcons' !important;
content: "\f119" !important; content: "\f119" !important;
} }
.bluetooth {
background-color: #007EF4 !important;
color: white !important;
}