Compare commits
134 Commits
v1.1.4
...
offlineapp
Author | SHA1 | Date | |
---|---|---|---|
8fd0bf92fa | |||
a33bff0520 | |||
7549f865d6 | |||
a5de9d88bb | |||
0437df10de | |||
2079173dfa | |||
018a1e7528 | |||
757f95d984 | |||
8bc3fdc8ba | |||
e8a1e73cf5 | |||
e9b2b239ad | |||
5ad2288a9f | |||
92d13ef343 | |||
471ca5d915 | |||
f745079728 | |||
d179f18ef3 | |||
805fc6c787 | |||
374bbb8304 | |||
25452efc92 | |||
80b9c715b2 | |||
cb816c91ad | |||
3012068986 | |||
4c9c7d6a69 | |||
ad3652c290 | |||
d8971829e3 | |||
8ca4558fc2 | |||
d85b5c5129 | |||
95b1c6a50f | |||
4dc2872286 | |||
6c9ff804c8 | |||
7581b5af9e | |||
07504027f9 | |||
a0e133864a | |||
0285711954 | |||
91be998d7e | |||
e862869327 | |||
8047cb2612 | |||
8e2ffefd2c | |||
092e7b0522 | |||
42454ecd57 | |||
2563fd6031 | |||
e0c1f2dca0 | |||
c80574ed3f | |||
b28b5cb6b7 | |||
d1f11059db | |||
6de5c6afdf | |||
b72c7c0c4f | |||
352c1ca5ec | |||
6d940a9ec7 | |||
c070173346 | |||
6fcf68f174 | |||
02f2a85d28 | |||
f63196908e | |||
ad48ee12ca | |||
83aeb24a98 | |||
fc5ecd9f10 | |||
0b3b840ac1 | |||
60c09809e7 | |||
148067a143 | |||
6f34887c94 | |||
64a9930c2e | |||
5815e16647 | |||
c4f5e425c2 | |||
361ae7a2d2 | |||
3769402ade | |||
5c3c83eb52 | |||
0f07a89981 | |||
11d887a213 | |||
8150b2dbb0 | |||
89f41f23da | |||
6b78e08053 | |||
186c86d2b1 | |||
181c71d2dd | |||
be05da3232 | |||
5958157a76 | |||
f72b825377 | |||
d76af5e5af | |||
23f48db20b | |||
2975bf2b55 | |||
5314515619 | |||
75b2db9f67 | |||
cf2e39f1b2 | |||
6d15d69aa1 | |||
72a0940235 | |||
b08dd8a7d2 | |||
def648a98b | |||
32a92789b3 | |||
9956bb06fb | |||
83b9aecd7a | |||
17ab24eaa9 | |||
9c5d5f9a86 | |||
43a13e0877 | |||
fe0915484d | |||
87a65aa38f | |||
1317da8904 | |||
62b2881e2a | |||
bf482a2ac9 | |||
243600ad8f | |||
349caa4aed | |||
56bbcde299 | |||
7e9cc791ec | |||
d5194b8d28 | |||
12b1eb349b | |||
68dc195ea4 | |||
0251b914f2 | |||
1fc818767c | |||
9aeaec477f | |||
7fc796d2cb | |||
cb1cd2a4b4 | |||
39bd7aa0eb | |||
140ba64462 | |||
42fe96aa5a | |||
1a5b42026d | |||
9fe649aa3c | |||
a97dfb17b2 | |||
277c9903bb | |||
0de8a84de2 | |||
a302bbfc2b | |||
bcb682b602 | |||
e4a7531541 | |||
348964c888 | |||
8b3461bebd | |||
e511630c2e | |||
db156d5df0 | |||
93c6975400 | |||
abc93dd7da | |||
85cfc86bf8 | |||
b66d4f2d64 | |||
5843deab11 | |||
8d5edc38bb | |||
0309e50058 | |||
aa40e7b169 | |||
75cf8da396 | |||
db9b6a995b |
25
README.md
@ -1,10 +1,8 @@
|
||||
# LEGO® MINDSTORMS® Education EV3 for Microsoft MakeCode
|
||||
|
||||
[](https://ci2.dot.net/job/Private/job/pxt_project_rainbow/job/master/job/pxt-ev3_Push/)
|
||||
# LEGO® MINDSTORMS® Education EV3 for Microsoft MakeCode [](https://travis-ci.org/microsoft/pxt-ev3)
|
||||
|
||||
This repo contains the editor target hosted at https://makecode.mindstorms.com
|
||||
|
||||
## Local Dev setup
|
||||
## Local setup
|
||||
|
||||
These instructions assume familiarity with dev tools and languages.
|
||||
|
||||
@ -12,10 +10,6 @@ These instructions assume familiarity with dev tools and languages.
|
||||
* install Docker; make sure `docker` command is in your `PATH`
|
||||
* (optional) install [Visual Studio Code](https://code.visualstudio.com/)
|
||||
|
||||
In a common folder,
|
||||
|
||||
* clone https://github.com/Microsoft/pxt to ``pxt`` folder
|
||||
* clone https://github.com/Microsoft/pxt-common-packages to ``pxt-common-packages`` folder
|
||||
* clone https://github.com/Microsoft/pxt-ev3 to ``pxt-ev3`` folder
|
||||
* go to ``pxt`` and run
|
||||
|
||||
@ -23,6 +17,18 @@ In a common folder,
|
||||
npm install
|
||||
```
|
||||
|
||||
* to run the local server,
|
||||
```
|
||||
pxt serve --cloud
|
||||
```
|
||||
|
||||
## Local Dev setup
|
||||
|
||||
In the common folder,
|
||||
|
||||
* clone https://github.com/Microsoft/pxt to ``pxt`` folder
|
||||
* clone https://github.com/Microsoft/pxt-common-packages to ``pxt-common-packages`` folder
|
||||
|
||||
* go to ``pxt-common-packages`` and run
|
||||
|
||||
```
|
||||
@ -54,9 +60,6 @@ cd libs/core
|
||||
pxt deploy
|
||||
```
|
||||
|
||||
### Jenkins build
|
||||
https://ci2.dot.net/job/Private/job/pxt_project_rainbow/job/master/
|
||||
|
||||
## License
|
||||
MIT
|
||||
|
||||
|
@ -6,13 +6,13 @@
|
||||
<a class="item" href="https://makecode.com/privacy" target="_blank" rel="noopener">Privacy & 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/trademarks" target="_blank" rel="noopener">Trademarks</a>
|
||||
<div class="item">© 2018 Microsoft</div>
|
||||
<div class="item">©2019 Microsoft</div>
|
||||
</div>
|
||||
<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>
|
||||
</div>
|
||||
<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>
|
||||
</footer>
|
||||
|
@ -4,6 +4,8 @@
|
||||
|
||||
* [Troubleshoot](/troubleshoot)
|
||||
* [EV3 Manager](https://ev3manager.education.lego.com/)
|
||||
* [Bluetooth](/bluetooth)
|
||||
* [Forum](https://forum.makecode.com)
|
||||
* [LEGO Support](https://www.lego.com/service/)
|
||||
* [FIRST LEGO League](/fll)
|
||||
|
||||
@ -19,11 +21,14 @@
|
||||
* [What Animal Am I?](/tutorials/what-animal-am-i)
|
||||
* [Music Brick](/tutorials/music-brick)
|
||||
* [Run Motors](/tutorials/run-motors)
|
||||
* [Tank ZigZag](/tutorials/tank-zigzag)
|
||||
* [Touch to Run](/tutorials/touch-to-run)
|
||||
* [Touch Sensor Values](/tutorials/touch-sensor-values)
|
||||
* [What Color?](/tutorials/what-color)
|
||||
* [Line Following](/tutorials/line-following)
|
||||
* [Red Light, Green Light](/tutorials/redlight-greenlight)
|
||||
* [Reflected Light Measure](/tutorials/reflected-light-measure)
|
||||
* [Reflected Light Calibration](/tutorials/reflected-light-calibration)
|
||||
* [Object Near?](/tutorials/object-near)
|
||||
* [Security Alert](/tutorials/security-alert)
|
||||
|
||||
|
@ -28,7 +28,7 @@ program to a **.uf2** file, which you then copy to the **@drivename@** drive. Th
|
||||
|
||||
### ~ 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
@ -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**
|
||||
|
||||

|
||||
|
||||
## 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.
|
@ -212,6 +212,12 @@ Here are some fun programs for your @boardname@!
|
||||
"description": "Keep your brick entertained and happy",
|
||||
"url":"/examples/happy-unhappy",
|
||||
"cardType": "example"
|
||||
}, {
|
||||
{
|
||||
"name": "Turtle",
|
||||
"description": "Encode moves and run them on a driving base",
|
||||
"url":"/examples/turtle",
|
||||
"cardType": "example"
|
||||
}, {
|
||||
"name": "Distance Measurer",
|
||||
"description": "Use a motor to measure angle and distance",
|
||||
|
18
docs/extensions.md
Normal file
@ -0,0 +1,18 @@
|
||||
# Extensions
|
||||
|
||||
## #gallery
|
||||
|
||||
## Using Extensions
|
||||
|
||||
In the web editor, click on ``Settings`` then ``Extensions`` to search and add extensions to the project.
|
||||
The Blocks and JavaScript definitions will be automatically loaded in the editor.
|
||||
|
||||
## Custom extensions
|
||||
|
||||
The [Build Your Own Extension](https://makecode.com/extensions/getting-started) manual is for advanced users who want to publish their own extension.
|
||||
|
||||
## ~ hint
|
||||
|
||||
**Extensions** were previously called **Packages** in MakeCode.
|
||||
|
||||
## ~
|
BIN
docs/file-manager.pdf
Normal file
56
docs/fll.md
@ -2,32 +2,43 @@
|
||||
|
||||

|
||||
|
||||
For teams participating in the Open Software Platform Pilot utilizing MakeCode, we’ve compiled a list of resources and information that we hope will be helpful for you.
|
||||
**For teams participating in City Shaper challenge**, you can use MakeCode for your challenge (see [City Shaper Challenge, page 7 bottom](https://firstinspiresst01.blob.core.windows.net/fll/2020/city-shaper-game-guide-pdf.pdf)!
|
||||
|
||||
We’ve compiled a list of resources and information that we hope will be helpful for you.
|
||||
|
||||
* **Got a question? Post it on the forums** at https://forum.makecode.com/
|
||||
|
||||
## FAQ
|
||||
|
||||
### I found a bug what do I do?
|
||||
|
||||
If you found a bug, please try if it hasn't been fixed yet! Go to https://makecode.mindstorms.com/beta and try if the bug is corrected. Otherwise, please tell us at https://forum.makecode.com/.
|
||||
|
||||
### How do I use MakeCode with my EV3?
|
||||
|
||||
* 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 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?
|
||||
|
||||
We have compiled a guide for EV3 LabView users at https://makecode.mindstorms.com/labview.
|
||||
|
||||
### What’s the best way to get started with MakeCode?
|
||||
|
||||
Watch some of the videos at https://makecode.mindstorms.com (at the bottom of the page).
|
||||
Try some of the provided tutorials:
|
||||
Go to https://makecode.mindstorms.com. The home screen is filled with videos, tutorials and examples that might be relevant for your missions.
|
||||
|
||||
* [Wake Up!](@homeurl@#tutorial:tutorials/wake-up) – show your EV3 brick waking up
|
||||
* [Animation](@homeurl@#tutorial:tutorials/make-an-animation) – create a custom animation to show
|
||||
* [Music Brick](@homeurl@#tutorial:tutorials/music-brick) – transform your EV3 into a musical instrument
|
||||
* [Run Motors](@homeurl@#tutorial:tutorials/run-motors) – control the motors of your robot
|
||||
* [Red Light, Green Light](@homeurl@#tutorial:tutorials/redlight-greenlight) – play red light, green light with the color sensor
|
||||
* [Line Following](@homeurl@#tutorial:tutorials/line-following) – have your robot follow a line
|
||||
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?
|
||||
|
||||
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?
|
||||
|
||||
You can right-click on any block and select “Help” in the context menu to open the documentation page describing what that block does.
|
||||
@ -81,20 +92,19 @@ 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.
|
||||
|
||||
### 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?
|
||||
|
||||
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. As a workaround, you can temporarily downgrade your firmware version, delete the files, and then upgrade back to the version that works with MakeCode.
|
||||
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.
|
||||
|
||||
Follow these steps to downgrade your firmware version, delete the files, and uprgade back again:
|
||||
|
||||
1. Go into **EV3 LabVIEW** - if it's not installed get it [here](https://education.lego.com/en-us/downloads/mindstorms-ev3/software)
|
||||
2. Plug in your EV3 Brick and start a new project
|
||||
3. Go to the **Tools** menu in the upper right corner, select **Firmware Update**
|
||||
4. In the **Firmware Update** dialog box, click on the **Show Details** button
|
||||
5. From the **Available Firmware Files** list, select **EV3 Firmware V1.09E**
|
||||
6. Click the **Update Firmware** button and wait for the update to complete
|
||||
|
||||
Now the firmware version on the EV3 Brick will be **V1.09E**. Also, in the process, the downgrade deleted all of the saved programs from the EV3 Brick. To continue to use MakeCode, the firmware version must be at **V1.10E** or above. So, the Brick firmware needs to be upgraded again. If you don't know or do remember how to do this, see the **Upgrade your @drivename@** section in the [troubleshooting](/troubleshoot) page.
|
||||
We have prepared a special program that lets you delete UF2 files from the brick.
|
||||
Download [these PDF instructions](/file-manager.pdf) and drop the PDF on the brick drive.
|
||||
This will present you with an menu for deleting files.
|
||||
|
||||
For other common questions, try the FAQ page https://makecode.mindstorms.com/faq.
|
||||
|
||||
@ -104,9 +114,3 @@ For other common questions, try the FAQ page https://makecode.mindstorms.com/faq
|
||||
|
||||
>* Description: Unable to delete program files from the EV3 brick after downloading them
|
||||
>* Status: LEGO Education team is working on a fix, no estimated date yet
|
||||
|
||||
## Community connection
|
||||
|
||||
For questions, issues, feedback and community for the Open Software Platform Pilot:
|
||||
|
||||
We are using a messaging service called **Slack**. Slack can be accessed via an app you download to your computer or mobile device, and via a web interface. For more information about Slack, click [here](https://slack.com/). Anyone in the pilot can participate by signing up with Slack first, and then clicking this [FIRST LEGO League Robot SW](https://fllrobotsw.slack.com/join/shared_invite/enQtNDgxOTQ5MDc2OTkyLTg2ZTRkYzQ4OGMyZTg1OTZmMDFhMWNlOTQ1OWRlNDdmNzNmMjlhMmZiM2M3OWUxYjU1ODEwY2FmODJkNjZkOTA) link to join the Slack workspace.
|
||||
|
@ -25,6 +25,12 @@
|
||||
"description": "Build a robot and drive into the world of robotics!",
|
||||
"url": "/getting-started/use",
|
||||
"cardType": "side"
|
||||
},
|
||||
{
|
||||
"name": "First LEGO League",
|
||||
"imageUrl": "/static/fll/fll-big.png",
|
||||
"description": "Information about using MakeCode in FLL competitions",
|
||||
"url": "/fll"
|
||||
}
|
||||
]
|
||||
```
|
@ -1,3 +1,3 @@
|
||||
{
|
||||
"appref": "v1.0.11"
|
||||
"appref": "v1.2.22"
|
||||
}
|
||||
|
@ -61,6 +61,12 @@ motors.largeBC.steer(0, 50, 1, MoveUnit.Rotations);
|
||||
motors.largeBC.stop();
|
||||
```
|
||||
|
||||
### ~ hint
|
||||
|
||||
The **turn ratio range is -200, 200** unlike LabView who used -100,100.
|
||||
|
||||
### ~
|
||||
|
||||
## 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.
|
||||
|
@ -3,11 +3,20 @@
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>LEGO® MINDSTORMS® Education EV3 Offline App</title>
|
||||
<meta name="Description" content="A MakeCode for LEGO® MINDSTORMS® Education EV3 offline app" />
|
||||
<title>@name@ Offline App</title>
|
||||
<meta name="Description" content="A MakeCode for @name@ offline app" />
|
||||
<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>
|
||||
p.item {
|
||||
color: rgba(0, 0, 0, 0.4);
|
||||
@ -55,6 +64,10 @@
|
||||
background-color: rgb(250, 250, 250);
|
||||
}
|
||||
|
||||
#legal-segment {
|
||||
background:white;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 800px) {
|
||||
.grid .column .image {
|
||||
display: none;
|
||||
@ -368,34 +381,61 @@
|
||||
</style>
|
||||
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
tickEvent = function (id, data) {
|
||||
if (!pxt.aiTrackEvent) return;
|
||||
if (!data) pxt.aiTrackEvent(id);
|
||||
else {
|
||||
var props = {};
|
||||
var measures = {};
|
||||
for (var k in data)
|
||||
if (typeof data[k] == "string") props[k] = data[k];
|
||||
else measures[k] = data[k];
|
||||
pxt.aiTrackEvent(id, props, measures);
|
||||
}
|
||||
var electronLatestVersion = "";
|
||||
function tickEvent(id, data) {
|
||||
if (!pxt.aiTrackEvent) return;
|
||||
if (!data) pxt.aiTrackEvent(id);
|
||||
else {
|
||||
var props = {};
|
||||
var measures = {};
|
||||
for (var k in data)
|
||||
if (typeof data[k] == "string") props[k] = data[k];
|
||||
else measures[k] = data[k];
|
||||
pxt.aiTrackEvent(id, props, measures);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function agreeCheckboxChanged() {
|
||||
var downloadSegment = document.getElementById("download-segment");
|
||||
downloadSegment.classList.toggle("hidden");
|
||||
showDownloads();
|
||||
}
|
||||
|
||||
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() {
|
||||
// TODO: Keep this link up-to-date with the desired release version
|
||||
window.open("https://makecode.com/api/release/ev3/v1.0.11/win64");
|
||||
tickEvent("offlineapp.download", { "target": "ev3", "platform": "win64" });
|
||||
tickEvent("offlineapp.download", { "target": "@targetid@", "platform": "win64" });
|
||||
}
|
||||
function downloadMac64() {
|
||||
// TODO: Keep this link up-to-date with the desired release version
|
||||
window.open("https://makecode.com/api/release/ev3/v1.0.11/mac64");
|
||||
tickEvent("offlineapp.download", { "target": "ev3", "platform": "mac64" });
|
||||
tickEvent("offlineapp.download", { "target": "@targetid@", "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>
|
||||
|
||||
</head>
|
||||
@ -406,35 +446,39 @@
|
||||
|
||||
<div class="ui grid topbar">
|
||||
<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 class="ten wide column">
|
||||
<h1 class="ui inverted welcomeheader">MakeCode Offline App</h1>
|
||||
<h1 class="ui inverted welcomeheader">@name@ Offline App</h1>
|
||||
</div>
|
||||
<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 class="ui compact segments terms-container">
|
||||
<div class="ui secondary center aligned segment">
|
||||
<div id="segments" class="ui compact segments terms-container">
|
||||
<div id="read-segment" class="ui secondary center aligned segment hidden">
|
||||
Please read and accept the following terms to download the app.
|
||||
</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">
|
||||
<p class="c11">
|
||||
<span class="c4 c1">MICROSOFT PRE-RELEASE SOFTWARE LICENSE TERMS</span>
|
||||
</p>
|
||||
<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 class="c7">
|
||||
<span class="c4 c1"></span>
|
||||
</p>
|
||||
<p class="c11">
|
||||
<span class="c3 c1">These license terms are an agreement between Microsoft Corporation (or based on where you live, one
|
||||
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>
|
||||
<span class="c3 c1">These license terms are an agreement between Microsoft Corporation (or based
|
||||
on where you live, one
|
||||
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 class="c7">
|
||||
<span class="c3 c1"></span>
|
||||
@ -446,27 +490,35 @@
|
||||
<span class="c5 c1">1.</span>
|
||||
<span class="c1"> </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
|
||||
your software applications for use with Lego Mindstorms Education EV3 hardware.</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
|
||||
your software applications for use with @name@ hardware.</span>
|
||||
</p>
|
||||
<p class="c2">
|
||||
<span class="c5 c1">2.</span>
|
||||
<span class="c1"> </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.
|
||||
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
|
||||
<span class="c3 c1">The software is a pre-release version. It may not work the way a final
|
||||
version of the software will.
|
||||
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>
|
||||
</p>
|
||||
<p class="c2">
|
||||
<span class="c5 c1">3.</span>
|
||||
<span class="c1"> </span>
|
||||
<span class="c5 c1">ASSOCIATED ONLINE SERVICES.</span>
|
||||
<span class="c1"> Some features of the software provide access
|
||||
to, or rely on, online services to provide you information about updates to the software or 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
|
||||
<span class="c1"> Some features of the software
|
||||
provide access
|
||||
to, or rely on, online services to provide you information about updates to the software or
|
||||
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
|
||||
transmission of information as described in Section 5, DATA.
|
||||
</span>
|
||||
</p>
|
||||
@ -474,9 +526,11 @@
|
||||
<span class="c5 c1">4.</span>
|
||||
<span class="c1"> </span>
|
||||
<span class="c5 c1">LICENSES FOR OTHER COMPONENTS.</span>
|
||||
<span class="c3 c1"> The software may include third party components with separate legal notices or governed by
|
||||
<span class="c3 c1"> 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
|
||||
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>
|
||||
</p>
|
||||
<p class="c2">
|
||||
@ -488,26 +542,35 @@
|
||||
<span class="c5 c1">a.</span>
|
||||
<span class="c1"> </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.
|
||||
Microsoft may use this information to provide services and improve our products and 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
|
||||
<span class="c1">The software may collect information about you and your use of the software,
|
||||
and send that to Microsoft.
|
||||
Microsoft may use this information to provide services and improve our products and
|
||||
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>
|
||||
<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 class="c1">.</span>
|
||||
<span class="c3 c1"> Your use of the software operates as your consent to these practices.</span>
|
||||
<span class="c3 c1"> Your use of the software operates as your consent to these
|
||||
practices.</span>
|
||||
</p>
|
||||
<p class="c8">
|
||||
<span class="c5 c1">b.</span>
|
||||
<span class="c1"> </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,
|
||||
Microsoft makes the commitments in the European Union General Data Protection Regulation Terms
|
||||
<span class="c1">To the extent Microsoft is a processor or subprocessor of personal data in
|
||||
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>
|
||||
<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 class="c3 c1">.</span>
|
||||
</p>
|
||||
@ -515,48 +578,62 @@
|
||||
<span class="c5 c1">6.</span>
|
||||
<span class="c1"> </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
|
||||
not give feedback that is subject to a license that requires Microsoft to license its software
|
||||
or documentation to third parties because we include your feedback in them. These rights survive
|
||||
not give feedback that is subject to a license that requires Microsoft to license its
|
||||
software
|
||||
or documentation to third parties because we include your feedback in them. These rights
|
||||
survive
|
||||
this agreement.</span>
|
||||
</p>
|
||||
<p class="c2">
|
||||
<span class="c5 c1">7.</span>
|
||||
<span class="c1"> </span>
|
||||
<span class="c5 c1">SCOPE OF LICENSE.</span>
|
||||
<span class="c3 c1"> The software is licensed, not sold. This agreement only gives you some rights to use the software.
|
||||
Microsoft reserves all other rights. Unless applicable law gives you more rights despite this
|
||||
<span class="c3 c1"> The software is licensed, not sold. This agreement only gives you some
|
||||
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. In
|
||||
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>
|
||||
</p>
|
||||
<p class="c8">
|
||||
<span class="c3 c1">- work around any technical limitations in the software;</span>
|
||||
<span class="c3 c1">- work around any technical limitations in the
|
||||
software;</span>
|
||||
</p>
|
||||
<p class="c8">
|
||||
<span class="c3 c1">- reverse engineer, decompile or disassemble the software, or attempt to do so, except
|
||||
<span class="c3 c1">- 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
|
||||
source components that may be included with the software;</span>
|
||||
</p>
|
||||
<p class="c8">
|
||||
<span class="c3 c1">- remove, minimize, block or modify any notices of Microsoft or its suppliers in the
|
||||
<span class="c3 c1">- remove, minimize, block or modify any notices of Microsoft
|
||||
or its suppliers in the
|
||||
software;
|
||||
</span>
|
||||
</p>
|
||||
<p class="c8">
|
||||
<span class="c3 c1">- use the software in any way that is against the law; or</span>
|
||||
<span class="c3 c1">- use the software in any way that is against the law;
|
||||
or</span>
|
||||
</p>
|
||||
<p class="c8">
|
||||
<span class="c3 c1">- share, publish, rent or lease the software, or provide the software as a stand-alone
|
||||
<span class="c3 c1">- share, publish, rent or lease the software, or provide the
|
||||
software as a stand-alone
|
||||
offering for others to use.</span>
|
||||
</p>
|
||||
<p class="c2">
|
||||
<span class="c5 c1">8. UPDATES. </span>
|
||||
<span class="c3 c1">The software may periodically check for updates and download and install them for you. You may obtain
|
||||
updates only from Microsoft or authorized sources. Microsoft may need to update your system 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
|
||||
<span class="c3 c1">The software may periodically check for updates and download and install
|
||||
them for you. You may obtain
|
||||
updates only from Microsoft or authorized sources. Microsoft may need to update your system
|
||||
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.
|
||||
</span>
|
||||
</p>
|
||||
@ -564,57 +641,74 @@
|
||||
<span class="c5 c1">9.</span>
|
||||
<span class="c1"> </span>
|
||||
<span class="c5 c1">EXPORT RESTRICTIONS.</span>
|
||||
<span class="c3 c1"> You must comply with all domestic and international export laws and regulations that apply
|
||||
to the software, which include restrictions on destinations, end users and end use. For further
|
||||
<span class="c3 c1"> You must comply with all domestic and international export laws and
|
||||
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>
|
||||
</p>
|
||||
<p class="c2">
|
||||
<span class="c5 c1">10.</span>
|
||||
<span class="c1"> </span>
|
||||
<span class="c5 c1">SUPPORT SERVICES. </span>
|
||||
<span class="c3 c1">Because the software is “as is,” we may not provide support services for it.</span>
|
||||
<span class="c3 c1">Because the software is “as is,” we may not provide support
|
||||
services for it.</span>
|
||||
</p>
|
||||
<p class="c2">
|
||||
<span class="c5 c1">11.</span>
|
||||
<span class="c1"> </span>
|
||||
<span class="c5 c1">ENTIRE AGREEMENT.</span>
|
||||
<span class="c3 c1"> This agreement, and the terms for supplements, updates, Internet-based services and support
|
||||
services that you use, are the entire agreement for the software and support services.</span>
|
||||
<span class="c3 c1"> This agreement, and the terms for supplements, updates, Internet-based
|
||||
services and support
|
||||
services that you use, are the entire agreement for the software and support
|
||||
services.</span>
|
||||
</p>
|
||||
<p class="c2">
|
||||
<span class="c5 c1">12.</span>
|
||||
<span class="c1"> </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
|
||||
of and claims for breach of this agreement, and the laws of the state where you live apply to
|
||||
<span class="c3 c1">If you acquired the software in the United States, Washington State law
|
||||
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>
|
||||
</p>
|
||||
<p class="c2">
|
||||
<span class="c5 c1">13.</span>
|
||||
<span class="c1"> </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,
|
||||
under the laws of your state or country. Separate and apart from your relationship with Microsoft,
|
||||
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
|
||||
<span class="c3 c1">This agreement describes certain legal rights. You may have other rights,
|
||||
including consumer rights,
|
||||
under the laws of your state or country. Separate and apart from your relationship with
|
||||
Microsoft,
|
||||
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>
|
||||
</p>
|
||||
<p class="c8">
|
||||
<span class="c5 c1">a.</span>
|
||||
<span class="c1"> </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>
|
||||
</p>
|
||||
<p class="c8">
|
||||
<span class="c5 c1">b.</span>
|
||||
<span class="c1"> </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
|
||||
update feature, disconnecting your device from the Internet (if and when you re-connect to 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
|
||||
<span class="c3 c1">If you acquired the software in Canada, you may stop receiving updates by
|
||||
turning off the automatic
|
||||
update feature, disconnecting your device from the Internet (if and when you re-connect to
|
||||
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>
|
||||
</p>
|
||||
<p class="c8">
|
||||
@ -627,8 +721,10 @@
|
||||
<span class="c5 c1">(i)</span>
|
||||
<span class="c1"> </span>
|
||||
<span class="c5 c1">Warranty</span>
|
||||
<span class="c3 c1">. The properly licensed software will perform substantially as described in any Microsoft materials
|
||||
that accompany the software. However, Microsoft gives no contractual guarantee in relation to
|
||||
<span class="c3 c1">. The properly licensed software will perform substantially as described in
|
||||
any Microsoft materials
|
||||
that accompany the software. However, Microsoft gives no contractual guarantee in relation
|
||||
to
|
||||
the licensed software.</span>
|
||||
</p>
|
||||
<p class="c6">
|
||||
@ -638,74 +734,103 @@
|
||||
<span class="c5 c1">(ii)</span>
|
||||
<span class="c1"> </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
|
||||
well as, in case of death or personal or physical injury, Microsoft is liable according to the
|
||||
<span class="c3 c1">. In case of intentional conduct, gross negligence, claims based on 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>
|
||||
</p>
|
||||
<p class="c10">
|
||||
<span class="c3 c1">Subject to the foregoing clause (ii), Microsoft will only be liable for slight negligence if Microsoft
|
||||
is in breach of such material contractual obligations, the fulfillment of which facilitate the
|
||||
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 "cardinal obligations").
|
||||
In other cases of slight negligence, Microsoft will not be liable for slight negligence.</span>
|
||||
<span class="c3 c1">Subject to the foregoing clause (ii), Microsoft will only be liable for
|
||||
slight negligence if Microsoft
|
||||
is in breach of such material contractual obligations, the fulfillment of which facilitate
|
||||
the
|
||||
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 "cardinal
|
||||
obligations").
|
||||
In other cases of slight negligence, Microsoft will not be liable for slight
|
||||
negligence.</span>
|
||||
</p>
|
||||
<p class="c2">
|
||||
<span class="c5 c1">14.</span>
|
||||
<span class="c1"> </span>
|
||||
<span class="c5 c1">LEGAL EFFECT.</span>
|
||||
<span class="c3 c1"> This agreement describes certain legal rights. You may have other rights under the laws of
|
||||
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
|
||||
<span class="c3 c1"> This agreement describes certain legal rights. You may have other
|
||||
rights under the laws of
|
||||
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>
|
||||
</p>
|
||||
<p class="c2">
|
||||
<span class="c5 c1">15.</span>
|
||||
<span class="c1"> </span>
|
||||
<span class="c4 c1">DISCLAIMER OF WARRANTY. THE SOFTWARE IS LICENSED “AS-IS.” YOU BEAR THE RISK OF
|
||||
USING IT. MICROSOFT GIVES NO EXPRESS WARRANTIES, GUARANTEES OR CONDITIONS. TO THE EXTENT PERMITTED
|
||||
<span class="c4 c1">DISCLAIMER OF WARRANTY. THE SOFTWARE IS LICENSED “AS-IS.”
|
||||
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
|
||||
FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.</span>
|
||||
</p>
|
||||
<p class="c2">
|
||||
<span class="c5 c1">16.</span>
|
||||
<span class="c1"> </span>
|
||||
<span class="c4 c1">LIMITATION ON AND EXCLUSION OF DAMAGES. YOU CAN RECOVER FROM MICROSOFT AND ITS SUPPLIERS ONLY DIRECT
|
||||
DAMAGES UP TO U.S. $5.00. YOU CANNOT RECOVER ANY OTHER DAMAGES, INCLUDING CONSEQUENTIAL, LOST
|
||||
<span class="c4 c1">LIMITATION ON AND EXCLUSION OF DAMAGES. YOU CAN RECOVER FROM MICROSOFT AND
|
||||
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>
|
||||
</p>
|
||||
<p class="c0">
|
||||
<span class="c3 c1">This limitation applies to (a) anything related to the software, services, content (including code)
|
||||
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
|
||||
<span class="c3 c1">This limitation applies to (a) anything related to the software, services,
|
||||
content (including code)
|
||||
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>
|
||||
</p>
|
||||
<p class="c0">
|
||||
<span class="c3 c1">It also applies even if Microsoft knew or should have known about the possibility of the damages.
|
||||
The above limitation or exclusion may not apply to you because your country may not allow the
|
||||
<span class="c3 c1">It also applies even if Microsoft knew or should have known about 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>
|
||||
</p>
|
||||
<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>
|
||||
</p>
|
||||
<p class="c12">
|
||||
<span class="c4 c1">Remarque : Ce logiciel étant distribué au Québec, Canada, certaines des clauses
|
||||
<span class="c4 c1">Remarque : Ce logiciel étant distribué au Québec,
|
||||
Canada, certaines des clauses
|
||||
dans ce contrat sont fournies ci-dessous en français.</span>
|
||||
</p>
|
||||
<p class="c11">
|
||||
<span class="c5 c1">EXONÉRATION DE GARANTIE.</span>
|
||||
<span class="c1 c3"> Le logiciel visé par une licence est offert « tel quel ». Toute utilisation
|
||||
de ce logiciel est à votre seule risque et péril. Microsoft n’accorde aucune
|
||||
autre garantie expresse. Vous pouvez béné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é marchande, d’adéquation
|
||||
<span class="c1 c3"> Le logiciel visé par une licence est offert « tel quel
|
||||
». Toute utilisation
|
||||
de ce logiciel est à votre seule risque et péril. Microsoft n’accorde
|
||||
aucune
|
||||
autre garantie expresse. Vous pouvez béné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é marchande,
|
||||
d’adéquation
|
||||
à un usage particulier et d’absence de contrefaçon sont exclues.
|
||||
</span>
|
||||
</p>
|
||||
<p class="c11">
|
||||
<span class="c5 c1">LIMITATION DES DOMMAGES-INTÉRÊTS ET EXCLUSION DE RESPONSABILITÉ POUR LES DOMMAGES.</span>
|
||||
<span class="c3 c1"> Vous pouvez obtenir de Microsoft et de ses fournisseurs une indemnisation en cas de dommages
|
||||
directs uniquement à hauteur de 5,00 $ US. Vous ne pouvez prétendre à aucune
|
||||
<span class="c5 c1">LIMITATION DES DOMMAGES-INTÉRÊTS ET EXCLUSION DE
|
||||
RESPONSABILITÉ POUR LES DOMMAGES.</span>
|
||||
<span class="c3 c1"> Vous pouvez obtenir de Microsoft et de ses fournisseurs une
|
||||
indemnisation en cas de dommages
|
||||
directs uniquement à hauteur de 5,00 $ US. Vous ne pouvez prétendre à
|
||||
aucune
|
||||
indemnisation pour les autres dommages, y compris les dommages spéciaux, indirects ou
|
||||
accessoires et pertes de bénéfices.</span>
|
||||
</p>
|
||||
@ -713,26 +838,36 @@
|
||||
<span class="c3 c1">Cette limitation concerne :</span>
|
||||
</p>
|
||||
<p class="c2">
|
||||
<span class="c3 c1">- tout ce qui est relié au logiciel, aux services ou au contenu
|
||||
(y compris le code) figurant sur des sites Internet tiers ou dans des programmes tiers ; et</span>
|
||||
<span class="c3 c1">- tout ce qui est relié au logiciel, aux
|
||||
services ou au contenu
|
||||
(y compris le code) figurant sur des sites Internet tiers ou dans des programmes tiers ;
|
||||
et</span>
|
||||
</p>
|
||||
<p class="c2">
|
||||
<span class="c3 c1">- les réclamations au titre de violation de contrat ou de garantie,
|
||||
ou au titre de responsabilité stricte, de négligence ou d’une autre faute
|
||||
<span class="c3 c1">- les réclamations au titre de violation
|
||||
de contrat ou de garantie,
|
||||
ou au titre de responsabilité stricte, de négligence ou d’une autre
|
||||
faute
|
||||
dans la limite autorisée par la loi en vigueur.</span>
|
||||
</p>
|
||||
<p class="c12">
|
||||
<span class="c3 c1">Elle s’applique également, même si Microsoft connaissait ou devrait connaître
|
||||
<span class="c3 c1">Elle s’applique également, même si Microsoft connaissait
|
||||
ou devrait connaître
|
||||
l’éventualité d’un tel dommage. Si votre pays n’autorise pas
|
||||
l’exclusion ou la limitation de responsabilité pour les dommages indirects, accessoires
|
||||
ou de quelque nature que ce soit, il se peut que la limitation ou l’exclusion ci-dessus
|
||||
l’exclusion ou la limitation de responsabilité pour les dommages indirects,
|
||||
accessoires
|
||||
ou de quelque nature que ce soit, il se peut que la limitation ou l’exclusion
|
||||
ci-dessus
|
||||
ne s’appliquera pas à votre égard.</span>
|
||||
</p>
|
||||
<p class="c16">
|
||||
<span class="c5 c1">EFFET JURIDIQUE.</span>
|
||||
<span class="c3 c1"> Le présent contrat décrit certains droits juridiques. Vous pourriez avoir d’autres
|
||||
droits prévus par les lois de votre pays. Le présent contrat ne modifie pas les
|
||||
droits que vous confèrent les lois de votre pays si celles-ci ne le permettent pas.</span>
|
||||
<span class="c3 c1"> Le présent contrat décrit certains droits juridiques.
|
||||
Vous pourriez avoir d’autres
|
||||
droits prévus par les lois de votre pays. Le présent contrat ne modifie pas
|
||||
les
|
||||
droits que vous confèrent les lois de votre pays si celles-ci ne le permettent
|
||||
pas.</span>
|
||||
</p>
|
||||
<p class="c15">
|
||||
<span class="c3 c1"></span>
|
||||
@ -740,9 +875,9 @@
|
||||
<p class="c16">
|
||||
<span class="c3 c1"> </span>
|
||||
</p>
|
||||
|
||||
<p class="c11">
|
||||
<span class="c3 c1">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.</span>
|
||||
<span class="c3 c1">@copyrightText@</span>
|
||||
</p>
|
||||
<p class="c11">
|
||||
<span class="c3 c1"> </span>
|
||||
@ -755,27 +890,30 @@
|
||||
</p>
|
||||
</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)">
|
||||
<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>
|
||||
</label>
|
||||
</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 class="ui grid">
|
||||
<div class="eight wide column">
|
||||
<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>
|
||||
makecode-ev3-setup-win64.exe
|
||||
</button>
|
||||
makecode-@targetid@-setup-win64.exe
|
||||
</a>
|
||||
</div>
|
||||
<div class="eight wide column">
|
||||
<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>
|
||||
makecode-ev3-mac64.zip
|
||||
</button>
|
||||
makecode-@targetid@-mac64.zip
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -788,4 +926,4 @@
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
</html>
|
@ -3,9 +3,3 @@
|
||||
## 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.
|
||||
|
||||
### ~ hint
|
||||
|
||||
The [MakeCode Offline App](/offline-app) is currently in development and is made available as a **pre-release** version.
|
||||
|
||||
### ~
|
||||
|
3
docs/packages-ref.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"redirect": "/extensions"
|
||||
}
|
3
docs/packages/approval-ref.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"redirect": "https://makecode.com/extensions/approval"
|
||||
}
|
3
docs/packages/build-your-own-ref.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"redirect": "https://makecode.com/extensions/getting-started"
|
||||
}
|
3
docs/packages/versioning-ref.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"redirect": "https://makecode.com/extensions/versioning"
|
||||
}
|
47
docs/reference/brick/battery-property.md
Normal file
@ -0,0 +1,47 @@
|
||||
# battery Property
|
||||
|
||||
Return the information about the battery
|
||||
|
||||
```sig
|
||||
brick.batteryInfo(BatteryProperty.Level)
|
||||
```
|
||||
|
||||
## Parameters
|
||||
|
||||
* property: the kind of information
|
||||
|
||||
## Returns
|
||||
|
||||
* a [number](/types/number) which represents the value of the property requested.
|
||||
|
||||
## Example
|
||||
|
||||
Show the battery level percentage on the screen. Also, show a green light if the battery level is above 15%. If the battery level is below 15% but above 5%, show a orange light. But, if the battery level is below 5%, show a pulsing red light.
|
||||
|
||||
```blocks
|
||||
let battery = 0;
|
||||
forever(function() {
|
||||
brick.showString("Battery level:", 1)
|
||||
brick.showNumber(battery, 2)
|
||||
battery = brick.batteryInfo(BatteryProperty.Level);
|
||||
if (battery > 15)
|
||||
{
|
||||
brick.setStatusLight(StatusLight.Green);
|
||||
} else if (battery > 5) {
|
||||
brick.setStatusLight(StatusLight.Orange);
|
||||
} else {
|
||||
brick.setStatusLight(StatusLight.RedPulse)
|
||||
}
|
||||
pause(30000)
|
||||
})
|
||||
```
|
||||
|
||||
Or see all the values
|
||||
|
||||
```blocks
|
||||
forever(function () {
|
||||
brick.showValue("bat V", brick.batteryInfo(BatteryProperty.Voltage), 1)
|
||||
brick.showValue("bat %", brick.batteryInfo(BatteryProperty.Level), 2)
|
||||
brick.showValue("bat I", brick.batteryInfo(BatteryProperty.Current), 3)
|
||||
})
|
||||
```
|
@ -10,12 +10,12 @@ You can find out what's connected to the ports on the brick and show its status.
|
||||
|
||||
## 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
|
||||
brick.showString("Press ENTER for port status", 1)
|
||||
brick.showPorts()
|
||||
brick.buttonEnter.onEvent(ButtonEvent.Pressed, function () {
|
||||
brick.showPorts()
|
||||
motors.resetAll()
|
||||
})
|
||||
```
|
||||
|
||||
|
59
docs/reference/motors/motor/ramp.md
Normal file
@ -0,0 +1,59 @@
|
||||
# Ramp
|
||||
|
||||
Schedules an acceleration, constant and deceleration phase at a given speed.
|
||||
|
||||
```sig
|
||||
motors.largeA.ramp(50, 100, 500, 100)
|
||||
```
|
||||
|
||||
The speed setting is a percentage of the motor's full speed. Full speed is the speed that the motor runs when the brick supplies maximum output voltage to the port.
|
||||
|
||||
|
||||
## Parameters
|
||||
|
||||
* **speed**: a [number](/types/number) that is the percentage of full speed. A negative value runs the motor in the reverse direction.
|
||||
* **acceleration**: the [number](/types/number) of movement units to rotate for while accelerating.
|
||||
* **value**: the [number](/types/number) of movement units to rotate for.
|
||||
* **deceleration**: the [number](/types/number) of movement units to rotate for while decelerating.
|
||||
* **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.
|
||||
|
||||
## Example
|
||||
|
||||
This is an interactive program that lets you change the values of
|
||||
the acceleration and deceleration and see the effects.
|
||||
|
||||
```blocks
|
||||
let steady = 0
|
||||
let dec = 0
|
||||
let acc = 0
|
||||
brick.buttonLeft.onEvent(ButtonEvent.Pressed, function () {
|
||||
acc += -100
|
||||
})
|
||||
brick.buttonEnter.onEvent(ButtonEvent.Pressed, function () {
|
||||
motors.largeB.ramp(50, steady, MoveUnit.MilliSeconds, acc, dec)
|
||||
})
|
||||
brick.buttonRight.onEvent(ButtonEvent.Pressed, function () {
|
||||
acc += 100
|
||||
})
|
||||
brick.buttonUp.onEvent(ButtonEvent.Pressed, function () {
|
||||
dec += 100
|
||||
})
|
||||
brick.buttonDown.onEvent(ButtonEvent.Pressed, function () {
|
||||
dec += -100
|
||||
})
|
||||
acc = 500
|
||||
steady = 1000
|
||||
acc = 500
|
||||
forever(function () {
|
||||
brick.showValue("acc", acc, 1)
|
||||
brick.showValue("steady", steady, 2)
|
||||
brick.showValue("dec", dec, 3)
|
||||
brick.showString("acc: left/right", 5)
|
||||
brick.showString("dec: up/down", 6)
|
||||
brick.showString("run large B: enter", 7)
|
||||
})
|
||||
```
|
||||
|
||||
## See also
|
||||
|
||||
[tank](/reference/motors/synced/tank), [steer](/reference/motors/synced/steer), [stop](/reference/motors/motor/stop)
|
@ -6,9 +6,9 @@ Set the rotation speed of the motor as a percentage of maximum speed.
|
||||
motors.largeA.run(50)
|
||||
```
|
||||
|
||||
The speed setting is a pecentage of the motor's full speed. Full speed is the speed that the motor runs when the brick supplies maximum output voltage to the port.
|
||||
The speed setting is a percentage of the motor's full speed. Full speed is the speed that the motor runs when the brick supplies maximum output voltage to the port.
|
||||
|
||||
If you use just the **speed** number, the motor runs continously and won't stop unless you tell it to. You can also give a value for a certain amount of distance you want the motor to rotate for. The **value** can be an amount of time, a turn angle in degrees, or a number of full rotations.
|
||||
If you use just the **speed** number, the motor runs continuously and won't stop unless you tell it to. You can also give a value for a certain amount of distance you want the motor to rotate for. The **value** can be an amount of time, a turn angle in degrees, or a number of full rotations.
|
||||
|
||||
If you decide to use a **value** of rotation distance, you need to choose a type of movement **unit**.
|
||||
|
||||
@ -30,8 +30,8 @@ Here is how you use each different movement unit to run the motor for a fixed ro
|
||||
// Run motor for 700 Milliseconds.
|
||||
motors.largeA.run(25, 700, MoveUnit.MilliSeconds);
|
||||
|
||||
// Run motor for 700 Milliseconds again but no units specified.
|
||||
motors.largeA.run(25, 700);
|
||||
// Run motors B and C for 700 Milliseconds again but no units specified.
|
||||
motors.largeBC.run(25, 700);
|
||||
|
||||
// Run the motor for 45 seconds
|
||||
motors.largeA.run(50, 45, MoveUnit.Seconds);
|
||||
@ -61,6 +61,14 @@ motors.largeB.run(-25)
|
||||
|
||||
## ~
|
||||
|
||||
## Multiple motors
|
||||
|
||||
When using **run** with multiple motors, there is no guarantee that their speed will stay in sync. Use [tank](/reference/motors/tank) or [steer](/reference/motors/steer) for synchronized motor operations.
|
||||
|
||||
```blocks
|
||||
motors.largeBC.run(50)
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### Drive the motor for 20 seconds
|
||||
|
30
docs/reference/motors/motor/set-brake-settle-time.md
Normal file
@ -0,0 +1,30 @@
|
||||
# set Brake Settle Time
|
||||
|
||||
Set the time to wait after a motor stopped to allow it settle
|
||||
when brake is enabled. Default is 10ms.
|
||||
|
||||
```sig
|
||||
motors.largeA.setBrakeSettleTime(200)
|
||||
```
|
||||
|
||||
When a the motor is stopped and brake is applied, it can still wiggle for a little while. You can use the settle time to automatically way after stopping and let the robot settle.
|
||||
|
||||
## Parameters
|
||||
|
||||
* **time**: a [number](/types/number) value which represents the number of milliseconds to wait after braking.
|
||||
|
||||
## Example
|
||||
|
||||
Set the brake mode and the settle time to 500ms. Run the motor connected to port **A** for 2 seconds at a speed of `30` and stop after 2s.
|
||||
|
||||
```blocks
|
||||
motors.largeA.setBrake(true)
|
||||
motors.largeA.setBrakeSettleTime(500)
|
||||
motors.largeA.run(30)
|
||||
pause(2000)
|
||||
motors.largeA.stop()
|
||||
```
|
||||
|
||||
## See also
|
||||
|
||||
[stop](/reference/motors/motor/stop)
|
@ -1,28 +1,28 @@
|
||||
# set Brake
|
||||
|
||||
Set the brake on the motor so it won't turn when it has no power.
|
||||
Set the brake on the motor so it will brake when it finishes a brake command.
|
||||
|
||||
```sig
|
||||
motors.largeA.setBrake(false)
|
||||
```
|
||||
|
||||
When a the motor is stopped, it can still rotate if an external force is applied to it. This can happen, for example, if your're tanking your brick on a inclined surface and stop the motors. Gravity will push down on the brick and might cause it to start rolling again. You can prevent this movement by setting the brake.
|
||||
When a the motor is stopped, it can still rotate if an external force is applied to it. This can happen, for example, if you're tanking your brick on a inclined surface and stop the motors. Gravity will push down on the brick and might cause it to start rolling again. You can prevent this movement by setting the brake.
|
||||
|
||||
Also, you can use the brake to do simple skid steering for your brick.
|
||||
|
||||
## Paramters
|
||||
## Parameters
|
||||
|
||||
* **brake**: a [boolean](/types/boolean) value which is either `true` to set the brake on or `false` to set the brake off.
|
||||
|
||||
## Example
|
||||
|
||||
Run the motor connected to port **A** for 2 seconds at a speed of `30`. Stop and set the brake.
|
||||
Run the motor connected to port **A** for 2 seconds at a speed of `30` and stop after 2s.
|
||||
|
||||
```blocks
|
||||
motors.largeA.setBrake(true)
|
||||
motors.largeA.run(30)
|
||||
pause(2000)
|
||||
motors.largeA.stop()
|
||||
motors.largeA.setBrake(true)
|
||||
```
|
||||
|
||||
## See also
|
||||
|
26
docs/reference/motors/motor/set-run-phase.md
Normal file
@ -0,0 +1,26 @@
|
||||
# Set Run Phase
|
||||
|
||||
Allows to specify an acceleration or deceleration phases for run commands.
|
||||
|
||||
```sig
|
||||
motors.largeD.setRunPhase(MovePhase.Acceleration, 1, MoveUnit.Seconds)
|
||||
```
|
||||
|
||||
Once the run phase is specified on a motor (or pair of motors),
|
||||
it will be automatically applied to [run](/reference/motors/run) commands.
|
||||
|
||||
## Time vs Rotation
|
||||
|
||||
The phases specified for time units (seconds, milliseconds) only apply to run with time
|
||||
moves. Similarly, the phases specified for rotation units (# rotation, degrees) only
|
||||
apply to run with rotation units.
|
||||
|
||||
## Examples
|
||||
|
||||
```blocks
|
||||
motors.largeB.setRunPhase(MovePhase.Acceleration, 0.5, MoveUnit.Seconds)
|
||||
motors.largeB.setRunPhase(MovePhase.Deceleration, 0.2, MoveUnit.Seconds)
|
||||
forever(function () {
|
||||
motors.largeB.run(50, 1, MoveUnit.Seconds)
|
||||
})
|
||||
```
|
25
docs/reference/motors/reset-all.md
Normal 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)
|
@ -22,4 +22,5 @@ motors.stopAll();
|
||||
|
||||
[stop](/reference/motors/motor/stop),
|
||||
[reset](/reference/motors/motor/reset),
|
||||
[reset-all](/reference/motors/motor/reset-all),
|
||||
[set brake](/reference/motors/motor/set-brake)
|
@ -22,7 +22,7 @@ If you decide to use a **value** of rotation distance, you need to choose a type
|
||||
|
||||
## 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.
|
||||
* **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.
|
||||
@ -39,6 +39,13 @@ motors.largeBC.steer(-15, -75)
|
||||
|
||||
## ~
|
||||
|
||||
## ~ hint
|
||||
|
||||
Only one set of synchronized motors will run at the same time. Once you launch tank/steer, it will cancel any existing synchronized speed command.
|
||||
|
||||
## ~
|
||||
|
||||
|
||||
## Examples
|
||||
|
||||
### Make a slight right
|
||||
@ -79,6 +86,51 @@ for (let i = 0; i < 4; i++) {
|
||||
motors.stopAll()
|
||||
```
|
||||
|
||||
### Steer tester
|
||||
|
||||
This program lets you change the values of speed and turn ratio with the buttons.
|
||||
|
||||
```typescript
|
||||
let speed = 0;
|
||||
let turnRatio = 0;
|
||||
|
||||
brick.showString(`steer tester`, 1)
|
||||
brick.showString(`connect motors BC`, 7)
|
||||
brick.showString(`up/down for speed`, 8)
|
||||
brick.showString(`left/right for turn ratio`, 9)
|
||||
|
||||
forever(function () {
|
||||
brick.showString(`motor B speed ${motors.largeB.speed()}%`, 4)
|
||||
brick.showString(`motor C speed ${motors.largeC.speed()}%`, 5)
|
||||
pause(100)
|
||||
})
|
||||
|
||||
function updateSteer() {
|
||||
motors.largeBC.steer(turnRatio, speed);
|
||||
brick.showString(`speed ${speed}%`, 2)
|
||||
brick.showString(`turnRatio ${turnRatio}`, 3)
|
||||
}
|
||||
|
||||
brick.buttonUp.onEvent(ButtonEvent.Pressed, function () {
|
||||
speed += 10
|
||||
updateSteer()
|
||||
})
|
||||
brick.buttonDown.onEvent(ButtonEvent.Pressed, function () {
|
||||
speed -= 10
|
||||
updateSteer()
|
||||
})
|
||||
brick.buttonLeft.onEvent(ButtonEvent.Pressed, function () {
|
||||
turnRatio -= 10
|
||||
updateSteer()
|
||||
})
|
||||
brick.buttonRight.onEvent(ButtonEvent.Pressed, function () {
|
||||
turnRatio += 10
|
||||
updateSteer()
|
||||
})
|
||||
|
||||
updateSteer()
|
||||
```
|
||||
|
||||
## See also
|
||||
|
||||
[tank](/reference/motors/synced/tank), [run](/reference/motors/motor/run)
|
@ -35,6 +35,12 @@ motors.largeBC.tank(-75, -75)
|
||||
|
||||
## ~
|
||||
|
||||
## ~ hint
|
||||
|
||||
Only one set of synchronized motors will run at the same time. Once you launch tank/steer, it will cancel any existing synchronized speed command.
|
||||
|
||||
## ~
|
||||
|
||||
## Examples
|
||||
|
||||
### Tank forward and backward
|
||||
@ -76,6 +82,51 @@ pause(5000)
|
||||
motors.stopAll()
|
||||
```
|
||||
|
||||
### Tank tester
|
||||
|
||||
This program lets you change the tank values using the brick buttons.
|
||||
|
||||
```typescript
|
||||
let tankB = 0;
|
||||
let tankC = 0;
|
||||
|
||||
brick.showString(`tank tester`, 1)
|
||||
brick.showString(`connect motors BC`, 7)
|
||||
brick.showString(`up/down for tank B`, 8)
|
||||
brick.showString(`left/right for tank C`, 9)
|
||||
|
||||
forever(function () {
|
||||
brick.showString(`motor B speed ${motors.largeB.speed()}%`, 4)
|
||||
brick.showString(`motor C speed ${motors.largeC.speed()}%`, 5)
|
||||
pause(100)
|
||||
})
|
||||
|
||||
function updateTank() {
|
||||
brick.showString(`tank A: ${tankB}%`, 2)
|
||||
brick.showString(`tank B: ${tankC}%`, 3)
|
||||
motors.largeBC.tank(tankB, tankC);
|
||||
}
|
||||
|
||||
brick.buttonUp.onEvent(ButtonEvent.Pressed, function () {
|
||||
tankB += 10
|
||||
updateTank();
|
||||
})
|
||||
brick.buttonDown.onEvent(ButtonEvent.Pressed, function () {
|
||||
tankB -= 10
|
||||
updateTank();
|
||||
})
|
||||
brick.buttonRight.onEvent(ButtonEvent.Pressed, function () {
|
||||
tankC += 10
|
||||
updateTank();
|
||||
})
|
||||
brick.buttonLeft.onEvent(ButtonEvent.Pressed, function () {
|
||||
tankC -= 10
|
||||
updateTank();
|
||||
})
|
||||
|
||||
updateTank();
|
||||
```
|
||||
|
||||
## See also
|
||||
|
||||
[steer](/reference/motors/synced/steer), [run](/reference/motors/motor/run)
|
BIN
docs/static/bluetooth/experimental.png
vendored
Normal file
After Width: | Height: | Size: 26 KiB |
BIN
docs/static/fll/fll-big.png
vendored
Normal file
After Width: | Height: | Size: 4.4 KiB |
BIN
docs/static/tutorials/calibrate-gyro.png
vendored
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
docs/static/tutorials/city-shaper/robot1.jpg
vendored
Normal file
After Width: | Height: | Size: 28 KiB |
BIN
docs/static/tutorials/city-shaper/robot2.jpg
vendored
Normal file
After Width: | Height: | Size: 31 KiB |
BIN
docs/static/tutorials/coast-or-brake.png
vendored
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
docs/static/tutorials/drifter.png
vendored
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
docs/static/tutorials/move-straight-with-gyro.png
vendored
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
docs/static/tutorials/move-to-color.png
vendored
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
docs/static/tutorials/pause-on-start.png
vendored
Normal file
After Width: | Height: | Size: 9.9 KiB |
BIN
docs/static/tutorials/pause-until-pressed.png
vendored
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
docs/static/tutorials/pivot-turn.png
vendored
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
docs/static/tutorials/reflected-light-calibration.png
vendored
Normal file
After Width: | Height: | Size: 7.0 KiB |
BIN
docs/static/tutorials/reflected-light-measure.png
vendored
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
docs/static/tutorials/smooth-turn.png
vendored
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
docs/static/tutorials/spin-turn.png
vendored
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
docs/static/tutorials/tank-zigzag.png
vendored
Normal file
After Width: | Height: | Size: 19 KiB |
BIN
docs/static/tutorials/turn-with-gyro.png
vendored
Normal file
After Width: | Height: | Size: 10 KiB |
BIN
docs/static/tutorials/wall-follower.png
vendored
Normal file
After Width: | Height: | Size: 17 KiB |
@ -2,113 +2,51 @@
|
||||
|
||||
Step by step guides to coding your @boardname@.
|
||||
|
||||
## Brick
|
||||
## Tutorials
|
||||
|
||||
```codecard
|
||||
[{
|
||||
"name": "Wake Up!",
|
||||
"description": "Show different moods on the screen. Is it tired, sleepy, or awake?",
|
||||
"cardType": "tutorial",
|
||||
"url":"/tutorials/wake-up",
|
||||
"name": "Brick",
|
||||
"description": "Learn how to use the screen and the buttons",
|
||||
"url":"/tutorials/brick",
|
||||
"imageUrl":"/static/tutorials/wake-up.png"
|
||||
}, {
|
||||
"name": "Make an Animation",
|
||||
"description": "Create a custom animation on your EV3 Brick Display.",
|
||||
"cardType": "tutorial",
|
||||
"url":"/tutorials/make-an-animation",
|
||||
"imageUrl":"/static/tutorials/make-an-animation.png"
|
||||
}, {
|
||||
"name": "What Animal Am I?",
|
||||
"description": "Create different animal effects and have someone guess what the animal is.",
|
||||
"cardType": "tutorial",
|
||||
"url":"/tutorials/what-animal-am-i",
|
||||
"imageUrl":"/static/tutorials/what-animal-am-i.png"
|
||||
}, {
|
||||
"name": "Music Brick",
|
||||
"description": "Transform the brick into a musical instrument!",
|
||||
"cardType": "tutorial",
|
||||
"url":"/tutorials/music-brick",
|
||||
"imageUrl":"/static/tutorials/music-brick.png"
|
||||
}]
|
||||
```
|
||||
|
||||
## Motors
|
||||
|
||||
```codecard
|
||||
[{
|
||||
"name": "Run Motors",
|
||||
"description": "Use the EV3 Brick buttons to start and stop the Large Motor and Medium Motor.",
|
||||
"cardType": "tutorial",
|
||||
"url":"/tutorials/run-motors",
|
||||
"name": "Motors",
|
||||
"description": "User motors to move the brick.",
|
||||
"url":"/tutorials/motors",
|
||||
"imageUrl":"/static/tutorials/run-motors.png"
|
||||
}]
|
||||
```
|
||||
|
||||
## Touch Sensor
|
||||
|
||||
```codecard
|
||||
[{
|
||||
"name": "Touch to Run",
|
||||
"description": "Press the Touch Sensor and run a motor.",
|
||||
"cardType": "tutorial",
|
||||
"url":"/tutorials/touch-to-run",
|
||||
}, {
|
||||
"name": "Touch Sensor",
|
||||
"description": "Use touch sensors in your robot.",
|
||||
"url":"/tutorials/touch-sensor",
|
||||
"imageUrl":"/static/tutorials/touch-to-run.png"
|
||||
}, {
|
||||
"name": "Touch Sensor Values",
|
||||
"description": "Check the value of a Touch Sensor and stop a motor if pressed.",
|
||||
"cardType": "tutorial",
|
||||
"url":"/tutorials/touch-sensor-values",
|
||||
"imageUrl":"/static/tutorials/touch-sensor-values.png"
|
||||
}]
|
||||
```
|
||||
|
||||
## Color Sensor
|
||||
|
||||
```codecard
|
||||
[{
|
||||
"name": "What Color?",
|
||||
"description": "Use the Color Sensor to detect different colors.",
|
||||
"cardType": "tutorial",
|
||||
"url":"/tutorials/what-color",
|
||||
"name": "Color Sensor",
|
||||
"description": "Use the color sensor to follow line or detect colors",
|
||||
"url":"/tutorials/color-sensor",
|
||||
"imageUrl":"/static/tutorials/what-color.png"
|
||||
}, {
|
||||
"name": "Line Following",
|
||||
"description": "Use the Color Sensor to make a robot follow a line.",
|
||||
"cardType": "tutorial",
|
||||
"url":"/tutorials/line-following",
|
||||
"imageUrl":"/static/tutorials/line-following.png"
|
||||
"name": "Gyro",
|
||||
"description": "Drive straight or turn more precisely with the gyro",
|
||||
"url":"/tutorials/gyro",
|
||||
"imageUrl":"/static/tutorials/calibrate-gyro.png"
|
||||
}, {
|
||||
"name": "Red Light, Green Light",
|
||||
"description": "Play Red Light, Green Light using the Color Sensor and the robot.",
|
||||
"cardType": "tutorial",
|
||||
"url":"/tutorials/redlight-greenlight",
|
||||
"imageUrl":"/static/tutorials/redlight-greenlight.png"
|
||||
}]
|
||||
```
|
||||
|
||||
## Infrared Sensor
|
||||
|
||||
```codecard
|
||||
[{
|
||||
"name": "Security Alert",
|
||||
"description": "Build an security alert using the Infrared Sensor.",
|
||||
"cardType": "tutorial",
|
||||
"url":"/tutorials/security-alert",
|
||||
"name": "Ultrasonic Sensor",
|
||||
"description": "Use the ultrasonic sensor to detect obstacles",
|
||||
"url":"/tutorials/ultrasonic-sensor",
|
||||
"imageUrl":"/static/tutorials/object-near.png"
|
||||
}, {
|
||||
"name": "Infrared Sensor",
|
||||
"description": "Use the infrared sensor to detect objects",
|
||||
"url":"/tutorials/infrared-sensor",
|
||||
"imageUrl":"/static/tutorials/security-alert.png"
|
||||
}]
|
||||
```
|
||||
|
||||
## See Also
|
||||
|
||||
[Wake Up!](/tutorials/wake-up),
|
||||
[Make An Animation](/tutorials/make-an-animation),
|
||||
[What Animal Am I?](/tutorials/what-animal-am-i),
|
||||
[Music Brick](/tutorials/music-brick),
|
||||
[Run Motors](/tutorials/run-motors),
|
||||
[Touch to Run](/tutorials/touch-to-run),
|
||||
[Touch Sensor Values](/tutorials/touch-sensor-values),
|
||||
[What Color?](/tutorials/what-color),
|
||||
[Line Following](/tutorials/line-following),
|
||||
[Red Light, Green Light](/tutorials/redlight-greenlight),
|
||||
[Object Near?](/tutorials/object-near),
|
||||
[Security Alert](/tutorials/security-alert)
|
||||
[Brick tutorials](/tutorials/brick),
|
||||
[Motors tutorials](/tutorials/motors),
|
||||
[Touch sensor tutorials](/tutorials/touch-sensor),
|
||||
[Color sensor tutorials](/tutorials/color-sensor),
|
||||
[Infrared sensor tutorials](/tutorials/infrared-sensor)
|
||||
|
44
docs/tutorials/brick.md
Normal file
@ -0,0 +1,44 @@
|
||||
# Brick Tutorials
|
||||
|
||||
## Tutorials
|
||||
|
||||
```codecard
|
||||
[{
|
||||
"name": "Wake Up!",
|
||||
"description": "Show different moods on the screen. Is it tired, sleepy, or awake?",
|
||||
"cardType": "tutorial",
|
||||
"url":"/tutorials/wake-up",
|
||||
"imageUrl":"/static/tutorials/wake-up.png"
|
||||
}, {
|
||||
"name": "Make an Animation",
|
||||
"description": "Create a custom animation on your EV3 Brick Display.",
|
||||
"cardType": "tutorial",
|
||||
"url":"/tutorials/make-an-animation",
|
||||
"imageUrl":"/static/tutorials/make-an-animation.png"
|
||||
}, {
|
||||
"name": "What Animal Am I?",
|
||||
"description": "Create different animal effects and have someone guess what the animal is.",
|
||||
"cardType": "tutorial",
|
||||
"url":"/tutorials/what-animal-am-i",
|
||||
"imageUrl":"/static/tutorials/what-animal-am-i.png"
|
||||
}, {
|
||||
"name": "Music Brick",
|
||||
"description": "Transform the brick into a musical instrument!",
|
||||
"cardType": "tutorial",
|
||||
"url":"/tutorials/music-brick",
|
||||
"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"
|
||||
}]
|
||||
```
|
||||
|
||||
## See Also
|
||||
|
||||
[Wake Up!](/tutorials/wake-up),
|
||||
[Make An Animation](/tutorials/make-an-animation),
|
||||
[What Animal Am I?](/tutorials/what-animal-am-i),
|
||||
[Music Brick](/tutorials/music-brick),
|
37
docs/tutorials/calibrate-gyro.md
Normal 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.
|
54
docs/tutorials/city-shaper.md
Normal 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)
|
||||
|
27
docs/tutorials/city-shaper/crane-mission.md
Normal file
@ -0,0 +1,27 @@
|
||||
# Crane Mission Lessons
|
||||
|
||||
The [Crane Mission Lessons](https://firstinspiresst01.blob.core.windows.net/fll/2020/fll-ev3-overview.pdf) adapted for MakeCode.
|
||||
|
||||
## Lessons
|
||||
|
||||
```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"
|
||||
}, {
|
||||
"name": "Crane Mission / Robot 2",
|
||||
"description": "Program your robot to move in different ways.",
|
||||
"cardType": "tutorial",
|
||||
"url":"/tutorials/city-shaper/robot-2"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
## See Also
|
||||
|
||||
[Robot 1](/tutorials/city-shaper/robot-1),
|
||||
[Robot 2](/tutorials/city-shaper/robot-2)
|
||||
|
20
docs/tutorials/city-shaper/mission-2.md
Normal file
@ -0,0 +1,20 @@
|
||||
# Mission 2 Lesson
|
||||
|
||||
Use the program below to tell your robot how to solve the Crane Mission (Mission 2).
|
||||
|
||||
```blocks
|
||||
brick.buttonEnter.onEvent(ButtonEvent.Pressed, function () {
|
||||
motors.largeBC.steer(0, 25, 2.25, MoveUnit.Rotations)
|
||||
control.timer1.reset()
|
||||
while (control.timer1.seconds() < 1.5) {
|
||||
motors.largeBC.steer(sensors.color1.light(LightIntensityMode.Reflected) - 40, 50)
|
||||
}
|
||||
motors.largeBC.stop()
|
||||
motors.largeBC.steer(0, 15, 0.25, MoveUnit.Rotations)
|
||||
motors.mediumA.run(25, 60, MoveUnit.Degrees)
|
||||
pause(2000)
|
||||
motors.mediumA.run(-25, 1, MoveUnit.Seconds)
|
||||
motors.largeBC.steer(0, -100, 4, MoveUnit.Rotations)
|
||||
})
|
||||
motors.largeBC.setBrake(true)
|
||||
```
|
53
docs/tutorials/city-shaper/robot-1.md
Normal file
@ -0,0 +1,53 @@
|
||||
# Robot 1 Lesson
|
||||
|
||||
## Step 1 - Build Your Driving Base Robot @unplugged
|
||||
|
||||
Build the medium motor robot driving base:
|
||||
|
||||
* [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.
|
||||
|
||||
## Step 2 - Show an image @fullscreen
|
||||
|
||||
At first, it's nice to know that your program is running. Plug in a ``||brick:show mood||`` from the **BRICK** toolbox drawer
|
||||
into the ``||loops:on start||`` block. Change the image to something else if you want!
|
||||
|
||||
```blocks
|
||||
brick.showMood(moods.sleeping)
|
||||
```
|
||||
|
||||
## Step 3 - Try your code @fullscreen
|
||||
|
||||
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.
|
||||
|
||||
## 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||``.
|
||||
Click on the **(+)** symbol and make sure to tell your motors to turn **1** rotation.
|
||||
|
||||
```blocks
|
||||
brick.showMood(moods.sleeping)
|
||||
brick.buttonEnter.pauseUntil(ButtonEvent.Pressed)
|
||||
brick.showMood(moods.neutral)
|
||||
motors.largeBC.steer(0, 50, 1, MoveUnit.Rotations)
|
||||
```
|
||||
|
||||
## 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.
|
||||
When you are ready, click **DOWNLOAD** and follow the instructions to transfer the code into your brick.
|
||||
|
||||
**Remember**: Take the driving base apart at the end of the session so that another group can build their robot too.
|
100
docs/tutorials/city-shaper/robot-2.md
Normal file
@ -0,0 +1,100 @@
|
||||
# Robot 2 Lesson
|
||||
|
||||
## Step 1 - Build Your Driving Base Robot @unplugged
|
||||
|
||||
Build the robot driving base:
|
||||
|
||||
[](https://le-www-live-s.legocdn.com/sc/media/lessons/mindstorms-ev3/building-instructions/ev3-rem-driving-base-79bebfc16bd491186ea9c9069842155e.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.
|
||||
|
||||
## Step 2 - Show an image and move @fullscreen
|
||||
|
||||
Add blocks to the ``||loops:on start||`` block to show an image and move the motors **B+C** for ``1`` rotation.
|
||||
|
||||
```blocks
|
||||
brick.showMood(moods.neutral)
|
||||
motors.largeBC.steer(0, 50, 1, MoveUnit.Rotations)
|
||||
```
|
||||
|
||||
## Step 3 - Brick button @fullscreen
|
||||
|
||||
Let's change the code so that your robot moves when the **UP** button is pressed.
|
||||
Add an ``||brick:on button up||`` block and move ``||motors:steer motors||`` inside of it.
|
||||
After downloading your code, press **UP** to move the robot.
|
||||
|
||||
```blocks
|
||||
brick.buttonUp.onEvent(ButtonEvent.Pressed, function () {
|
||||
brick.showMood(moods.awake)
|
||||
motors.largeBC.steer(0, 50, 1, MoveUnit.Rotations)
|
||||
})
|
||||
brick.showMood(moods.neutral)
|
||||
```
|
||||
|
||||
## Step 4 - Braking @fullscreen
|
||||
|
||||
When the motors are done turning, the robot keeps on moving for a short distance.
|
||||
Turn on the **brakes** so that your robot stops immediately.
|
||||
Drag a ``||motors:set brake||`` block into the ``||loops:on start||`` and set it to **ON** for the the **BC** motors.
|
||||
|
||||
```blocks
|
||||
brick.buttonUp.onEvent(ButtonEvent.Pressed, function () {
|
||||
brick.showMood(moods.awake)
|
||||
motors.largeBC.steer(0, 50, 1, MoveUnit.Rotations)
|
||||
})
|
||||
brick.showMood(moods.neutral)
|
||||
motors.largeBC.setBrake(true)
|
||||
```
|
||||
|
||||
## Step 5 - Left and Right turn @fullscreen
|
||||
|
||||
Let's make the robot turn to the left when the **LEFT** button is pressed on the brick.
|
||||
Find an ``||brick:on button||`` block and put a ``||motors:steer motors||`` in it. Make the turn ratio drive the motor to left.
|
||||
Get another ``||brick:on button||`` and set it to run when the **RIGHT** is pressed.
|
||||
Put a ``||motors:steer motors||`` in for that button and set the turn ratio to drive to the right.
|
||||
|
||||
```blocks
|
||||
brick.buttonLeft.onEvent(ButtonEvent.Pressed, function () {
|
||||
brick.showMood(moods.middleLeft)
|
||||
motors.largeBC.steer(-50, 50, 1, MoveUnit.Rotations)
|
||||
})
|
||||
brick.buttonRight.onEvent(ButtonEvent.Pressed, function () {
|
||||
brick.showMood(moods.middleRight)
|
||||
motors.largeBC.steer(50, 50, 1, MoveUnit.Rotations)
|
||||
})
|
||||
```
|
||||
|
||||
## Step 6 - Backwards @fullscreen
|
||||
|
||||
Let's make the robot go backwards when the **DOWN** button is pressed on the brick.
|
||||
Add a ``||motors:steer motors||`` to an ``||brick:on button||`` block and change the speed to be negative. This will make the motor go backwards.
|
||||
|
||||
```blocks
|
||||
brick.buttonDown.onEvent(ButtonEvent.Pressed, function () {
|
||||
brick.showMood(moods.knockedOut)
|
||||
motors.largeBC.steer(0, -50, 1, MoveUnit.Rotations)
|
||||
})
|
||||
```
|
||||
|
||||
## Step 7 - Add an Ultrasonic sensor @fullscreen
|
||||
|
||||
Add an Ultrasonic sensor to your driving base.
|
||||
|
||||
[](https://le-www-live-s.legocdn.com/sc/media/lessons/mindstorms-ev3/building-instructions/ev3-ultrasonic-sensor-driving-base-61ffdfa461aee2470b8ddbeab16e2070.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.
|
||||
|
||||
## Step 8 - Stopping distance @fullscreen
|
||||
|
||||
Create a program that moves the Driving Base and makes it stop 6 cm from the Cuboid.
|
||||
|
||||
```blocks
|
||||
brick.buttonEnter.onEvent(ButtonEvent.Pressed, function () {
|
||||
brick.showMood(moods.dizzy)
|
||||
motors.largeBC.steer(0, 50)
|
||||
pauseUntil(() => sensors.ultrasonic4.distance() < 6)
|
||||
motors.largeBC.stop()
|
||||
})
|
||||
```
|
||||
|
||||
Try sending your robot towards a wall!
|
16
docs/tutorials/coast-or-brake.md
Normal file
@ -0,0 +1,16 @@
|
||||
# Coast or Brake
|
||||
|
||||
This code example will set the brake when button **A** is pressed or let the motor coast (turn freely when not running) when button **B** is pressed. The motor is turned by one rotation to cause motion.
|
||||
|
||||
```blocks
|
||||
brick.buttonLeft.onEvent(ButtonEvent.Pressed, function () {
|
||||
// tell motor to brake once the run command is done
|
||||
motors.largeB.setBrake(true)
|
||||
motors.largeB.run(100, 1, MoveUnit.Rotations)
|
||||
})
|
||||
brick.buttonRight.onEvent(ButtonEvent.Pressed, function () {
|
||||
// tell motor to coast once the run command is done
|
||||
motors.largeB.setBrake(false)
|
||||
motors.largeB.run(100, 1, MoveUnit.Rotations)
|
||||
})
|
||||
```
|
49
docs/tutorials/color-sensor.md
Normal file
@ -0,0 +1,49 @@
|
||||
# Color Sensor
|
||||
|
||||
## Tutorials
|
||||
|
||||
```codecard
|
||||
[{
|
||||
"name": "What Color?",
|
||||
"description": "Use the Color Sensor to detect different colors.",
|
||||
"cardType": "tutorial",
|
||||
"url":"/tutorials/what-color",
|
||||
"imageUrl":"/static/tutorials/what-color.png"
|
||||
}, {
|
||||
"name": "Line Following",
|
||||
"description": "Use the Color Sensor to make a robot follow a line.",
|
||||
"cardType": "tutorial",
|
||||
"url":"/tutorials/line-following",
|
||||
"imageUrl":"/static/tutorials/line-following.png"
|
||||
}, {
|
||||
"name": "Red Light, Green Light",
|
||||
"description": "Play Red Light, Green Light using the Color Sensor and the robot.",
|
||||
"cardType": "tutorial",
|
||||
"url":"/tutorials/redlight-greenlight",
|
||||
"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",
|
||||
"description": "Teach the sensor what light or dark is.",
|
||||
"cardType": "example",
|
||||
"url":"/tutorials/reflected-light-measure",
|
||||
"imageUrl":"/static/tutorials/reflected-light-measure.png"
|
||||
}, {
|
||||
"name": "Reflected Light Calibration",
|
||||
"description": "Use the auto-calibration feature to setup the dark and bright values.",
|
||||
"cardType": "example",
|
||||
"url":"/tutorials/reflected-light-calibration",
|
||||
"imageUrl":"/static/tutorials/reflected-light-calibration.png"
|
||||
}]
|
||||
```
|
||||
|
||||
## See Also
|
||||
|
||||
[What Color?](/tutorials/what-color),
|
||||
[Line Following](/tutorials/line-following),
|
||||
[Red Light, Green Light](/tutorials/redlight-greenlight),
|
34
docs/tutorials/drifter.md
Normal 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
@ -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"
|
||||
}]
|
||||
```
|
17
docs/tutorials/infrared-sensor.md
Normal file
@ -0,0 +1,17 @@
|
||||
# Infrared sensor
|
||||
|
||||
## Tutorials
|
||||
|
||||
```codecard
|
||||
[{
|
||||
"name": "Security Alert",
|
||||
"description": "Build an security alert using the Infrared Sensor.",
|
||||
"cardType": "tutorial",
|
||||
"url":"/tutorials/security-alert",
|
||||
"imageUrl":"/static/tutorials/security-alert.png"
|
||||
}]
|
||||
```
|
||||
|
||||
## See Also
|
||||
|
||||
[Security Alert](/tutorials/security-alert)
|
58
docs/tutorials/motors.md
Normal file
@ -0,0 +1,58 @@
|
||||
# Motors
|
||||
|
||||
## Tutorials
|
||||
|
||||
```codecard
|
||||
[{
|
||||
"name": "Run Motors",
|
||||
"description": "Use the EV3 Brick buttons to start and stop the Large Motor and Medium Motor.",
|
||||
"cardType": "tutorial",
|
||||
"url":"/tutorials/run-motors",
|
||||
"imageUrl":"/static/tutorials/run-motors.png"
|
||||
}, {
|
||||
"name": "Spin Turn",
|
||||
"description": "Turn the driving base around its center.",
|
||||
"cardType": "example",
|
||||
"url":"/tutorials/spin-turn",
|
||||
"imageUrl":"/static/tutorials/spin-turn.png"
|
||||
}, {
|
||||
"name": "Pivot Turn",
|
||||
"description": "Turn the driving base around a wheel.",
|
||||
"cardType": "example",
|
||||
"url":"/tutorials/pivot-turn",
|
||||
"imageUrl":"/static/tutorials/pivot-turn.png"
|
||||
}, {
|
||||
"name": "Smooth Turn",
|
||||
"description": "Turn the driving base in a smooth, steering motion.",
|
||||
"cardType": "example",
|
||||
"url":"/tutorials/smooth-turn",
|
||||
"imageUrl":"/static/tutorials/smooth-turn.png"
|
||||
}, {
|
||||
"name": "Tank ZigZag",
|
||||
"description": "Use the tank block to keep motors in sync.",
|
||||
"cardType": "example",
|
||||
"url":"/tutorials/tank-zigzag",
|
||||
"imageUrl":"/static/tutorials/tank-zigzag.png"
|
||||
}, {
|
||||
"name": "Coast Or Brake",
|
||||
"description": "Tell motors to coast or brake once the run command is done.",
|
||||
"cardType": "example",
|
||||
"url":"/tutorials/coast-or-brake",
|
||||
"imageUrl":"/static/tutorials/coast-or-brake.png"
|
||||
}, {
|
||||
"name": "Turtle",
|
||||
"description": "Encode moves and run them on a driving base",
|
||||
"url":"/tutorials/turtle",
|
||||
"cardType": "example"
|
||||
}]
|
||||
```
|
||||
|
||||
## See Also
|
||||
|
||||
[Run Motors](/tutorials/run-motors),
|
||||
[Spin Turn](/tutorials/spin-turn),
|
||||
[Pivot Turn](/tutorials/pivot-turn),
|
||||
[Smooth Turn](/tutorials/smooth-turn),
|
||||
[Tank ZigZag](/tutorials/tank-zigzag),
|
||||
[Coast Or Brake](/tutorials/coast-or-brake),
|
||||
[Turtle](/tutorials/turtle)
|
61
docs/tutorials/move-straight-with-gyro.md
Normal 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).
|
65
docs/tutorials/move-to-color.md
Normal 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!
|
33
docs/tutorials/pause-on-start.md
Normal 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)
|
||||
```
|
9
docs/tutorials/pause-until-pressed.md
Normal file
@ -0,0 +1,9 @@
|
||||
# Pause Until Pressed
|
||||
|
||||
This is a code example to detect contact or collision with another object. It uses a touch sensor to detect hitting a wall or other obstacle. The motors are run and then stopped when the sensor is pressed.
|
||||
|
||||
```blocks
|
||||
motors.largeBC.tank(50, 50)
|
||||
sensors.touch1.pauseUntil(ButtonEvent.Pressed)
|
||||
motors.largeBC.stop()
|
||||
```
|
12
docs/tutorials/pivot-turn.md
Normal file
@ -0,0 +1,12 @@
|
||||
# Pivot Turn
|
||||
|
||||
A **pivot turn** happens when a [EV3 Driving Base](https://le-www-live-s.legocdn.com/sc/media/lessons/mindstorms-ev3/building-instructions/ev3-rem-driving-base-79bebfc16bd491186ea9c9069842155e.pdf) turns around the wheel on the inside of the turn by spinning just the single wheel at the outside of the turn.
|
||||
|
||||
You can make a turn happen with either a ``||motors:tank||`` or a ``||motors:steer||`` block.
|
||||
|
||||
```blocks
|
||||
forever(function() {
|
||||
motors.largeBC.tank(50, 0, 2, MoveUnit.Rotations)
|
||||
motors.largeBC.tank(0, 50, 2, MoveUnit.Rotations)
|
||||
})
|
||||
```
|
24
docs/tutorials/reflected-light-calibration.md
Normal file
@ -0,0 +1,24 @@
|
||||
# Reflected light calibration
|
||||
|
||||
The ``||sensors:calibrateLight||`` blocks allows you to calibrate the reflected light of the color sensor in one block. At the time you run the block, move the sensor over a dark surface and a bright surface; then stop moving it.
|
||||
|
||||
```blocks
|
||||
sensors.color3.onLightDetected(LightIntensityMode.Reflected, Light.Dark, function () {
|
||||
brick.showString("dark", 2)
|
||||
})
|
||||
sensors.color3.onLightDetected(LightIntensityMode.Reflected, Light.Bright, function () {
|
||||
brick.showString("bright", 2)
|
||||
})
|
||||
console.sendToScreen()
|
||||
console.log("move color sensor")
|
||||
console.log("over DARK and BRIGHT color")
|
||||
console.log("and stop moving when done")
|
||||
console.log("press ENTER when ready")
|
||||
brick.buttonEnter.pauseUntil(ButtonEvent.Pressed)
|
||||
sensors.color3.calibrateLight(LightIntensityMode.Reflected)
|
||||
brick.showValue("dark", sensors.color3.threshold(Light.Dark), 4)
|
||||
brick.showValue("bright", sensors.color3.threshold(Light.Bright), 5)
|
||||
forever(function () {
|
||||
brick.showValue("reflected light", sensors.color3.light(LightIntensityMode.Reflected), 1)
|
||||
})
|
||||
```
|
29
docs/tutorials/reflected-light-measure.md
Normal file
@ -0,0 +1,29 @@
|
||||
# Reflected light measure
|
||||
|
||||
This example uses a color sensor to measure the reflected light from a dark and light surface
|
||||
and sets the light/dark thresholds.
|
||||
|
||||
```blocks
|
||||
sensors.color3.onLightDetected(LightIntensityMode.Reflected, Light.Dark, function () {
|
||||
brick.showString("dark", 2)
|
||||
})
|
||||
sensors.color3.onLightDetected(LightIntensityMode.Reflected, Light.Bright, function () {
|
||||
brick.showString("bright", 2)
|
||||
})
|
||||
console.sendToScreen()
|
||||
console.log("move color sensor")
|
||||
console.log("over DARK color")
|
||||
console.log("press ENTER when ready")
|
||||
brick.buttonEnter.pauseUntil(ButtonEvent.Pressed)
|
||||
sensors.color3.setThreshold(Light.Dark, sensors.color3.light(LightIntensityMode.Reflected) + 5)
|
||||
console.logValue("dark", sensors.color3.threshold(Light.Dark))
|
||||
console.log("move color sensor")
|
||||
console.log("over BRIGHT color")
|
||||
console.log("press ENTER when ready")
|
||||
brick.buttonEnter.pauseUntil(ButtonEvent.Pressed)
|
||||
sensors.color3.setThreshold(Light.Bright, sensors.color3.light(LightIntensityMode.Reflected) - 5)
|
||||
console.logValue("bright", sensors.color3.threshold(Light.Bright))
|
||||
forever(function () {
|
||||
brick.showValue("reflected light", sensors.color3.light(LightIntensityMode.Reflected), 1)
|
||||
})
|
||||
```
|
12
docs/tutorials/smooth-turn.md
Normal file
@ -0,0 +1,12 @@
|
||||
# Smooth Turn
|
||||
|
||||
A **smooth turn** happens when a [EV3 Driving Base](https://le-www-live-s.legocdn.com/sc/media/lessons/mindstorms-ev3/building-instructions/ev3-rem-driving-base-79bebfc16bd491186ea9c9069842155e.pdf) makes a turn by spinning both both wheels but with each running at a different speed.
|
||||
|
||||
You can make a turn happen with either a ``||motors:tank||`` or a ``||motors:steer||`` block.
|
||||
|
||||
```blocks
|
||||
forever(function() {
|
||||
motors.largeBC.tank(50, 20, 2, MoveUnit.Rotations)
|
||||
motors.largeBC.tank(20, 50, 2, MoveUnit.Rotations)
|
||||
})
|
||||
```
|
12
docs/tutorials/spin-turn.md
Normal file
@ -0,0 +1,12 @@
|
||||
# Spin Turn
|
||||
|
||||
A **spin turn** happens when a [EV3 Driving Base](https://le-www-live-s.legocdn.com/sc/media/lessons/mindstorms-ev3/building-instructions/ev3-rem-driving-base-79bebfc16bd491186ea9c9069842155e.pdf) turns, or rotates, on a single spot by spinning both wheels, but with each turning in opposite directions.
|
||||
|
||||
You can make a turn happen with either a ``||motors:tank||`` or a ``||motors:steer||`` block.
|
||||
|
||||
```blocks
|
||||
forever(function() {
|
||||
motors.largeBC.tank(50, -50, 2, MoveUnit.Rotations)
|
||||
motors.largeBC.tank(-50, 50, 2, MoveUnit.Rotations)
|
||||
})
|
||||
```
|
9
docs/tutorials/stop-at-object.md
Normal file
@ -0,0 +1,9 @@
|
||||
# Stop At Object
|
||||
|
||||
This is a code example to detect contact or collision with another object. It uses a touch sensor to detect hitting a wall or other obstacle. The motors are run and then stopped when the sensor is pressed.
|
||||
|
||||
```blocks
|
||||
motors.largeBC.tank(50, 50)
|
||||
sensors.touch1.pauseUntil(ButtonEvent.Pressed)
|
||||
motors.largeBC.stop()
|
||||
```
|
17
docs/tutorials/tank-zigzag.md
Normal file
@ -0,0 +1,17 @@
|
||||
# Tank ZigZag
|
||||
|
||||
This example shows how to use the [tank](/reference/motors/tank) block to keep the speed of 2 large motors synchronized. The [EV3 Driving Base](https://le-www-live-s.legocdn.com/sc/media/lessons/mindstorms-ev3/building-instructions/ev3-rem-driving-base-79bebfc16bd491186ea9c9069842155e.pdf)
|
||||
) will move in a zig zag pattern.
|
||||
|
||||
```blocks
|
||||
/**
|
||||
* Use the tank block to keep large motors synched.
|
||||
Use this code with a EV3 driving base.
|
||||
*/
|
||||
forever(function () {
|
||||
brick.showImage(images.eyesMiddleRight)
|
||||
motors.largeBC.tank(50, 10, 2, MoveUnit.Rotations)
|
||||
brick.showImage(images.eyesMiddleLeft)
|
||||
motors.largeBC.tank(10, 50, 2, MoveUnit.Rotations)
|
||||
})
|
||||
```
|
31
docs/tutorials/touch-sensor.md
Normal file
@ -0,0 +1,31 @@
|
||||
# Touch Sensor
|
||||
|
||||
## Tutorials
|
||||
|
||||
```codecard
|
||||
[{
|
||||
"name": "Touch to Run",
|
||||
"description": "Press the Touch Sensor and run a motor.",
|
||||
"cardType": "tutorial",
|
||||
"url":"/tutorials/touch-to-run",
|
||||
"imageUrl":"/static/tutorials/touch-to-run.png"
|
||||
}, {
|
||||
"name": "Sensor Values",
|
||||
"description": "Check the value of a Touch Sensor and stop a motor if pressed.",
|
||||
"cardType": "tutorial",
|
||||
"url":"/tutorials/touch-sensor-values",
|
||||
"imageUrl":"/static/tutorials/touch-sensor-values.png"
|
||||
}, {
|
||||
"name": "Stop At Object",
|
||||
"description": "Waits for the sensor to be pressed before continuing the program",
|
||||
"cardType": "tutorial",
|
||||
"url":"/tutorials/stop-at-object",
|
||||
"imageUrl":"/static/tutorials/pause-until-pressed.png"
|
||||
}]
|
||||
```
|
||||
|
||||
## See Also
|
||||
|
||||
[Touch to Run](/tutorials/touch-to-run),
|
||||
[Touch Sensor Values](/tutorials/touch-sensor-values),
|
||||
[Stop At Object](/tutorials/stop-at-object)
|
43
docs/tutorials/turn-with-gyro.md
Normal 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()
|
||||
```
|
52
docs/tutorials/turtle.md
Normal file
@ -0,0 +1,52 @@
|
||||
# Turtle
|
||||
|
||||
A fun interactive program where the user enters a sequence of moves using the buttons and the robot executes it.
|
||||
|
||||
|
||||
```blocks
|
||||
/**
|
||||
* Run this program with a driving base.
|
||||
**/
|
||||
let indent = ""
|
||||
let command = ""
|
||||
let c = ""
|
||||
brick.buttonLeft.onEvent(ButtonEvent.Pressed, function () {
|
||||
command = command + "L"
|
||||
})
|
||||
brick.buttonRight.onEvent(ButtonEvent.Pressed, function () {
|
||||
command = command + "R"
|
||||
})
|
||||
brick.buttonUp.onEvent(ButtonEvent.Pressed, function () {
|
||||
command = command + "F"
|
||||
})
|
||||
brick.buttonDown.onEvent(ButtonEvent.Pressed, function () {
|
||||
command = command + "B"
|
||||
})
|
||||
brick.buttonEnter.onEvent(ButtonEvent.Pressed, function () {
|
||||
indent = ""
|
||||
for (let index = 0; index <= command.length; index++) {
|
||||
c = command[index]
|
||||
brick.showString("" + indent + c, 4)
|
||||
indent = "" + indent + " "
|
||||
if (c == "L") {
|
||||
motors.largeBC.steer(-100, 50, 378, MoveUnit.Degrees)
|
||||
} else if (c == "R") {
|
||||
motors.largeBC.steer(100, 50, 378, MoveUnit.Degrees)
|
||||
} else if (c == "F") {
|
||||
motors.largeBC.steer(0, 50, 1, MoveUnit.Rotations)
|
||||
} else if (c == "B") {
|
||||
motors.largeBC.steer(0, -50, 1, MoveUnit.Rotations)
|
||||
}
|
||||
}
|
||||
command = ""
|
||||
brick.showString("", 2)
|
||||
})
|
||||
motors.largeBC.setBrake(true)
|
||||
forever(function () {
|
||||
brick.showString("TURTLE", 1)
|
||||
brick.showString(command, 3)
|
||||
brick.showString("up/down: forward/backward", 8)
|
||||
brick.showString("left/right: turn", 9)
|
||||
brick.showString("enter: play commands", 10)
|
||||
})
|
||||
```
|
24
docs/tutorials/ultrasonic-sensor.md
Normal file
@ -0,0 +1,24 @@
|
||||
# Infrared sensor
|
||||
|
||||
## Tutorials
|
||||
|
||||
```codecard
|
||||
[{
|
||||
"name": "Object Near",
|
||||
"description": "Detect if objects are near.",
|
||||
"cardType": "tutorial",
|
||||
"url":"/tutorials/object-near",
|
||||
"imageUrl":"/static/tutorials/object-near.png"
|
||||
}, {
|
||||
"name": "Wall Follower",
|
||||
"description": "Follow a wall at a distance using the ultrasonic sensor and a proportional controller.",
|
||||
"cardType": "tutorial",
|
||||
"url":"/tutorials/wall-follower",
|
||||
"imageUrl":"/static/tutorials/wall-follower.png"
|
||||
}]
|
||||
```
|
||||
|
||||
## See Also
|
||||
|
||||
[Object Near?](/tutorials/object-near),
|
||||
[Wall Follower](/tutorials/wall-follower)
|
191
docs/tutorials/wall-follower.md
Normal file
@ -0,0 +1,191 @@
|
||||
# Wall Follower
|
||||
|
||||
## Introduction @unplugged
|
||||
|
||||
This tutorial shows you how to use the ultrasonic sensor to
|
||||
move a [EV3 Driving Base](https://le-www-live-s.legocdn.com/sc/media/lessons/mindstorms-ev3/building-instructions/ev3-rem-driving-base-79bebfc16bd491186ea9c9069842155e.pdf)
|
||||
along a wall.
|
||||
|
||||
Your ultrasonic sensor should be placed horizontally, near the driving wheel, facing the wall.
|
||||
|
||||
## Step 1 Measure distance
|
||||
|
||||
Declare a new variable ``distance`` and store the distance from
|
||||
the ultrasonic sensor on port 4.
|
||||
|
||||
```blocks
|
||||
let distance = 0
|
||||
forever(function () {
|
||||
distance = sensors.ultrasonic4.distance()
|
||||
})
|
||||
```
|
||||
|
||||
## Step 2 Show distance
|
||||
|
||||
Use a ``||brick:show value||`` block to display the distance value on the screen.
|
||||
This is **very** helpful when you are debugging your code on the robot.
|
||||
|
||||
Once your code is ready, download it to your robot and check that the measured distance looks ok.
|
||||
|
||||
```blocks
|
||||
let distance = 0
|
||||
forever(function () {
|
||||
distance = sensors.ultrasonic4.distance()
|
||||
brick.showValue("distance", distance, 1)
|
||||
})
|
||||
```
|
||||
|
||||
## Step 3 Goal
|
||||
|
||||
Declare a new variable ``goal`` and assign it to ``10`` in ``on start``.
|
||||
The value should be the distance in centimeters between your robot and the wall.
|
||||
|
||||
```blocks
|
||||
let goal = 0
|
||||
goal = 10
|
||||
```
|
||||
|
||||
## Step 4 Compute Error
|
||||
|
||||
Declare a new variable ``error`` and assign a difference between ``distance`` and ``goal``.
|
||||
We will use this value to determine how much the robot needs to correct its trajectory.
|
||||
|
||||
```blocks
|
||||
let distance = 0
|
||||
let goal = 0
|
||||
let error = 0
|
||||
goal = 10
|
||||
forever(function () {
|
||||
distance = sensors.ultrasonic4.distance()
|
||||
brick.showValue("distance", distance, 1)
|
||||
error = distance - goal
|
||||
brick.showValue("error", error, 2)
|
||||
})
|
||||
```
|
||||
|
||||
## Step 5 Show Error
|
||||
|
||||
Just like ``distance``, use ``||brick:show value||`` to display the value of the error (line 2).
|
||||
This will allow you to debug your code while it is running on the robot.
|
||||
|
||||
Download your program to the robot and check that the error goes to ``0`` when
|
||||
the robot is around 10cm from the wall.
|
||||
|
||||
```blocks
|
||||
let distance = 0
|
||||
let goal = 0
|
||||
let error = 0
|
||||
goal = 10
|
||||
forever(function () {
|
||||
distance = sensors.ultrasonic4.distance()
|
||||
brick.showValue("distance", distance, 1)
|
||||
error = distance - goal
|
||||
brick.showValue("error", error, 2)
|
||||
})
|
||||
```
|
||||
|
||||
## Step 6 Kp
|
||||
|
||||
Declare a new variable ``kp`` and assign it to ``1``.
|
||||
This number determines how to convert the error into a ``turn ratio`` for the steer block.
|
||||
For starter, set it to 1 and we will go through the steps to tune its value later on.
|
||||
As usual, also use ``||brick:show value||`` to display the value of ``kp`` on the screen (line 3).
|
||||
|
||||
```blocks
|
||||
let distance = 0
|
||||
let goal = 0
|
||||
let error = 0
|
||||
let kp = 0
|
||||
goal = 10
|
||||
kp = 1
|
||||
forever(function () {
|
||||
distance = sensors.ultrasonic4.distance()
|
||||
brick.showValue("distance", distance, 1)
|
||||
error = distance - goal
|
||||
brick.showValue("error", error, 2)
|
||||
brick.showValue("kp", kp, 3)
|
||||
})
|
||||
```
|
||||
|
||||
## Step 7 Turn ratio
|
||||
|
||||
Declare a new variable ``turnratio`` and store the product of ``error`` and ``kp`` in it.
|
||||
Also use ``||brick:show value||`` to display its value on screen.
|
||||
|
||||
Download the program on the robot and try moving the robot around the wall. You should see
|
||||
the value of ``turnratio`` change similarly to ``error``.
|
||||
|
||||
```blocks
|
||||
let distance = 0
|
||||
let goal = 0
|
||||
let error = 0
|
||||
let kp = 0
|
||||
let turnratio = 0
|
||||
goal = 10
|
||||
kp = 1
|
||||
forever(function () {
|
||||
distance = sensors.ultrasonic4.distance()
|
||||
brick.showValue("distance", distance, 1)
|
||||
error = distance - goal
|
||||
brick.showValue("error", error, 2)
|
||||
brick.showValue("kp", kp, 3)
|
||||
turnratio = error * kp
|
||||
brick.showValue("turn", turnratio, 4)
|
||||
})
|
||||
```
|
||||
|
||||
## Step 8 Steering
|
||||
|
||||
Add a ``||motors:steer motors||`` block for ``large B+C`` at 35% and place the ``turnratio``
|
||||
variable for the turn value.
|
||||
|
||||
Download the code to your robot and try it out. Does it follow the wall?...
|
||||
Not really, this is because we need to tune the ``kp`` variable.
|
||||
|
||||
```blocks
|
||||
let distance = 0
|
||||
let goal = 0
|
||||
let error = 0
|
||||
let kp = 0
|
||||
let turnratio = 0
|
||||
goal = 10
|
||||
kp = 1
|
||||
forever(function () {
|
||||
distance = sensors.ultrasonic4.distance()
|
||||
brick.showValue("distance", distance, 1)
|
||||
error = distance - goal
|
||||
brick.showValue("error", error, 2)
|
||||
brick.showValue("kp", kp, 3)
|
||||
turnratio = error * kp
|
||||
brick.showValue("turn", turnratio, 4)
|
||||
motors.largeBC.steer(turnratio, 35)
|
||||
})
|
||||
```
|
||||
|
||||
## Step 9 Tuning kp
|
||||
|
||||
As mentioned in a previous step, we need to find the right value for kp so that the robot
|
||||
follows the wall properly. This tuning can be tedious so we are going to the brick buttons
|
||||
to speed up the process.
|
||||
|
||||
Add ``||brick:on button||`` blocks to handle the left and right button pressed. When left is pressed, change ``kp`` by ``-1``. When right is pressed, change ``kp`` by 1.
|
||||
|
||||
Download your code to the robot and change the values of ``kp`` until the robot follows the wall. (Tip try something around -5 / -10).
|
||||
|
||||
```blocks
|
||||
let kp = 0
|
||||
brick.buttonLeft.onEvent(ButtonEvent.Pressed, function () {
|
||||
kp += -1
|
||||
})
|
||||
brick.buttonRight.onEvent(ButtonEvent.Pressed, function () {
|
||||
kp += 1
|
||||
})
|
||||
```
|
||||
|
||||
## Step 10 @unplugged
|
||||
|
||||
Well done! Your robot is using the ultrasonic distance
|
||||
to correct is trajectory using a proportional controller!
|
||||
|
||||
The robot will be more precise if it goes slow... Try using a variable
|
||||
and the brick up and down events to control the speed as well.
|
273
editor/deploy.ts
@ -2,72 +2,236 @@
|
||||
/// <reference path="../node_modules/pxt-core/built/pxtsim.d.ts"/>
|
||||
|
||||
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() {
|
||||
return initAsync()
|
||||
return initHidAsync()
|
||||
.then(w => w.downloadFileAsync("/tmp/dmesg.txt", v => console.log(pxt.Util.uint8ArrayToString(v))))
|
||||
}
|
||||
|
||||
function hf2Async() {
|
||||
return pxt.HF2.mkPacketIOAsync()
|
||||
.then(h => {
|
||||
let w = new pxt.editor.Ev3Wrapper(h)
|
||||
ev3 = w
|
||||
return w.reconnectAsync(true)
|
||||
.then(() => w)
|
||||
})
|
||||
|
||||
// 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>;
|
||||
}
|
||||
|
||||
let noHID = false
|
||||
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;
|
||||
|
||||
let initPromise: Promise<pxt.editor.Ev3Wrapper>
|
||||
export function initAsync() {
|
||||
if (initPromise)
|
||||
return initPromise
|
||||
constructor(private port: SerialPort, private options: SerialOptions) {
|
||||
}
|
||||
|
||||
let canHID = false
|
||||
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() {
|
||||
const pktIOAsync: Promise<pxt.HF2.PacketIO> = useWebSerial
|
||||
? WebSerialPackageIO.mkPacketIOAsync() : pxt.HF2.mkPacketIOAsync()
|
||||
return pktIOAsync.then(h => {
|
||||
let w = new Ev3Wrapper(h)
|
||||
ev3 = w
|
||||
return w.reconnectAsync(true)
|
||||
.then(() => w)
|
||||
})
|
||||
}
|
||||
|
||||
let useHID = false;
|
||||
let useWebSerial = false;
|
||||
export function initAsync(): Promise<void> {
|
||||
if (pxt.U.isNodeJS) {
|
||||
// doesn't seem to work ATM
|
||||
canHID = false
|
||||
useHID = false
|
||||
} else {
|
||||
const forceHexDownload = /forceHexDownload/i.test(window.location.href);
|
||||
if (pxt.Cloud.isLocalHost() && pxt.Cloud.localToken && !forceHexDownload)
|
||||
canHID = true
|
||||
const nodehid = /nodehid/i.test(window.location.href);
|
||||
if (pxt.Cloud.isLocalHost() && pxt.Cloud.localToken && nodehid)
|
||||
useHID = true;
|
||||
}
|
||||
|
||||
if (noHID)
|
||||
canHID = false
|
||||
if (WebSerialPackageIO.isSupported())
|
||||
pxt.tickEvent("webserial.supported");
|
||||
|
||||
if (canHID) {
|
||||
initPromise = hf2Async()
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
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 => {
|
||||
console.error(err);
|
||||
initPromise = null
|
||||
noHID = true
|
||||
return Promise.reject(err)
|
||||
useHID = false;
|
||||
useWebSerial = false;
|
||||
return Promise.reject(err);
|
||||
})
|
||||
} else {
|
||||
noHID = true
|
||||
useHID = false
|
||||
useWebSerial = false;
|
||||
initPromise = Promise.reject(new Error("no HID"))
|
||||
}
|
||||
|
||||
return initPromise
|
||||
return initPromise;
|
||||
}
|
||||
|
||||
// this comes from aux/pxt.lms
|
||||
const fspath = "../prjs/BrkProg_SAVE/"
|
||||
const rbfTemplate = `
|
||||
4c45474f580000006d000100000000001c000000000000000e000000821b038405018130813e8053
|
||||
74617274696e672e2e2e0084006080XX00448581644886488405018130813e80427965210084000a
|
||||
`
|
||||
export function deployCoreAsync(resp: pxtc.CompileResult) {
|
||||
let w: pxt.editor.Ev3Wrapper
|
||||
|
||||
let filename = resp.downloadFileBaseName || "pxt"
|
||||
filename = filename.replace(/^lego-/, "")
|
||||
|
||||
let fspath = "../prjs/BrkProg_SAVE/"
|
||||
|
||||
let elfPath = fspath + filename + ".elf"
|
||||
let rbfPath = fspath + filename + ".rbf"
|
||||
|
||||
@ -107,27 +271,54 @@ export function deployCoreAsync(resp: pxtc.CompileResult) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
if (noHID) return saveUF2Async()
|
||||
if (!useHID) return saveUF2Async()
|
||||
|
||||
return initAsync()
|
||||
pxt.tickEvent("webserial.flash");
|
||||
let w: Ev3Wrapper;
|
||||
return initHidAsync()
|
||||
.then(w_ => {
|
||||
w = w_
|
||||
if (w.isStreaming)
|
||||
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.flashAsync(elfPath, UF2.readBytes(origElfUF2, 0, origElfUF2.length * 256)))
|
||||
.then(() => w.flashAsync(rbfPath, rbfBIN))
|
||||
.then(() => w.runAsync(rbfPath))
|
||||
.then(() => Promise.delay(500))
|
||||
.then(() => {
|
||||
pxt.tickEvent("webserial.success");
|
||||
return w.disconnectAsync()
|
||||
//return Promise.delay(1000).then(() => w.dmesgAsync())
|
||||
}).catch(e => {
|
||||
// if we failed to initalize, retry
|
||||
if (noHID)
|
||||
return saveUF2Async()
|
||||
else
|
||||
return Promise.reject(e)
|
||||
pxt.tickEvent("webserial.fail");
|
||||
useHID = false;
|
||||
useWebSerial = false;
|
||||
// if we failed to initalize, tell the user to retry
|
||||
return Promise.reject(e)
|
||||
})
|
||||
}
|
||||
|
@ -1,28 +1,28 @@
|
||||
/// <reference path="../node_modules/pxt-core/built/pxteditor.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> {
|
||||
const projectView = opts.projectView;
|
||||
pxt.debug('loading pxt-ev3 target extensions...')
|
||||
|
||||
function enableWebSerialAndCompileAsync() {
|
||||
return enableWebSerialAsync()
|
||||
.then(() => Promise.delay(500))
|
||||
.then(() => projectView.compile());
|
||||
}
|
||||
|
||||
const res: pxt.editor.ExtensionResult = {
|
||||
deployCoreAsync,
|
||||
showUploadInstructionsAsync: (fn: string, url: string, confirmAsync: (options: any) => Promise<number>) => {
|
||||
let resolve: (thenableOrResult?: void | PromiseLike<void>) => void;
|
||||
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 || "???";
|
||||
|
||||
setConfirmAsync(confirmAsync);
|
||||
// https://msdn.microsoft.com/en-us/library/cc848897.aspx
|
||||
// "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"
|
||||
const downloadAgain = !pxt.BrowserUtils.isIE() && !pxt.BrowserUtils.isEdge();
|
||||
const docUrl = pxt.appTarget.appTheme.usbDocs;
|
||||
const saveAs = pxt.BrowserUtils.hasSaveAs();
|
||||
|
||||
const htmlBody = `
|
||||
<div class="ui grid stackable">
|
||||
@ -84,7 +84,38 @@ pxt.editor.initExtensionsAsync = function (opts: pxt.editor.ExtensionOptions): P
|
||||
hideAgree: false,
|
||||
agreeLbl: lf("I got it"),
|
||||
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,
|
||||
icon: "download",
|
||||
className: "lightgrey focused",
|
||||
|
535
editor/wrap.ts
@ -1,285 +1,286 @@
|
||||
namespace pxt.editor {
|
||||
import HF2 = pxt.HF2
|
||||
import U = pxt.U
|
||||
/**
|
||||
* See https://www.lego.com/cdn/cs/set/assets/blt6879b00ae6951482/LEGO_MINDSTORMS_EV3_Communication_Developer_Kit.pdf
|
||||
* 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) {
|
||||
pxt.log("EWRAP: " + msg)
|
||||
function log(msg: string) {
|
||||
pxt.log("serial: " + msg)
|
||||
}
|
||||
|
||||
export interface DirEntry {
|
||||
name: string;
|
||||
md5?: string;
|
||||
size?: number;
|
||||
}
|
||||
|
||||
const runTemplate = "C00882010084XX0060640301606400"
|
||||
const usbMagic = 0x3d3f
|
||||
const DIRECT_COMMAND_NO_REPLY = 0x80
|
||||
|
||||
export class Ev3Wrapper {
|
||||
msgs = new U.PromiseBuffer<Uint8Array>()
|
||||
private cmdSeq = U.randomUint32() & 0xffff;
|
||||
private lock = new U.PromiseQueue();
|
||||
isStreaming = false;
|
||||
dataDump = /talkdbg=1/.test(window.location.href);
|
||||
|
||||
constructor(public io: pxt.HF2.PacketIO) {
|
||||
io.onData = buf => {
|
||||
buf = buf.slice(0, HF2.read16(buf, 0) + 2)
|
||||
if (HF2.read16(buf, 4) == usbMagic) {
|
||||
let code = HF2.read16(buf, 6)
|
||||
let payload = buf.slice(8)
|
||||
if (code == 1) {
|
||||
let str = U.uint8ArrayToString(payload)
|
||||
if (U.isNodeJS)
|
||||
pxt.debug("SERIAL: " + str.replace(/\n+$/, ""))
|
||||
else
|
||||
window.postMessage({
|
||||
type: 'serial',
|
||||
id: 'n/a', // TODO?
|
||||
data: str
|
||||
}, "*")
|
||||
} else
|
||||
pxt.debug("Magic: " + code + ": " + U.toHex(payload))
|
||||
return
|
||||
}
|
||||
if (this.dataDump)
|
||||
log("RECV: " + U.toHex(buf))
|
||||
this.msgs.push(buf)
|
||||
}
|
||||
}
|
||||
|
||||
export interface DirEntry {
|
||||
name: string;
|
||||
md5?: string;
|
||||
size?: number;
|
||||
private allocCore(addSize: number, replyType: number) {
|
||||
let len = 5 + addSize
|
||||
let buf = new Uint8Array(len)
|
||||
HF2.write16(buf, 0, len - 2) // pktLen
|
||||
HF2.write16(buf, 2, this.cmdSeq++) // msgCount
|
||||
buf[4] = replyType
|
||||
return buf
|
||||
}
|
||||
|
||||
const runTemplate = "C00882010084XX0060640301606400"
|
||||
const usbMagic = 0x3d3f
|
||||
private allocSystem(addSize: number, cmd: number, replyType = 1) {
|
||||
let buf = this.allocCore(addSize + 1, replyType)
|
||||
buf[5] = cmd
|
||||
return buf
|
||||
}
|
||||
|
||||
export class Ev3Wrapper {
|
||||
msgs = new U.PromiseBuffer<Uint8Array>()
|
||||
private cmdSeq = U.randomUint32() & 0xffff;
|
||||
private lock = new U.PromiseQueue();
|
||||
isStreaming = false;
|
||||
dataDump = false;
|
||||
private allocCustom(code: number, addSize = 0) {
|
||||
let buf = this.allocCore(1 + 2 + addSize, 0)
|
||||
HF2.write16(buf, 4, usbMagic)
|
||||
HF2.write16(buf, 6, code)
|
||||
return buf
|
||||
}
|
||||
|
||||
constructor(public io: pxt.HF2.PacketIO) {
|
||||
io.onData = buf => {
|
||||
buf = buf.slice(0, HF2.read16(buf, 0) + 2)
|
||||
if (HF2.read16(buf, 4) == usbMagic) {
|
||||
let code = HF2.read16(buf, 6)
|
||||
let payload = buf.slice(8)
|
||||
if (code == 1) {
|
||||
let str = U.uint8ArrayToString(payload)
|
||||
if (Util.isNodeJS)
|
||||
console.log("SERIAL: " + str.replace(/\n+$/, ""))
|
||||
else
|
||||
window.postMessage({
|
||||
type: 'serial',
|
||||
id: 'n/a', // TODO?
|
||||
data: str
|
||||
}, "*")
|
||||
} else
|
||||
console.log("Magic: " + code + ": " + U.toHex(payload))
|
||||
return
|
||||
}
|
||||
if (this.dataDump)
|
||||
log("RECV: " + U.toHex(buf))
|
||||
this.msgs.push(buf)
|
||||
}
|
||||
}
|
||||
|
||||
private allocCore(addSize: number, replyType: number) {
|
||||
let len = 5 + addSize
|
||||
let buf = new Uint8Array(len)
|
||||
HF2.write16(buf, 0, len - 2) // pktLen
|
||||
HF2.write16(buf, 2, this.cmdSeq++) // msgCount
|
||||
buf[4] = replyType
|
||||
return buf
|
||||
}
|
||||
|
||||
private allocSystem(addSize: number, cmd: number, replyType = 1) {
|
||||
let buf = this.allocCore(addSize + 1, replyType)
|
||||
buf[5] = cmd
|
||||
return buf
|
||||
}
|
||||
|
||||
private allocCustom(code: number, addSize = 0) {
|
||||
let buf = this.allocCore(1 + 2 + addSize, 0)
|
||||
HF2.write16(buf, 4, usbMagic)
|
||||
HF2.write16(buf, 6, code)
|
||||
return buf
|
||||
}
|
||||
|
||||
stopAsync() {
|
||||
return this.isVmAsync()
|
||||
.then(vm => {
|
||||
if (vm) return Promise.resolve();
|
||||
log(`stopping PXT app`)
|
||||
let buf = this.allocCustom(2)
|
||||
return this.justSendAsync(buf)
|
||||
.then(() => Promise.delay(500))
|
||||
})
|
||||
}
|
||||
|
||||
dmesgAsync() {
|
||||
log(`asking for DMESG buffer over serial`)
|
||||
let buf = this.allocCustom(3)
|
||||
return this.justSendAsync(buf)
|
||||
}
|
||||
|
||||
runAsync(path: string) {
|
||||
let codeHex = runTemplate.replace("XX", U.toHex(U.stringToUint8Array(path)))
|
||||
let code = U.fromHex(codeHex)
|
||||
let pkt = this.allocCore(2 + code.length, 0)
|
||||
HF2.write16(pkt, 5, 0x0800)
|
||||
U.memcpy(pkt, 7, code)
|
||||
log(`run ${path}`)
|
||||
return this.justSendAsync(pkt)
|
||||
}
|
||||
|
||||
justSendAsync(buf: Uint8Array) {
|
||||
return this.lock.enqueue("talk", () => {
|
||||
this.msgs.drain()
|
||||
if (this.dataDump)
|
||||
log("SEND: " + U.toHex(buf))
|
||||
return this.io.sendPacketAsync(buf)
|
||||
})
|
||||
}
|
||||
|
||||
talkAsync(buf: Uint8Array, altResponse = 0) {
|
||||
return this.lock.enqueue("talk", () => {
|
||||
this.msgs.drain()
|
||||
if (this.dataDump)
|
||||
log("TALK: " + U.toHex(buf))
|
||||
return this.io.sendPacketAsync(buf)
|
||||
.then(() => this.msgs.shiftAsync(1000))
|
||||
.then(resp => {
|
||||
if (resp[2] != buf[2] || resp[3] != buf[3])
|
||||
U.userError("msg count de-sync")
|
||||
if (buf[4] == 1) {
|
||||
if (altResponse != -1 && resp[5] != buf[5])
|
||||
U.userError("cmd de-sync")
|
||||
if (altResponse != -1 && resp[6] != 0 && resp[6] != altResponse)
|
||||
U.userError("cmd error: " + resp[6])
|
||||
}
|
||||
return resp
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
flashAsync(path: string, file: Uint8Array) {
|
||||
log(`write ${file.length} bytes to ${path}`)
|
||||
|
||||
let handle = -1
|
||||
|
||||
let loopAsync = (pos: number): Promise<void> => {
|
||||
if (pos >= file.length) return Promise.resolve()
|
||||
let size = file.length - pos
|
||||
if (size > 1000) size = 1000
|
||||
let upl = this.allocSystem(1 + size, 0x93, 0x1)
|
||||
upl[6] = handle
|
||||
U.memcpy(upl, 6 + 1, file, pos, size)
|
||||
return this.talkAsync(upl, 8) // 8=EOF
|
||||
.then(() => loopAsync(pos + size))
|
||||
}
|
||||
|
||||
let begin = this.allocSystem(4 + path.length + 1, 0x92)
|
||||
HF2.write32(begin, 6, file.length) // fileSize
|
||||
U.memcpy(begin, 10, U.stringToUint8Array(path))
|
||||
return this.lock.enqueue("file", () =>
|
||||
this.talkAsync(begin)
|
||||
.then(resp => {
|
||||
handle = resp[7]
|
||||
return loopAsync(0)
|
||||
}))
|
||||
}
|
||||
|
||||
lsAsync(path: string): Promise<DirEntry[]> {
|
||||
let lsReq = this.allocSystem(2 + path.length + 1, 0x99)
|
||||
HF2.write16(lsReq, 6, 1024) // maxRead
|
||||
U.memcpy(lsReq, 8, U.stringToUint8Array(path))
|
||||
|
||||
return this.talkAsync(lsReq, 8)
|
||||
.then(resp =>
|
||||
U.uint8ArrayToString(resp.slice(12)).split(/\n/).map(s => {
|
||||
if (!s) return null as DirEntry
|
||||
let m = /^([A-F0-9]+) ([A-F0-9]+) ([^\/]*)$/.exec(s)
|
||||
if (m)
|
||||
return {
|
||||
md5: m[1],
|
||||
size: parseInt(m[2], 16),
|
||||
name: m[3]
|
||||
}
|
||||
else
|
||||
return {
|
||||
name: s.replace(/\/$/, "")
|
||||
}
|
||||
}).filter(v => !!v))
|
||||
}
|
||||
|
||||
rmAsync(path: string): Promise<void> {
|
||||
log(`rm ${path}`)
|
||||
let rmReq = this.allocSystem(path.length + 1, 0x9c)
|
||||
U.memcpy(rmReq, 6, U.stringToUint8Array(path))
|
||||
|
||||
return this.talkAsync(rmReq, 5)
|
||||
.then(resp => { })
|
||||
}
|
||||
|
||||
isVmAsync(): Promise<boolean> {
|
||||
let path = "/no/such/dir"
|
||||
let mkdirReq = this.allocSystem(path.length + 1, 0x9b)
|
||||
U.memcpy(mkdirReq, 6, U.stringToUint8Array(path))
|
||||
return this.talkAsync(mkdirReq, -1)
|
||||
.then(resp => {
|
||||
let isVM = resp[6] == 0x05
|
||||
log(`${isVM ? "PXT app" : "VM"} running`)
|
||||
return isVM
|
||||
})
|
||||
}
|
||||
|
||||
private streamFileOnceAsync(path: string, cb: (d: Uint8Array) => void) {
|
||||
let fileSize = 0
|
||||
let filePtr = 0
|
||||
let handle = -1
|
||||
let resp = (buf: Uint8Array): Promise<void> => {
|
||||
if (buf[6] == 2) {
|
||||
// handle not ready - file is missing
|
||||
this.isStreaming = false
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
if (buf[6] != 0 && buf[6] != 8)
|
||||
U.userError("bad response when streaming file: " + buf[6] + " " + U.toHex(buf))
|
||||
|
||||
this.isStreaming = true
|
||||
fileSize = HF2.read32(buf, 7)
|
||||
if (handle == -1) {
|
||||
handle = buf[11]
|
||||
log(`stream on, handle=${handle}`)
|
||||
}
|
||||
let data = buf.slice(12)
|
||||
filePtr += data.length
|
||||
if (data.length > 0)
|
||||
cb(data)
|
||||
|
||||
if (buf[6] == 8) {
|
||||
// end of file
|
||||
this.isStreaming = false
|
||||
return this.rmAsync(path)
|
||||
}
|
||||
|
||||
let contFileReq = this.allocSystem(1 + 2, 0x97)
|
||||
HF2.write16(contFileReq, 7, 1000) // maxRead
|
||||
contFileReq[6] = handle
|
||||
return Promise.delay(data.length > 0 ? 0 : 500)
|
||||
.then(() => this.talkAsync(contFileReq, -1))
|
||||
.then(resp)
|
||||
}
|
||||
|
||||
let getFileReq = this.allocSystem(2 + path.length + 1, 0x96)
|
||||
HF2.write16(getFileReq, 6, 1000) // maxRead
|
||||
U.memcpy(getFileReq, 8, U.stringToUint8Array(path))
|
||||
return this.talkAsync(getFileReq, -1).then(resp)
|
||||
}
|
||||
|
||||
streamFileAsync(path: string, cb: (d: Uint8Array) => void) {
|
||||
let loop = (): Promise<void> =>
|
||||
this.lock.enqueue("file", () =>
|
||||
this.streamFileOnceAsync(path, cb))
|
||||
stopAsync() {
|
||||
return this.isVmAsync()
|
||||
.then(vm => {
|
||||
if (vm) return Promise.resolve();
|
||||
log(`stopping PXT app`)
|
||||
let buf = this.allocCustom(2)
|
||||
return this.justSendAsync(buf)
|
||||
.then(() => Promise.delay(500))
|
||||
.then(loop)
|
||||
return loop()
|
||||
})
|
||||
}
|
||||
|
||||
dmesgAsync() {
|
||||
log(`asking for DMESG buffer over serial`)
|
||||
let buf = this.allocCustom(3)
|
||||
return this.justSendAsync(buf)
|
||||
}
|
||||
|
||||
runAsync(path: string) {
|
||||
let codeHex = runTemplate.replace("XX", U.toHex(U.stringToUint8Array(path)))
|
||||
let code = U.fromHex(codeHex)
|
||||
let pkt = this.allocCore(2 + code.length, DIRECT_COMMAND_NO_REPLY)
|
||||
HF2.write16(pkt, 5, 0x0800)
|
||||
U.memcpy(pkt, 7, code)
|
||||
log(`run ${path}`)
|
||||
return this.justSendAsync(pkt)
|
||||
}
|
||||
|
||||
justSendAsync(buf: Uint8Array) {
|
||||
return this.lock.enqueue("talk", () => {
|
||||
this.msgs.drain()
|
||||
if (this.dataDump)
|
||||
log("SEND: " + U.toHex(buf))
|
||||
return this.io.sendPacketAsync(buf)
|
||||
})
|
||||
}
|
||||
|
||||
talkAsync(buf: Uint8Array, altResponse = 0) {
|
||||
return this.lock.enqueue("talk", () => {
|
||||
this.msgs.drain()
|
||||
if (this.dataDump)
|
||||
log("TALK: " + U.toHex(buf))
|
||||
return this.io.sendPacketAsync(buf)
|
||||
.then(() => this.msgs.shiftAsync(1000))
|
||||
.then(resp => {
|
||||
if (resp[2] != buf[2] || resp[3] != buf[3])
|
||||
U.userError("msg count de-sync")
|
||||
if (buf[4] == 1) {
|
||||
if (altResponse != -1 && resp[5] != buf[5])
|
||||
U.userError("cmd de-sync")
|
||||
if (altResponse != -1 && resp[6] != 0 && resp[6] != altResponse)
|
||||
U.userError("cmd error: " + resp[6])
|
||||
}
|
||||
return resp
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
flashAsync(path: string, file: Uint8Array) {
|
||||
log(`write ${file.length} bytes to ${path}`)
|
||||
|
||||
let handle = -1
|
||||
|
||||
let loopAsync = (pos: number): Promise<void> => {
|
||||
if (pos >= file.length) return Promise.resolve()
|
||||
let size = file.length - pos
|
||||
if (size > 1000) size = 1000
|
||||
let upl = this.allocSystem(1 + size, 0x93, 0x1)
|
||||
upl[6] = handle
|
||||
U.memcpy(upl, 6 + 1, file, pos, size)
|
||||
return this.talkAsync(upl, 8) // 8=EOF
|
||||
.then(() => loopAsync(pos + size))
|
||||
}
|
||||
|
||||
let begin = this.allocSystem(4 + path.length + 1, 0x92)
|
||||
HF2.write32(begin, 6, file.length) // fileSize
|
||||
U.memcpy(begin, 10, U.stringToUint8Array(path))
|
||||
return this.lock.enqueue("file", () =>
|
||||
this.talkAsync(begin)
|
||||
.then(resp => {
|
||||
handle = resp[7]
|
||||
return loopAsync(0)
|
||||
}))
|
||||
}
|
||||
|
||||
downloadFileAsync(path: string, cb: (d: Uint8Array) => void) {
|
||||
return this.lock.enqueue("file", () =>
|
||||
this.streamFileOnceAsync(path, cb))
|
||||
}
|
||||
|
||||
lsAsync(path: string): Promise<DirEntry[]> {
|
||||
let lsReq = this.allocSystem(2 + path.length + 1, 0x99)
|
||||
HF2.write16(lsReq, 6, 1024) // maxRead
|
||||
U.memcpy(lsReq, 8, U.stringToUint8Array(path))
|
||||
|
||||
private initAsync() {
|
||||
return Promise.resolve()
|
||||
return this.talkAsync(lsReq, 8)
|
||||
.then(resp =>
|
||||
U.uint8ArrayToString(resp.slice(12)).split(/\n/).map(s => {
|
||||
if (!s) return null as DirEntry
|
||||
let m = /^([A-F0-9]+) ([A-F0-9]+) ([^\/]*)$/.exec(s)
|
||||
if (m)
|
||||
return {
|
||||
md5: m[1],
|
||||
size: parseInt(m[2], 16),
|
||||
name: m[3]
|
||||
}
|
||||
else
|
||||
return {
|
||||
name: s.replace(/\/$/, "")
|
||||
}
|
||||
}).filter(v => !!v))
|
||||
}
|
||||
|
||||
rmAsync(path: string): Promise<void> {
|
||||
log(`rm ${path}`)
|
||||
let rmReq = this.allocSystem(path.length + 1, 0x9c)
|
||||
U.memcpy(rmReq, 6, U.stringToUint8Array(path))
|
||||
|
||||
return this.talkAsync(rmReq, 5)
|
||||
.then(resp => { })
|
||||
}
|
||||
|
||||
isVmAsync(): Promise<boolean> {
|
||||
let path = "/no/such/dir"
|
||||
let mkdirReq = this.allocSystem(path.length + 1, 0x9b)
|
||||
U.memcpy(mkdirReq, 6, U.stringToUint8Array(path))
|
||||
return this.talkAsync(mkdirReq, -1)
|
||||
.then(resp => {
|
||||
let isVM = resp[6] == 0x05
|
||||
log(`${isVM ? "PXT app" : "VM"} running`)
|
||||
return isVM
|
||||
})
|
||||
}
|
||||
|
||||
private streamFileOnceAsync(path: string, cb: (d: Uint8Array) => void) {
|
||||
let fileSize = 0
|
||||
let filePtr = 0
|
||||
let handle = -1
|
||||
let resp = (buf: Uint8Array): Promise<void> => {
|
||||
if (buf[6] == 2) {
|
||||
// handle not ready - file is missing
|
||||
this.isStreaming = false
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
if (buf[6] != 0 && buf[6] != 8)
|
||||
U.userError("bad response when streaming file: " + buf[6] + " " + U.toHex(buf))
|
||||
|
||||
this.isStreaming = true
|
||||
fileSize = HF2.read32(buf, 7)
|
||||
if (handle == -1) {
|
||||
handle = buf[11]
|
||||
log(`stream on, handle=${handle}`)
|
||||
}
|
||||
let data = buf.slice(12)
|
||||
filePtr += data.length
|
||||
if (data.length > 0)
|
||||
cb(data)
|
||||
|
||||
if (buf[6] == 8) {
|
||||
// end of file
|
||||
this.isStreaming = false
|
||||
return this.rmAsync(path)
|
||||
}
|
||||
|
||||
let contFileReq = this.allocSystem(1 + 2, 0x97)
|
||||
HF2.write16(contFileReq, 7, 1000) // maxRead
|
||||
contFileReq[6] = handle
|
||||
return Promise.delay(data.length > 0 ? 0 : 500)
|
||||
.then(() => this.talkAsync(contFileReq, -1))
|
||||
.then(resp)
|
||||
}
|
||||
|
||||
private resetState() {
|
||||
let getFileReq = this.allocSystem(2 + path.length + 1, 0x96)
|
||||
HF2.write16(getFileReq, 6, 1000) // maxRead
|
||||
U.memcpy(getFileReq, 8, U.stringToUint8Array(path))
|
||||
return this.talkAsync(getFileReq, -1).then(resp)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
reconnectAsync(first = false): Promise<void> {
|
||||
this.resetState()
|
||||
if (first) return this.initAsync()
|
||||
log(`reconnect`);
|
||||
return this.io.reconnectAsync()
|
||||
.then(() => this.initAsync())
|
||||
}
|
||||
|
||||
disconnectAsync() {
|
||||
log(`disconnect`);
|
||||
return this.io.disconnectAsync()
|
||||
}
|
||||
streamFileAsync(path: string, cb: (d: Uint8Array) => void) {
|
||||
let loop = (): Promise<void> =>
|
||||
this.lock.enqueue("file", () =>
|
||||
this.streamFileOnceAsync(path, cb))
|
||||
.then(() => Promise.delay(500))
|
||||
.then(loop)
|
||||
return loop()
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
downloadFileAsync(path: string, cb: (d: Uint8Array) => void) {
|
||||
return this.lock.enqueue("file", () =>
|
||||
this.streamFileOnceAsync(path, cb))
|
||||
}
|
||||
|
||||
|
||||
private initAsync() {
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
private resetState() {
|
||||
|
||||
}
|
||||
|
||||
reconnectAsync(first = false): Promise<void> {
|
||||
this.resetState()
|
||||
if (first) return this.initAsync()
|
||||
log(`reconnect`);
|
||||
return this.io.reconnectAsync()
|
||||
.then(() => this.initAsync())
|
||||
}
|
||||
|
||||
disconnectAsync() {
|
||||
log(`disconnect`);
|
||||
return this.io.disconnectAsync()
|
||||
}
|
||||
}
|
||||
|
13
ev3.code-workspace
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"folders": [
|
||||
{
|
||||
"path": "."
|
||||
},
|
||||
{
|
||||
"path": "../pxt-common-packages"
|
||||
},
|
||||
{
|
||||
"path": "../pxt"
|
||||
}
|
||||
]
|
||||
}
|
@ -76,9 +76,10 @@ export class FieldSpeed extends Blockly.FieldSlider implements Blockly.FieldCust
|
||||
};
|
||||
|
||||
setReadout_(readout: Element, value: string) {
|
||||
this.updateSpeed(parseFloat(value));
|
||||
let x = parseFloat(value) || 0;
|
||||
this.updateSpeed(x);
|
||||
// Update reporter
|
||||
this.reporter.textContent = `${value}%`;
|
||||
this.reporter.textContent = `${x}%`;
|
||||
}
|
||||
|
||||
private updateSpeed(speed: number) {
|
||||
|
@ -23,7 +23,7 @@ export class FieldTurnRatio extends Blockly.FieldSlider implements Blockly.Field
|
||||
* @constructor
|
||||
*/
|
||||
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 as any).sliderColor_ = '#a8aaa8';
|
||||
}
|
||||
@ -76,26 +76,26 @@ export class FieldTurnRatio extends Blockly.FieldSlider implements Blockly.Field
|
||||
if (!this.path_) {
|
||||
return;
|
||||
}
|
||||
let v = goog.math.clamp(parseFloat(this.getText()), -100, 100);
|
||||
let v = goog.math.clamp(parseFloat(this.getText()), -200, 200);
|
||||
if (isNaN(v)) {
|
||||
v = 0;
|
||||
}
|
||||
|
||||
const x = goog.math.clamp(parseFloat(this.getText()), -100, 100) / 100;
|
||||
const theta = x * Math.PI / 2;
|
||||
const cx = FieldTurnRatio.HALF;
|
||||
const cy = FieldTurnRatio.HALF - 14;
|
||||
const gamma = Math.PI - 2 * theta;
|
||||
const r = FieldTurnRatio.RADIUS;
|
||||
const alpha = 0.2 + Math.abs(x) * 0.5;
|
||||
const x1 = 0;
|
||||
const x = v / 100;
|
||||
const nx = Math.max(-1, Math.min(1, x));
|
||||
const theta = Math.max(nx) * Math.PI / 2;
|
||||
const r = FieldTurnRatio.RADIUS - 6;
|
||||
let cx = FieldTurnRatio.HALF;
|
||||
const cy = FieldTurnRatio.HALF - 22;
|
||||
if (Math.abs(x) > 1) {
|
||||
cx -= (x - (x > 0 ? 1 : -1)) * r / 2; // move center of circle
|
||||
}
|
||||
const alpha = 0.2 + Math.abs(nx) * 0.5;
|
||||
const y1 = r * alpha;
|
||||
const y2 = r * Math.sin(Math.PI / 2 - theta);
|
||||
const x2 = r * Math.cos(Math.PI / 2 - theta);
|
||||
const y3 = y2 - r * alpha * Math.cos(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}`;
|
||||
this.path_.setAttribute('d', d);
|
||||
|
||||
|
66
libs/broadcast/broadcast.ts
Normal 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
@ -0,0 +1,11 @@
|
||||
{
|
||||
"name": "broadcast",
|
||||
"description": "Broadcasting messages - beta",
|
||||
"files": [
|
||||
"broadcast.ts"
|
||||
],
|
||||
"public": true,
|
||||
"dependencies": {
|
||||
"core": "file:../core"
|
||||
}
|
||||
}
|
@ -73,13 +73,7 @@ namespace sensors {
|
||||
}
|
||||
|
||||
setMode(m: ColorSensorMode) {
|
||||
if (m == ColorSensorMode.AmbientLightIntensity) {
|
||||
this.thresholdDetector.setLowThreshold(5);
|
||||
this.thresholdDetector.setHighThreshold(20);
|
||||
} else {
|
||||
this.thresholdDetector.setLowThreshold(20);
|
||||
this.thresholdDetector.setHighThreshold(80);
|
||||
}
|
||||
// don't change threshold after initialization
|
||||
this._setMode(m)
|
||||
}
|
||||
|
||||
@ -111,6 +105,9 @@ namespace sensors {
|
||||
"red",
|
||||
"white",
|
||||
"brown"][this._query()];
|
||||
case ColorSensorMode.AmbientLightIntensity:
|
||||
case ColorSensorMode.ReflectedLightIntensity:
|
||||
return `${this._query()}%`;
|
||||
default:
|
||||
return this._query().toString();
|
||||
}
|
||||
@ -179,22 +176,25 @@ namespace sensors {
|
||||
//% group="Color Sensor"
|
||||
//% blockGap=8
|
||||
color(): ColorSensorColor {
|
||||
this.poke();
|
||||
this.setMode(ColorSensorMode.Color)
|
||||
return this.getNumber(NumberFormat.UInt8LE, 0)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current raw rgb values from the color sensor.
|
||||
* Get the current raw rgb values as an array from the color sensor.
|
||||
* @param sensor the color sensor to query the request
|
||||
*/
|
||||
//% help=sensors/color-sensor/rgbraw
|
||||
//% blockId=colorRgbRaw block="**color sensor** %this| RGB raw"
|
||||
//% parts="colorsensor"
|
||||
//% blockNamespace=sensors
|
||||
//% this.fieldEditor="ports"
|
||||
//% weight=98
|
||||
//% weight=1
|
||||
//% group="Color Sensor"
|
||||
//% blockGap=8
|
||||
rgbRaw(): number[] {
|
||||
this.poke();
|
||||
this.setMode(ColorSensorMode.RgbRaw);
|
||||
return [this.getNumber(NumberFormat.UInt16LE, 0), this.getNumber(NumberFormat.UInt16LE, 2), this.getNumber(NumberFormat.UInt16LE, 4)];
|
||||
}
|
||||
@ -245,11 +245,12 @@ namespace sensors {
|
||||
//% parts="colorsensor"
|
||||
//% blockNamespace=sensors
|
||||
//% this.fieldEditor="ports"
|
||||
//% weight=87
|
||||
//% weight=87 blockGap=8
|
||||
//% group="Color Sensor"
|
||||
light(mode: LightIntensityMode) {
|
||||
this.poke();
|
||||
this.setMode(<ColorSensorMode><number>mode)
|
||||
switch(mode) {
|
||||
switch (mode) {
|
||||
case LightIntensityMode.ReflectedRaw:
|
||||
return this.reflectedLightRaw();
|
||||
default:
|
||||
@ -278,6 +279,7 @@ namespace sensors {
|
||||
*/
|
||||
//%
|
||||
reflectedLightRaw(): number {
|
||||
this.poke();
|
||||
this.setMode(ColorSensorMode.RefRaw);
|
||||
return this.getNumber(NumberFormat.UInt16LE, 0);
|
||||
}
|
||||
|
@ -1,13 +1,40 @@
|
||||
|
||||
const enum BatteryProperty {
|
||||
//% block="level (%)"
|
||||
Level,
|
||||
//% block="current (I)"
|
||||
Current,
|
||||
//% block="voltage (V)"
|
||||
Voltage
|
||||
}
|
||||
|
||||
namespace brick {
|
||||
/**
|
||||
* Returns the current battery level
|
||||
*/
|
||||
//% blockId=brickBatteryLevel block="battery level"
|
||||
//% group="More"
|
||||
//% group="Battery"
|
||||
//% help=brick/battery-level
|
||||
//% deprecated blockHidden=1
|
||||
export function batteryLevel(): number {
|
||||
const info = sensors.internal.getBatteryInfo();
|
||||
return info.current;
|
||||
return info.level;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns information about the battery
|
||||
*/
|
||||
//% blockId=brickBatteryProperty block="battery %property"
|
||||
//% group="Battery"
|
||||
//% help=brick/battery-property
|
||||
export function batteryInfo(property: BatteryProperty): number {
|
||||
const info = sensors.internal.getBatteryInfo();
|
||||
switch(property) {
|
||||
case BatteryProperty.Level: return info.level;
|
||||
case BatteryProperty.Current: return info.Ibatt;
|
||||
case BatteryProperty.Voltage: return info.Vbatt;
|
||||
default: return 0;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -55,6 +55,10 @@ namespace brick {
|
||||
this._wasPressed = false
|
||||
}
|
||||
|
||||
protected poke() {
|
||||
|
||||
}
|
||||
|
||||
//% hidden
|
||||
_update(curr: boolean) {
|
||||
if (this == null) return
|
||||
@ -85,6 +89,7 @@ namespace brick {
|
||||
//% group="Buttons"
|
||||
//% button.fieldEditor="brickbuttons"
|
||||
isPressed() {
|
||||
this.poke();
|
||||
return this._isPressed
|
||||
}
|
||||
|
||||
@ -102,6 +107,7 @@ namespace brick {
|
||||
//% group="Buttons"
|
||||
//% button.fieldEditor="brickbuttons"
|
||||
wasPressed() {
|
||||
this.poke();
|
||||
const r = this._wasPressed
|
||||
this._wasPressed = false
|
||||
return r
|
||||
@ -144,6 +150,7 @@ namespace brick {
|
||||
namespace brick {
|
||||
let btnsMM: MMap
|
||||
let buttons: DevButton[]
|
||||
let buttonPoller: sensors.internal.Poller;
|
||||
|
||||
export namespace internal {
|
||||
export function getBtnsMM() {
|
||||
@ -159,12 +166,6 @@ namespace brick {
|
||||
if (sl[i])
|
||||
ret |= 1 << i
|
||||
}
|
||||
// this needs to be done in query(), which is run without the main JS execution mutex
|
||||
// otherwise, while(true){} will lock the device
|
||||
if (ret & DAL.BUTTON_ID_ESCAPE) {
|
||||
motors.stopAll(); // ensuring that all motors are off
|
||||
control.reset()
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
@ -173,7 +174,7 @@ namespace brick {
|
||||
btnsMM = control.mmap("/dev/lms_ui", DAL.NUM_BUTTONS, 0)
|
||||
if (!btnsMM) control.fail("no buttons?")
|
||||
buttons = []
|
||||
sensors.internal.unsafePollForChanges(50, readButtons, (prev, curr) => {
|
||||
buttonPoller = new sensors.internal.Poller(50, readButtons, (prev, curr) => {
|
||||
for (let b of buttons)
|
||||
b._update(!!(curr & b.mask))
|
||||
})
|
||||
@ -188,6 +189,10 @@ namespace brick {
|
||||
initBtns()
|
||||
buttons.push(this)
|
||||
}
|
||||
|
||||
protected poke() {
|
||||
buttonPoller.poke();
|
||||
}
|
||||
}
|
||||
|
||||
initBtns() // always ON as it handles ESCAPE button
|
||||
|
@ -1,34 +1,76 @@
|
||||
namespace sensors.internal {
|
||||
//% shim=pxt::unsafePollForChanges
|
||||
export function unsafePollForChanges(
|
||||
periodMs: number,
|
||||
query: () => 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().
|
||||
export class Poller {
|
||||
private query: () => number;
|
||||
private update: (previous: number, current: number) => void;
|
||||
public interval: number;
|
||||
|
||||
// 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(() => {
|
||||
let prev = query()
|
||||
changeHandler(prev, prev)
|
||||
while (true) {
|
||||
pause(periodMs)
|
||||
let curr = query()
|
||||
if (prev !== curr) {
|
||||
changeHandler(prev, curr)
|
||||
prev = curr
|
||||
}
|
||||
constructor(interval: number, query: () => number, update: (previous: number, current: number) => void) {
|
||||
this.interval = interval | 0;
|
||||
this.query = query;
|
||||
this.update = update;
|
||||
|
||||
this.poll();
|
||||
}
|
||||
|
||||
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 {
|
||||
let s = ''
|
||||
for (let i = 0; i < buf.length; i++)
|
||||
s += String.fromCharCode(buf[i])
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
let analogMM: MMap
|
||||
let uartMM: MMap
|
||||
let IICMM: MMap
|
||||
let powerMM: MMap
|
||||
let devcon: Buffer
|
||||
let sensorInfos: SensorInfo[]
|
||||
let devPoller: Poller
|
||||
let sensorInfos: SensorInfo[];
|
||||
|
||||
let batteryInfo: {
|
||||
CinCnt: number;
|
||||
CoutCnt: number;
|
||||
VinCnt: number;
|
||||
};
|
||||
let batteryVMin: number;
|
||||
let batteryVMax: number;
|
||||
|
||||
class SensorInfo {
|
||||
port: number
|
||||
@ -36,12 +78,29 @@ namespace sensors.internal {
|
||||
sensors: Sensor[]
|
||||
connType: number
|
||||
devType: number
|
||||
iicid: string
|
||||
poller: Poller;
|
||||
|
||||
constructor(p: number) {
|
||||
this.port = p
|
||||
this.connType = DAL.CONN_NONE
|
||||
this.devType = DAL.DEVICE_TYPE_NONE
|
||||
this.iicid = ''
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
@ -57,21 +116,15 @@ namespace sensors.internal {
|
||||
uartMM = control.mmap("/dev/lms_uart", UartOff.Size, 0)
|
||||
if (!uartMM) control.fail("no uart sensor")
|
||||
|
||||
forever(() => {
|
||||
detectDevices()
|
||||
pause(500)
|
||||
})
|
||||
IICMM = control.mmap("/dev/lms_iic", IICOff.Size, 0)
|
||||
if (!IICMM) control.fail("no iic sensor")
|
||||
|
||||
for (let info_ of sensorInfos) {
|
||||
let info = info_
|
||||
unsafePollForChanges(50, () => {
|
||||
if (info.sensor) return info.sensor._query()
|
||||
return 0
|
||||
}, (prev, curr) => {
|
||||
if (info.sensor) info.sensor._update(prev, curr)
|
||||
})
|
||||
}
|
||||
powerMM = control.mmap("/dev/lms_power", 2, 0)
|
||||
|
||||
devPoller = new Poller(250, () => { return hashDevices(); },
|
||||
(prev, curr) => {
|
||||
detectDevices();
|
||||
});
|
||||
}
|
||||
|
||||
export function getActiveSensors(): Sensor[] {
|
||||
@ -89,60 +142,216 @@ namespace sensors.internal {
|
||||
//serial.writeLine("UART " + port + " / " + mode + " - " + info)
|
||||
}
|
||||
|
||||
export function getBatteryInfo(): { temp: number; current: number } {
|
||||
init();
|
||||
return {
|
||||
temp: analogMM.getNumber(NumberFormat.Int16LE, AnalogOff.BatteryTemp),
|
||||
current: Math.round(analogMM.getNumber(NumberFormat.Int16LE, AnalogOff.BatteryCurrent) / 10)
|
||||
export function readIICID(port: number) {
|
||||
const buf = output.createBuffer(IICStr.Size)
|
||||
buf[IICStr.Port] = port
|
||||
IICMM.ioctl(IO.IIC_READ_TYPE_INFO, buf)
|
||||
const manufacturer = bufferToString(buf.slice(IICStr.Manufacturer, 8))
|
||||
const sensorType = bufferToString(buf.slice(IICStr.SensorType, 8))
|
||||
return manufacturer + sensorType;
|
||||
}
|
||||
|
||||
const ADC_REF = 5000 //!< [mV] maximal value on ADC
|
||||
const ADC_RES = 4095 //!< [CNT] maximal count on ADC
|
||||
// see c_ui.c
|
||||
const SHUNT_IN = 0.11 // [Ohm]
|
||||
const AMP_CIN = 22.0 // [Times]
|
||||
|
||||
const EP2_SHUNT_IN = 0.05 // [Ohm]
|
||||
const EP2_AMP_CIN = 15.0 // [Times]
|
||||
|
||||
const SHUNT_OUT = 0.055 // [Ohm]
|
||||
const AMP_COUT = 19.0 // [Times]
|
||||
|
||||
const VCE = 0.05 // [V]
|
||||
const AMP_VIN = 0.5 // [Times]
|
||||
|
||||
const AVR_CIN = 300
|
||||
const AVR_COUT = 30
|
||||
const AVR_VIN = 30
|
||||
// lms2012
|
||||
const BATT_INDICATOR_HIGH = 7500 //!< Battery indicator high [mV]
|
||||
const BATT_INDICATOR_LOW = 6200 //!< Battery indicator low [mV]
|
||||
const ACCU_INDICATOR_HIGH = 7500 //!< Rechargeable battery indicator high [mV]
|
||||
const ACCU_INDICATOR_LOW = 7100 //!< Rechargeable battery indicator low [mV]
|
||||
|
||||
function CNT_V(C: number) {
|
||||
return ((C * ADC_REF) / (ADC_RES * 1000.0))
|
||||
}
|
||||
|
||||
function updateBatteryInfo() {
|
||||
let CinCnt = analogMM.getNumber(NumberFormat.Int16LE, AnalogOff.BatteryCurrent);
|
||||
let CoutCnt = analogMM.getNumber(NumberFormat.Int16LE, AnalogOff.MotorCurrent);
|
||||
let VinCnt = analogMM.getNumber(NumberFormat.Int16LE, AnalogOff.Cell123456);
|
||||
if (!batteryInfo) {
|
||||
batteryVMin = BATT_INDICATOR_LOW;
|
||||
batteryVMax = BATT_INDICATOR_HIGH;
|
||||
if (powerMM) {
|
||||
const accu = powerMM.getNumber(NumberFormat.UInt8LE, 0);
|
||||
if (accu > 0) {
|
||||
control.dmesg("rechargeable battery")
|
||||
batteryVMin = ACCU_INDICATOR_LOW;
|
||||
batteryVMax = ACCU_INDICATOR_HIGH;
|
||||
}
|
||||
}
|
||||
batteryInfo = {
|
||||
CinCnt: CinCnt,
|
||||
CoutCnt: CoutCnt,
|
||||
VinCnt: VinCnt
|
||||
};
|
||||
// update in background
|
||||
control.runInParallel(() => forever(updateBatteryInfo));
|
||||
} else {
|
||||
CinCnt = batteryInfo.CinCnt = ((batteryInfo.CinCnt * (AVR_CIN - 1)) + CinCnt) / AVR_CIN;
|
||||
CoutCnt = batteryInfo.CoutCnt = ((batteryInfo.CoutCnt * (AVR_COUT - 1)) + CoutCnt) / AVR_COUT;
|
||||
VinCnt = batteryInfo.VinCnt = ((batteryInfo.VinCnt * (AVR_VIN - 1)) + VinCnt) / AVR_VIN;
|
||||
}
|
||||
}
|
||||
|
||||
function detectDevices() {
|
||||
let conns = analogMM.slice(AnalogOff.InConn, DAL.NUM_INPUTS)
|
||||
let numChanged = 0
|
||||
export function getBatteryInfo(): {
|
||||
level: number;
|
||||
Ibatt: number,
|
||||
Vbatt: number,
|
||||
Imotor: number
|
||||
} {
|
||||
init();
|
||||
if (!batteryInfo) updateBatteryInfo();
|
||||
const CinCnt = batteryInfo.CinCnt;
|
||||
const CoutCnt = batteryInfo.CoutCnt;
|
||||
const VinCnt = batteryInfo.VinCnt;
|
||||
/*
|
||||
void cUiUpdatePower(void)
|
||||
{
|
||||
#ifndef Linux_X86
|
||||
DATAF CinV;
|
||||
DATAF CoutV;
|
||||
|
||||
for (let info of sensorInfos) {
|
||||
let newConn = conns[info.port]
|
||||
if (newConn == info.connType)
|
||||
continue
|
||||
if ((UiInstance.Hw == FINAL) || (UiInstance.Hw == FINALB))
|
||||
{
|
||||
CinV = CNT_V(UiInstance.CinCnt) / AMP_CIN;
|
||||
UiInstance.Vbatt = (CNT_V(UiInstance.VinCnt) / AMP_VIN) + CinV + VCE;
|
||||
|
||||
UiInstance.Ibatt = CinV / SHUNT_IN;
|
||||
CoutV = CNT_V(UiInstance.CoutCnt) / AMP_COUT;
|
||||
UiInstance.Imotor = CoutV / SHUNT_OUT;
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
CinV = CNT_V(UiInstance.CinCnt) / EP2_AMP_CIN;
|
||||
UiInstance.Vbatt = (CNT_V(UiInstance.VinCnt) / AMP_VIN) + CinV + VCE;
|
||||
|
||||
UiInstance.Ibatt = CinV / EP2_SHUNT_IN;
|
||||
UiInstance.Imotor = 0;
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
#ifdef DEBUG_TEMP_SHUTDOWN
|
||||
|
||||
UiInstance.Vbatt = 7.0;
|
||||
UiInstance.Ibatt = 5.0;
|
||||
|
||||
#endif
|
||||
}
|
||||
*/
|
||||
const CinV = CNT_V(CinCnt) / AMP_CIN;
|
||||
const Vbatt = CNT_V(VinCnt) / AMP_VIN + CinV + VCE;
|
||||
const Ibatt = CinV / SHUNT_IN;
|
||||
const CoutV = CNT_V(CoutCnt) / AMP_COUT;
|
||||
const Imotor = CoutV / SHUNT_OUT;
|
||||
const level = Math.max(0, Math.min(100, Math.floor((Vbatt * 1000.0 - batteryVMin)
|
||||
/ (batteryVMax - batteryVMin) * 100)));
|
||||
|
||||
return {
|
||||
level: level,
|
||||
Vbatt: Vbatt,
|
||||
Ibatt: Ibatt,
|
||||
Imotor: Imotor
|
||||
};
|
||||
}
|
||||
|
||||
function hashDevices(): number {
|
||||
const conns = analogMM.slice(AnalogOff.InConn, DAL.NUM_INPUTS)
|
||||
let r = 0;
|
||||
for (let i = 0; i < conns.length; ++i) {
|
||||
r = (r << 8 | conns[i]);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
let nonActivated = 0;
|
||||
function detectDevices() {
|
||||
//control.dmesg(`detect devices (${nonActivated} na)`)
|
||||
const conns = analogMM.slice(AnalogOff.InConn, DAL.NUM_INPUTS)
|
||||
let numChanged = 0;
|
||||
const uartSensors: SensorInfo[] = [];
|
||||
|
||||
for (const sensorInfo of sensorInfos) {
|
||||
const newConn = conns[sensorInfo.port]
|
||||
if (newConn == sensorInfo.connType) {
|
||||
// control.dmesg(`connection unchanged ${newConn} at ${sensorInfo.port}`)
|
||||
continue;
|
||||
}
|
||||
numChanged++
|
||||
info.connType = newConn
|
||||
info.devType = DAL.DEVICE_TYPE_NONE
|
||||
sensorInfo.connType = newConn
|
||||
sensorInfo.devType = DAL.DEVICE_TYPE_NONE
|
||||
if (newConn == DAL.CONN_INPUT_UART) {
|
||||
control.dmesg(`new UART connection at ${info.port}`)
|
||||
setUartMode(info.port, 0)
|
||||
let uinfo = readUartInfo(info.port, 0)
|
||||
info.devType = uinfo[TypesOff.Type]
|
||||
control.dmesg(`UART type ${info.devType}`)
|
||||
control.dmesg(`new UART connection at ${sensorInfo.port}`)
|
||||
updateUartMode(sensorInfo.port, 0);
|
||||
uartSensors.push(sensorInfo);
|
||||
} else if (newConn == DAL.CONN_NXT_IIC) {
|
||||
control.dmesg(`new IIC connection at ${sensorInfo.port}`)
|
||||
sensorInfo.devType = DAL.DEVICE_TYPE_IIC_UNKNOWN
|
||||
sensorInfo.iicid = readIICID(sensorInfo.port)
|
||||
control.dmesg(`IIC ID ${sensorInfo.iicid.length}`)
|
||||
} else if (newConn == DAL.CONN_INPUT_DUMB) {
|
||||
control.dmesg(`new DUMB connection at ${info.port}`)
|
||||
control.dmesg(`new DUMB connection at ${sensorInfo.port}`)
|
||||
// TODO? for now assume touch
|
||||
info.devType = DAL.DEVICE_TYPE_TOUCH
|
||||
sensorInfo.devType = DAL.DEVICE_TYPE_TOUCH
|
||||
} else if (newConn == DAL.CONN_NONE || newConn == 0) {
|
||||
control.dmesg(`disconnect at ${info.port}`)
|
||||
//control.dmesg(`disconnect at port ${sensorInfo.port}`)
|
||||
} else {
|
||||
control.dmesg(`unknown connection type: ${newConn} at ${info.port}`)
|
||||
control.dmesg(`unknown connection type: ${newConn} at ${sensorInfo.port}`)
|
||||
}
|
||||
}
|
||||
|
||||
if (numChanged == 0)
|
||||
if (uartSensors.length > 0) {
|
||||
setUartModes();
|
||||
for (const sensorInfo of uartSensors) {
|
||||
let uinfo = readUartInfo(sensorInfo.port, 0)
|
||||
sensorInfo.devType = uinfo[TypesOff.Type]
|
||||
control.dmesg(`UART type ${sensorInfo.devType}`)
|
||||
}
|
||||
}
|
||||
|
||||
if (numChanged == 0 && nonActivated == 0)
|
||||
return
|
||||
|
||||
for (let si of sensorInfos) {
|
||||
if (si.sensor && si.sensor._deviceType() != si.devType) {
|
||||
si.sensor = null
|
||||
}
|
||||
if (si.devType != DAL.DEVICE_TYPE_NONE) {
|
||||
// TODO figure out compiler problem when '|| null' is added here!
|
||||
si.sensor = si.sensors.filter(s => s._deviceType() == si.devType)[0]
|
||||
if (si.sensor == null) {
|
||||
control.dmesg(`sensor not found for type=${si.devType} at ${si.port}`)
|
||||
//control.dmesg(`updating sensor status`)
|
||||
nonActivated = 0;
|
||||
for (const sensorInfo of sensorInfos) {
|
||||
if (sensorInfo.devType == DAL.DEVICE_TYPE_IIC_UNKNOWN) {
|
||||
sensorInfo.sensor = sensorInfo.sensors.filter(s => s._IICId() == sensorInfo.iicid)[0]
|
||||
if (!sensorInfo.sensor) {
|
||||
control.dmesg(`sensor not found for iicid=${sensorInfo.iicid} at ${sensorInfo.port}`)
|
||||
nonActivated++;
|
||||
} else {
|
||||
control.dmesg(`sensor connected type=${si.devType} at ${si.port}`)
|
||||
si.sensor._activated()
|
||||
control.dmesg(`sensor connected iicid=${sensorInfo.iicid} at ${sensorInfo.port}`)
|
||||
sensorInfo.sensor._activated()
|
||||
}
|
||||
} else if (sensorInfo.devType != DAL.DEVICE_TYPE_NONE) {
|
||||
sensorInfo.sensor = sensorInfo.sensors.filter(s => s._deviceType() == sensorInfo.devType)[0]
|
||||
if (!sensorInfo.sensor) {
|
||||
control.dmesg(`sensor not found for type=${sensorInfo.devType} at ${sensorInfo.port}`)
|
||||
nonActivated++;
|
||||
} else {
|
||||
control.dmesg(`sensor connected type=${sensorInfo.devType} at ${sensorInfo.port}`)
|
||||
sensorInfo.sensor._activated()
|
||||
}
|
||||
}
|
||||
}
|
||||
//control.dmesg(`detect devices done`)
|
||||
}
|
||||
|
||||
export class Sensor extends control.Component {
|
||||
@ -158,6 +367,11 @@ namespace sensors.internal {
|
||||
this.markUsed();
|
||||
}
|
||||
|
||||
poke() {
|
||||
if (this.isActive())
|
||||
sensorInfos[this._port].poke();
|
||||
}
|
||||
|
||||
markUsed() {
|
||||
sensors.__sensorUsed(this._port, this._deviceType());
|
||||
}
|
||||
@ -187,6 +401,10 @@ namespace sensors.internal {
|
||||
_deviceType() {
|
||||
return 0
|
||||
}
|
||||
|
||||
_IICId() {
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
export class AnalogSensor extends Sensor {
|
||||
@ -242,6 +460,55 @@ namespace sensors.internal {
|
||||
}
|
||||
}
|
||||
|
||||
export class IICSensor extends Sensor {
|
||||
protected mode: number // the mode user asked for
|
||||
protected realmode: number // the mode the hardware is in
|
||||
private readLength: number
|
||||
|
||||
constructor(port: number) {
|
||||
super(port)
|
||||
this.mode = 0
|
||||
this.realmode = 0
|
||||
this.readLength = 1;
|
||||
}
|
||||
|
||||
_activated() {
|
||||
this.realmode = 0
|
||||
this._setMode(this.mode)
|
||||
}
|
||||
|
||||
protected _setMode(m: number) {
|
||||
let v = m | 0
|
||||
this.mode = v
|
||||
if (!this.isActive()) return
|
||||
if (this.realmode != this.mode) {
|
||||
this.realmode = v
|
||||
setIICMode(this._port, this._deviceType(), v)
|
||||
}
|
||||
}
|
||||
|
||||
getBytes(): Buffer {
|
||||
return getIICBytes(this.isActive() ? this._port : -1, this.readLength)
|
||||
}
|
||||
|
||||
getNumber(fmt: NumberFormat, off: number) {
|
||||
if (!this.isActive())
|
||||
return 0
|
||||
return getIICNumber(this.readLength, fmt, off, this._port)
|
||||
}
|
||||
|
||||
transaction(deviceAddress: number, write: number[], read: number) {
|
||||
this.readLength = read;
|
||||
transactionIIC(this._port, deviceAddress, write, read)
|
||||
}
|
||||
|
||||
_deviceType() {
|
||||
return DAL.DEVICE_TYPE_IIC_UNKNOWN
|
||||
}
|
||||
}
|
||||
|
||||
export const iicsensor = new IICSensor(3)
|
||||
|
||||
function uartReset(port: number) {
|
||||
if (port < 0) return
|
||||
control.dmesg(`UART reset at ${port}`)
|
||||
@ -266,6 +533,7 @@ namespace sensors.internal {
|
||||
}
|
||||
|
||||
function uartClearChange(port: number) {
|
||||
control.dmesg(`UART clear change`);
|
||||
const UART_DATA_READY = 8
|
||||
const UART_PORT_CHANGED = 1
|
||||
while (true) {
|
||||
@ -287,20 +555,43 @@ namespace sensors.internal {
|
||||
}
|
||||
}
|
||||
|
||||
function setUartModes() {
|
||||
control.dmesg(`UART set modes`)
|
||||
uartMM.ioctl(IO.UART_SET_CONN, devcon)
|
||||
const ports: number[] = [];
|
||||
for (let port = 0; port < DAL.NUM_INPUTS; ++port) {
|
||||
if (devcon.getNumber(NumberFormat.Int8LE, DevConOff.Connection + port) == DAL.CONN_INPUT_UART) {
|
||||
ports.push(port);
|
||||
}
|
||||
}
|
||||
|
||||
while (ports.length) {
|
||||
const port = ports.pop();
|
||||
const status = waitNonZeroUartStatus(port)
|
||||
control.dmesg(`UART set mode ${status} at ${port}`);
|
||||
}
|
||||
}
|
||||
|
||||
function updateUartMode(port: number, mode: number) {
|
||||
control.dmesg(`UART set mode to ${mode} at ${port}`)
|
||||
devcon.setNumber(NumberFormat.Int8LE, DevConOff.Connection + port, DAL.CONN_INPUT_UART)
|
||||
devcon.setNumber(NumberFormat.Int8LE, DevConOff.Type + port, 33)
|
||||
devcon.setNumber(NumberFormat.Int8LE, DevConOff.Mode + port, mode)
|
||||
}
|
||||
|
||||
function setUartMode(port: number, mode: number) {
|
||||
const UART_PORT_CHANGED = 1
|
||||
while (true) {
|
||||
if (port < 0) return
|
||||
control.dmesg(`UART set mode to ${mode} at ${port}`)
|
||||
devcon.setNumber(NumberFormat.Int8LE, DevConOff.Connection + port, DAL.CONN_INPUT_UART)
|
||||
devcon.setNumber(NumberFormat.Int8LE, DevConOff.Type + port, 33)
|
||||
devcon.setNumber(NumberFormat.Int8LE, DevConOff.Mode + port, mode)
|
||||
updateUartMode(port, mode);
|
||||
uartMM.ioctl(IO.UART_SET_CONN, devcon)
|
||||
let status = waitNonZeroUartStatus(port)
|
||||
if (status & UART_PORT_CHANGED) {
|
||||
control.dmesg(`UART clear changed at ${port}`)
|
||||
uartClearChange(port)
|
||||
} else {
|
||||
break
|
||||
control.dmesg(`UART status ${status}`);
|
||||
break;
|
||||
}
|
||||
pause(10)
|
||||
}
|
||||
@ -321,6 +612,48 @@ namespace sensors.internal {
|
||||
UartOff.Raw + DAL.MAX_DEVICE_DATALENGTH * 300 * port + DAL.MAX_DEVICE_DATALENGTH * index + off)
|
||||
}
|
||||
|
||||
export function setIICMode(port: number, type: number, mode: number) {
|
||||
if (port < 0) return;
|
||||
devcon.setNumber(NumberFormat.Int8LE, DevConOff.Connection + port, DAL.CONN_NXT_IIC)
|
||||
devcon.setNumber(NumberFormat.Int8LE, DevConOff.Type + port, type)
|
||||
devcon.setNumber(NumberFormat.Int8LE, DevConOff.Mode + port, mode)
|
||||
IICMM.ioctl(IO.IIC_SET_CONN, devcon)
|
||||
}
|
||||
|
||||
export function transactionIIC(port: number, deviceAddress: number, writeBuf: number[], readLen: number) {
|
||||
if (port < 0) return;
|
||||
let iicdata = output.createBuffer(IICDat.Size)
|
||||
iicdata.setNumber(NumberFormat.Int8LE, IICDat.Port, port)
|
||||
iicdata.setNumber(NumberFormat.Int8LE, IICDat.Repeat, 0)
|
||||
iicdata.setNumber(NumberFormat.Int16LE, IICDat.Time, 0)
|
||||
iicdata.setNumber(NumberFormat.Int8LE, IICDat.WrLng, writeBuf.length + 1)
|
||||
for (let i = 0; i < writeBuf.length; i++)
|
||||
iicdata.setNumber(NumberFormat.Int8LE, IICDat.WrData + i + 1, writeBuf[i])
|
||||
iicdata.setNumber(NumberFormat.Int8LE, IICDat.WrData, deviceAddress)
|
||||
iicdata.setNumber(NumberFormat.Int8LE, IICDat.RdLng, readLen)
|
||||
IICMM.ioctl(IO.IIC_SETUP, iicdata)
|
||||
}
|
||||
|
||||
export function getIICBytes(port: number, length: number) {
|
||||
if (port < 0) return output.createBuffer(length);
|
||||
let index = IICMM.getNumber(NumberFormat.UInt16LE, IICOff.Actual + port * 2);
|
||||
let buf = IICMM.slice(
|
||||
IICOff.Raw + DAL.MAX_DEVICE_DATALENGTH * 300 * port + DAL.MAX_DEVICE_DATALENGTH * index,
|
||||
length
|
||||
);
|
||||
|
||||
// Reverse
|
||||
for (let i = 0; i < length / 2; i++) {
|
||||
let c = buf[i]
|
||||
buf[i] = buf[length - i - 1]
|
||||
buf[length - i - 1] = c
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
export function getIICNumber(length: number, format: NumberFormat, off: number, port: number) {
|
||||
return getIICBytes(port, length).getNumber(format, off)
|
||||
}
|
||||
|
||||
const enum NxtColOff {
|
||||
Calibration = 0, // uint32[4][3]
|
||||
@ -404,6 +737,52 @@ namespace sensors.internal {
|
||||
Size = 58
|
||||
}
|
||||
|
||||
const enum IICOff {
|
||||
TypeData = 0, // Types[8][4]
|
||||
Repeat = 1792, // uint16[300][4]
|
||||
Raw = 4192, // int8[32][300][4]
|
||||
Actual = 42592, // uint16[4]
|
||||
LogIn = 42600, // uint16[4]
|
||||
Status = 42608, // int8[4]
|
||||
Output = 42612, // int8[32][4]
|
||||
OutputLength = 42740, // int8[4]
|
||||
Size = 42744
|
||||
}
|
||||
|
||||
const enum IICCtlOff {
|
||||
TypeData = 0, // Types
|
||||
Port = 56, // int8
|
||||
Mode = 57, // int8
|
||||
Size = 58
|
||||
}
|
||||
|
||||
const enum IICDat {
|
||||
Result = 0, // result
|
||||
Port = 4, // int8
|
||||
Repeat = 5, // int8
|
||||
Time = 6, // int16
|
||||
WrLng = 8, // int8
|
||||
WrData = 9, // int8[32]
|
||||
RdLng = 41, // int8
|
||||
RdData = 42, //int8[32]
|
||||
Size = 74,
|
||||
}
|
||||
|
||||
const enum IICStr {
|
||||
Port = 0, // int8
|
||||
Time = 2, // int16
|
||||
Type = 4, // int8
|
||||
Mode = 5, // int8
|
||||
Manufacturer = 6, // int8[9]
|
||||
SensorType = 15, // int[9]
|
||||
SetupLng = 24, // int8
|
||||
SetupString = 28, // ulong
|
||||
PollLng = 32, // int8
|
||||
PollString = 36, // ulong
|
||||
ReadLng = 40, // int8
|
||||
Size = 44
|
||||
}
|
||||
|
||||
const enum IO {
|
||||
UART_SET_CONN = 0xc00c7500,
|
||||
UART_READ_MODE_INFO = 0xc03c7501,
|
||||
@ -435,10 +814,10 @@ namespace sensors {
|
||||
|
||||
export class ThresholdDetector {
|
||||
public id: number;
|
||||
public min: number;
|
||||
public max: number;
|
||||
public lowThreshold: number;
|
||||
public highThreshold: number;
|
||||
private min: number;
|
||||
private max: number;
|
||||
private lowThreshold: number;
|
||||
private highThreshold: number;
|
||||
public level: number;
|
||||
public state: ThresholdState;
|
||||
|
||||
@ -471,7 +850,7 @@ namespace sensors {
|
||||
}
|
||||
|
||||
public threshold(t: ThresholdState): number {
|
||||
switch(t) {
|
||||
switch (t) {
|
||||
case ThresholdState.High: return this.highThreshold;
|
||||
case ThresholdState.Low: return this.lowThreshold;
|
||||
default: return (this.max - this.min) / 2;
|
||||
@ -511,5 +890,5 @@ namespace sensors {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
26
libs/core/integrator.ts
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
@ -14,6 +14,9 @@
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <malloc.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include "ev3const.h"
|
||||
|
||||
#define THREAD_DBG(...)
|
||||
|
||||
@ -144,6 +147,29 @@ static void startUsb() {
|
||||
pthread_detach(pid);
|
||||
}
|
||||
|
||||
static void *exitThread(void *) {
|
||||
int fd = open("/dev/lms_ui", O_RDWR, 0666);
|
||||
if (fd < 0)
|
||||
return 0;
|
||||
uint8_t *data =
|
||||
(uint8_t *)mmap(NULL, NUM_BUTTONS, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
|
||||
if (data == MAP_FAILED) {
|
||||
close(fd);
|
||||
return 0;
|
||||
}
|
||||
for (;;) {
|
||||
if (data[5])
|
||||
target_reset();
|
||||
sleep_core_us(50000);
|
||||
}
|
||||
}
|
||||
|
||||
static void startExitThread() {
|
||||
pthread_t pid;
|
||||
pthread_create(&pid, NULL, exitThread, NULL);
|
||||
pthread_detach(pid);
|
||||
}
|
||||
|
||||
void sendUsb(uint16_t code, const char *data, int len) {
|
||||
while (len > 0) {
|
||||
int sz = len;
|
||||
@ -420,11 +446,6 @@ static void runPoller(Thread *thr) {
|
||||
// disposeThread(thr);
|
||||
}
|
||||
|
||||
//%
|
||||
void unsafePollForChanges(int ms, Action query, Action handler) {
|
||||
setupThread(handler, 0, runPoller, query, fromInt(ms));
|
||||
}
|
||||
|
||||
uint32_t afterProgramPage() {
|
||||
return 0;
|
||||
}
|
||||
@ -489,14 +510,23 @@ void runLMS() {
|
||||
}
|
||||
|
||||
void stopMotors() {
|
||||
uint8_t cmd[2] = { 0xA3, 0x0F };
|
||||
uint8_t cmd[3] = {opOutputStop, 0x0F, 0};
|
||||
int fd = open("/dev/lms_pwm", O_RDWR);
|
||||
write(fd, cmd, 2);
|
||||
write(fd, cmd, 3);
|
||||
close(fd);
|
||||
}
|
||||
|
||||
void stopProgram() {
|
||||
uint8_t cmd[1] = {opOutputProgramStop};
|
||||
int fd = open("/dev/lms_pwm", O_RDWR);
|
||||
write(fd, cmd, 1);
|
||||
close(fd);
|
||||
}
|
||||
|
||||
extern "C" void target_reset() {
|
||||
pthread_mutex_trylock(&execMutex);
|
||||
stopMotors();
|
||||
stopProgram();
|
||||
if (lmsPid)
|
||||
runLMS();
|
||||
else
|
||||
@ -510,6 +540,7 @@ void initRuntime() {
|
||||
DMESG("runtime starting...");
|
||||
stopLMS();
|
||||
startUsb();
|
||||
startExitThread();
|
||||
pthread_t disp;
|
||||
pthread_create(&disp, NULL, evtDispatcher, NULL);
|
||||
pthread_detach(disp);
|
||||
|
@ -36,6 +36,13 @@ enum MoveUnit {
|
||||
MilliSeconds
|
||||
}
|
||||
|
||||
enum MovePhase {
|
||||
//% block="acceleration"
|
||||
Acceleration,
|
||||
//% block="deceleration"
|
||||
Deceleration
|
||||
}
|
||||
|
||||
namespace motors {
|
||||
let pwmMM: MMap
|
||||
let motorMM: MMap
|
||||
@ -55,7 +62,7 @@ namespace motors {
|
||||
motorMM = control.mmap("/dev/lms_motor", MotorDataOff.Size * DAL.NUM_OUTPUTS, 0)
|
||||
if (!motorMM) control.fail("no motor file")
|
||||
|
||||
resetAllMotors()
|
||||
resetAll()
|
||||
|
||||
const buf = output.createBuffer(1)
|
||||
buf[0] = DAL.opProgramStart
|
||||
@ -111,20 +118,31 @@ namespace motors {
|
||||
* Stops all motors
|
||||
*/
|
||||
//% blockId=motorStopAll block="stop all motors"
|
||||
//% weight=1
|
||||
//% weight=2
|
||||
//% group="Move"
|
||||
//% help=motors/stop-all
|
||||
export function stopAll() {
|
||||
const b = mkCmd(Output.ALL, DAL.opOutputStop, 0)
|
||||
writePWM(b)
|
||||
writePWM(b);
|
||||
pause(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets all motors
|
||||
*/
|
||||
//% blockId=motorResetAll block="reset all motors"
|
||||
//% weight=1
|
||||
//% group="Move"
|
||||
export function resetAllMotors() {
|
||||
//% help=motors/reset-all
|
||||
export function resetAll() {
|
||||
reset(Output.ALL)
|
||||
pause(1);
|
||||
}
|
||||
|
||||
interface MoveSchedule {
|
||||
speed: number;
|
||||
useSteps: boolean;
|
||||
steps: number[];
|
||||
}
|
||||
|
||||
//% fixedInstances
|
||||
@ -132,26 +150,32 @@ namespace motors {
|
||||
protected _port: Output;
|
||||
protected _portName: string;
|
||||
protected _brake: boolean;
|
||||
protected _regulated: boolean;
|
||||
private _pauseOnRun: boolean;
|
||||
private _initialized: boolean;
|
||||
private _brakeSettleTime: number;
|
||||
private _init: () => void;
|
||||
private _run: (speed: number) => void;
|
||||
private _move: (steps: boolean, stepsOrTime: number, speed: number) => void;
|
||||
private _accelerationSteps: number;
|
||||
private _accelerationTime: number;
|
||||
private _decelerationSteps: number;
|
||||
private _decelerationTime: number;
|
||||
|
||||
protected static output_types: number[] = [0x7, 0x7, 0x7, 0x7];
|
||||
|
||||
constructor(port: Output, init: () => void, run: (speed: number) => void, move: (steps: boolean, stepsOrTime: number, speed: number) => void) {
|
||||
constructor(port: Output, init: () => void) {
|
||||
super();
|
||||
this._port = port;
|
||||
this._portName = outputToName(this._port);
|
||||
this._brake = false;
|
||||
this._regulated = true;
|
||||
this._pauseOnRun = true;
|
||||
this._initialized = false;
|
||||
this._brakeSettleTime = 10;
|
||||
this._init = init;
|
||||
this._run = run;
|
||||
this._move = move;
|
||||
this._accelerationSteps = 0;
|
||||
this._accelerationTime = 0;
|
||||
this._decelerationSteps = 0;
|
||||
this._decelerationTime = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -214,6 +238,7 @@ namespace motors {
|
||||
//% weight=1 blockGap=8
|
||||
//% group="Properties"
|
||||
//% millis.defl=200 millis.min=0 millis.max=500
|
||||
//% help=motors/motor/set-brake-settle-time
|
||||
setBrakeSettleTime(millis: number) {
|
||||
this.init();
|
||||
// ensure in [0,500]
|
||||
@ -239,6 +264,9 @@ namespace motors {
|
||||
// allow 500ms for robot to settle
|
||||
if (this._brake && this._brakeSettleTime > 0)
|
||||
pause(this._brakeSettleTime);
|
||||
else {
|
||||
pause(1);
|
||||
}
|
||||
}
|
||||
|
||||
protected pauseOnRun(stepsOrTime: number) {
|
||||
@ -247,6 +275,8 @@ namespace motors {
|
||||
this.pauseUntilReady();
|
||||
// allow robot to settle
|
||||
this.settle();
|
||||
} else {
|
||||
pause(1);
|
||||
}
|
||||
}
|
||||
|
||||
@ -263,6 +293,42 @@ namespace motors {
|
||||
reset(this._port);
|
||||
}
|
||||
|
||||
private normalizeSchedule(speed: number, step1: number, step2: number, step3: number, unit: MoveUnit): MoveSchedule {
|
||||
const r: MoveSchedule = {
|
||||
speed: Math.clamp(-100, 100, speed >> 0),
|
||||
useSteps: true,
|
||||
steps: [step1 || 0, step2 || 0, step3 || 0]
|
||||
}
|
||||
let scale = 1;
|
||||
switch (unit) {
|
||||
case MoveUnit.Rotations:
|
||||
scale = 360;
|
||||
r.useSteps = true;
|
||||
if (r.steps[1] < 0) {
|
||||
r.speed = -r.speed;
|
||||
r.steps[1] = -r.steps[1];
|
||||
}
|
||||
break;
|
||||
case MoveUnit.Degrees:
|
||||
r.useSteps = true;
|
||||
if (r.steps[1] < 0) {
|
||||
r.speed = -r.speed;
|
||||
r.steps[1] = -r.steps[1];
|
||||
}
|
||||
break;
|
||||
case MoveUnit.Seconds:
|
||||
scale = 1000;
|
||||
r.useSteps = false;
|
||||
break;
|
||||
default:
|
||||
r.useSteps = false;
|
||||
break;
|
||||
}
|
||||
for (let i = 0; i < r.steps.length; ++i)
|
||||
r.steps[i] = Math.max(0, (r.steps[i] * scale) | 0);
|
||||
return r;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the motor at a given speed for limited time or distance.
|
||||
* @param speed the speed from ``100`` full forward to ``-100`` full backward, eg: 50
|
||||
@ -277,41 +343,156 @@ namespace motors {
|
||||
//% help=motors/motor/run
|
||||
run(speed: number, value: number = 0, unit: MoveUnit = MoveUnit.MilliSeconds) {
|
||||
this.init();
|
||||
speed = Math.clamp(-100, 100, speed >> 0);
|
||||
const schedule = this.normalizeSchedule(speed, 0, value, 0, unit);
|
||||
// stop if speed is 0
|
||||
if (!speed) {
|
||||
if (!schedule.speed) {
|
||||
this.stop();
|
||||
return;
|
||||
}
|
||||
// special: 0 is infinity
|
||||
if (value == 0) {
|
||||
this._run(speed);
|
||||
if (schedule.steps[0] + schedule.steps[1] + schedule.steps[2] == 0) {
|
||||
this._run(schedule.speed);
|
||||
pause(1);
|
||||
return;
|
||||
}
|
||||
|
||||
// timed motor moves
|
||||
const steps = schedule.steps;
|
||||
const useSteps = schedule.useSteps;
|
||||
|
||||
// compute ramp up and down
|
||||
steps[0] = (useSteps ? this._accelerationSteps : this._accelerationTime) || 0;
|
||||
steps[2] = (useSteps ? this._decelerationSteps : this._decelerationTime) || 0;
|
||||
if (steps[0] + steps[2] > steps[1]) {
|
||||
// rescale
|
||||
const r = steps[1] / (steps[0] + steps[2]);
|
||||
steps[0] = Math.floor(steps[0] * r);
|
||||
steps[2] *= Math.floor(steps[2] * r);
|
||||
}
|
||||
steps[1] -= (steps[0] + steps[2]);
|
||||
|
||||
// send ramped command
|
||||
this._schedule(schedule);
|
||||
this.pauseOnRun(steps[0] + steps[1] + steps[2]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedules a run of the motor with an acceleration, constant and deceleration phase.
|
||||
* @param speed the speed from ``100`` full forward to ``-100`` full backward, eg: 50
|
||||
* @param value measured distance or rotation, eg: 500
|
||||
* @param unit (optional) unit of the value, eg: MoveUnit.MilliSeconds
|
||||
* @param acceleration acceleration phase measured distance or rotation, eg: 500
|
||||
* @param deceleration deceleration phase measured distance or rotation, eg: 500
|
||||
*/
|
||||
//% blockId=motorSchedule block="ramp %motor at %speed=motorSpeedPicker|\\%|for %value|%unit||accelerate %acceleration|decelerate %deceleration"
|
||||
//% weight=99 blockGap=8
|
||||
//% group="Move"
|
||||
//% motor.fieldEditor="motors"
|
||||
//% help=motors/motor/ramp
|
||||
//% inlineInputMode=inline
|
||||
//% expandableArgumentMode=toggle
|
||||
//% value.defl=500
|
||||
ramp(speed: number, value: number = 500, unit: MoveUnit = MoveUnit.MilliSeconds, acceleration?: number, deceleration?: number) {
|
||||
this.init();
|
||||
const schedule = this.normalizeSchedule(speed, acceleration, value, deceleration, unit);
|
||||
// stop if speed is 0
|
||||
if (!schedule.speed) {
|
||||
this.stop();
|
||||
return;
|
||||
}
|
||||
// special case: do nothing
|
||||
if (schedule.steps[0] + schedule.steps[1] + schedule.steps[2] == 0) {
|
||||
return;
|
||||
}
|
||||
// timed motor moves
|
||||
let useSteps: boolean;
|
||||
let stepsOrTime: number;
|
||||
const steps = schedule.steps;
|
||||
// send ramped command
|
||||
this._schedule(schedule);
|
||||
this.pauseOnRun(steps[0] + steps[1] + steps[2]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies the amount of rotation or time for the acceleration
|
||||
* of run commands.
|
||||
*/
|
||||
//% blockId=outputMotorsetRunRamp block="set %motor|run %ramp to $value||$unit"
|
||||
//% motor.fieldEditor="motors"
|
||||
//% weight=21 blockGap=8
|
||||
//% group="Properties"
|
||||
//% help=motors/motor/set-run-phase
|
||||
setRunPhase(phase: MovePhase, value: number, unit: MoveUnit = MoveUnit.MilliSeconds) {
|
||||
let temp: number;
|
||||
switch (unit) {
|
||||
case MoveUnit.Rotations:
|
||||
stepsOrTime = (value * 360) >> 0;
|
||||
useSteps = true;
|
||||
temp = Math.max(0, (value * 360) | 0);
|
||||
if (phase == MovePhase.Acceleration)
|
||||
this._accelerationSteps = temp;
|
||||
else
|
||||
this._decelerationSteps = temp;
|
||||
break;
|
||||
case MoveUnit.Degrees:
|
||||
stepsOrTime = value >> 0;
|
||||
useSteps = true;
|
||||
temp = Math.max(0, value | 0);
|
||||
if (phase == MovePhase.Acceleration)
|
||||
this._accelerationSteps = temp;
|
||||
else
|
||||
this._decelerationSteps = temp;
|
||||
break;
|
||||
case MoveUnit.Seconds:
|
||||
stepsOrTime = (value * 1000) >> 0;
|
||||
useSteps = false;
|
||||
temp = Math.max(0, (value * 1000) | 0);
|
||||
if (phase == MovePhase.Acceleration)
|
||||
this._accelerationTime = temp;
|
||||
else
|
||||
this._decelerationTime = temp;
|
||||
break;
|
||||
default:
|
||||
stepsOrTime = value;
|
||||
useSteps = false;
|
||||
case MoveUnit.MilliSeconds:
|
||||
temp = Math.max(0, value | 0);
|
||||
if (phase == MovePhase.Acceleration)
|
||||
this._accelerationTime = temp;
|
||||
else
|
||||
this._decelerationTime = temp;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
this._move(useSteps, stepsOrTime, speed);
|
||||
this.pauseOnRun(stepsOrTime);
|
||||
private _run(speed: number) {
|
||||
// ramp up acceleration
|
||||
if (this._accelerationTime) {
|
||||
this._schedule({ speed: speed, useSteps: false, steps: [this._accelerationTime, 100, 0] });
|
||||
pause(this._accelerationTime);
|
||||
}
|
||||
// keep going
|
||||
const b = mkCmd(this._port, this._regulated ? DAL.opOutputSpeed : DAL.opOutputPower, 1)
|
||||
b.setNumber(NumberFormat.Int8LE, 2, speed)
|
||||
writePWM(b)
|
||||
if (speed) {
|
||||
writePWM(mkCmd(this._port, DAL.opOutputStart, 0))
|
||||
}
|
||||
}
|
||||
|
||||
private _schedule(schedule: MoveSchedule) {
|
||||
const p = {
|
||||
useSteps: schedule.useSteps,
|
||||
step1: schedule.steps[0],
|
||||
step2: schedule.steps[1],
|
||||
step3: schedule.steps[2],
|
||||
speed: this._regulated ? schedule.speed : undefined,
|
||||
power: this._regulated ? undefined : schedule.speed,
|
||||
useBrake: this._brake
|
||||
};
|
||||
step(this._port, p)
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the motor(s) speed should be regulated. Default is true.
|
||||
* @param value true for regulated motor
|
||||
*/
|
||||
//% blockId=outputMotorSetRegulated block="set %motor|regulated %value=toggleOnOff"
|
||||
//% motor.fieldEditor="motors"
|
||||
//% weight=58 blockGap=8
|
||||
//% group="Properties"
|
||||
//% help=motors/motor/set-regulated
|
||||
setRegulated(value: boolean) {
|
||||
this._regulated = value;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -332,14 +513,18 @@ namespace motors {
|
||||
*/
|
||||
//% blockId=motorPauseUntilRead block="pause until %motor|ready"
|
||||
//% motor.fieldEditor="motors"
|
||||
//% weight=90
|
||||
//% weight=90 blockGap=8
|
||||
//% group="Move"
|
||||
pauseUntilReady(timeOut?: number) {
|
||||
pauseUntil(() => this.isReady(), timeOut);
|
||||
}
|
||||
|
||||
setRunSmoothness(accelerationPercent: number, decelerationPercent: number) {
|
||||
|
||||
}
|
||||
|
||||
protected setOutputType(large: boolean) {
|
||||
for (let i = 0; i < DAL.NUM_OUTPUTS; ++i) {
|
||||
for (let i = 0; i < DAL.NUM_OUTPUTS; ++i) {
|
||||
if (this._port & (1 << i)) {
|
||||
// (0x07: Large motor, Medium motor = 0x08)
|
||||
MotorBase.output_types[i] = large ? 0x07 : 0x08;
|
||||
@ -364,12 +549,10 @@ namespace motors {
|
||||
//% fixedInstances
|
||||
export class Motor extends MotorBase {
|
||||
private _large: boolean;
|
||||
private _regulated: boolean;
|
||||
|
||||
constructor(port: Output, large: boolean) {
|
||||
super(port, () => this.__init(), (speed) => this.__setSpeed(speed), (steps, stepsOrTime, speed) => this.__move(steps, stepsOrTime, speed));
|
||||
super(port, () => this.__init());
|
||||
this._large = large;
|
||||
this._regulated = true;
|
||||
this.markUsed();
|
||||
}
|
||||
|
||||
@ -381,44 +564,6 @@ namespace motors {
|
||||
this.setOutputType(this._large);
|
||||
}
|
||||
|
||||
private __setSpeed(speed: number) {
|
||||
const b = mkCmd(this._port, this._regulated ? DAL.opOutputSpeed : DAL.opOutputPower, 1)
|
||||
b.setNumber(NumberFormat.Int8LE, 2, speed)
|
||||
writePWM(b)
|
||||
if (speed) {
|
||||
writePWM(mkCmd(this._port, DAL.opOutputStart, 0))
|
||||
}
|
||||
}
|
||||
|
||||
private __move(steps: boolean, stepsOrTime: number, speed: number) {
|
||||
control.dmesg("motor.__move")
|
||||
const p = {
|
||||
useSteps: steps,
|
||||
step1: 0,
|
||||
step2: stepsOrTime,
|
||||
step3: 0,
|
||||
speed: this._regulated ? speed : undefined,
|
||||
power: this._regulated ? undefined : speed,
|
||||
useBrake: this._brake
|
||||
};
|
||||
control.dmesg("motor.1")
|
||||
step(this._port, p)
|
||||
control.dmesg("motor.__move end")
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the motor speed should be regulated. Default is true.
|
||||
* @param value true for regulated motor
|
||||
*/
|
||||
//% blockId=outputMotorSetRegulated block="set %motor|regulated %value=toggleOnOff"
|
||||
//% motor.fieldEditor="motors"
|
||||
//% weight=58 blockGap=8
|
||||
//% group="Properties"
|
||||
//% help=motors/motor/set-regulated
|
||||
setRegulated(value: boolean) {
|
||||
this._regulated = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets motor actual speed.
|
||||
* @param motor the port which connects to the motor
|
||||
@ -476,6 +621,33 @@ namespace motors {
|
||||
toString(): string {
|
||||
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
|
||||
@ -506,7 +678,7 @@ namespace motors {
|
||||
export class SynchedMotorPair extends MotorBase {
|
||||
|
||||
constructor(ports: Output) {
|
||||
super(ports, () => this.__init(), (speed) => this.__setSpeed(speed), (steps, stepsOrTime, speed) => this.__move(steps, stepsOrTime, speed));
|
||||
super(ports, () => this.__init());
|
||||
this.markUsed();
|
||||
}
|
||||
|
||||
@ -518,24 +690,6 @@ namespace motors {
|
||||
this.setOutputType(true);
|
||||
}
|
||||
|
||||
private __setSpeed(speed: number) {
|
||||
syncMotors(this._port, {
|
||||
speed: speed,
|
||||
turnRatio: 0, // same speed
|
||||
useBrake: !!this._brake
|
||||
})
|
||||
}
|
||||
|
||||
private __move(steps: boolean, stepsOrTime: number, speed: number) {
|
||||
syncMotors(this._port, {
|
||||
useSteps: steps,
|
||||
speed: speed,
|
||||
turnRatio: 0, // same speed
|
||||
stepsOrTime: stepsOrTime,
|
||||
useBrake: this._brake
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* The Move Tank block can make a robot drive forward, backward, turn, or stop.
|
||||
* Use the Move Tank block for robot vehicles that have two Large Motors,
|
||||
@ -561,10 +715,12 @@ namespace motors {
|
||||
speedRight = Math.clamp(-100, 100, speedRight >> 0);
|
||||
|
||||
const speed = Math.abs(speedLeft) > Math.abs(speedRight) ? speedLeft : speedRight;
|
||||
const turnRatio = speedLeft == speed
|
||||
? (100 - speedRight / speedLeft * 100)
|
||||
: (speedLeft / speedRight * 100 - 100);
|
||||
let turnRatio = speedLeft == speed
|
||||
? speedLeft == 0 ? 0 : (100 - speedRight / speedLeft * 100)
|
||||
: speedRight == 0 ? 0 : (speedLeft / speedRight * 100 - 100);
|
||||
turnRatio = Math.floor(turnRatio);
|
||||
|
||||
//control.dmesg(`tank ${speedLeft} ${speedRight} => ${turnRatio} ${speed}`)
|
||||
this.steer(turnRatio, speed, value, unit);
|
||||
}
|
||||
|
||||
@ -587,7 +743,7 @@ namespace motors {
|
||||
this.init();
|
||||
speed = Math.clamp(-100, 100, speed >> 0);
|
||||
if (!speed) {
|
||||
stop(this._port, this._brake);
|
||||
this.stop();
|
||||
return;
|
||||
}
|
||||
|
||||
@ -596,10 +752,18 @@ namespace motors {
|
||||
let stepsOrTime: number;
|
||||
switch (unit) {
|
||||
case MoveUnit.Rotations:
|
||||
if (value < 0) {
|
||||
value = -value;
|
||||
speed = -speed;
|
||||
}
|
||||
stepsOrTime = (value * 360) >> 0;
|
||||
useSteps = true;
|
||||
break;
|
||||
case MoveUnit.Degrees:
|
||||
if (value < 0) {
|
||||
value = -value;
|
||||
speed = -speed;
|
||||
}
|
||||
stepsOrTime = value >> 0;
|
||||
useSteps = true;
|
||||
break;
|
||||
@ -743,26 +907,15 @@ namespace motors {
|
||||
return
|
||||
}
|
||||
speed = Math.clamp(-100, 100, speed)
|
||||
control.dmesg('speed: ' + speed)
|
||||
|
||||
let b = mkCmd(out, op, 15)
|
||||
control.dmesg('STEP 5')
|
||||
b.setNumber(NumberFormat.Int8LE, 2, speed)
|
||||
// note that b[3] is padding
|
||||
control.dmesg('STEP 1')
|
||||
b.setNumber(NumberFormat.Int32LE, 4 + 4 * 0, opts.step1)
|
||||
control.dmesg('STEP 2')
|
||||
b.setNumber(NumberFormat.Int32LE, 4 + 4 * 1, opts.step2)
|
||||
control.dmesg('STEP 3')
|
||||
b.setNumber(NumberFormat.Int32LE, 4 + 4 * 2, opts.step3)
|
||||
control.dmesg('STEP 4')
|
||||
control.dmesg('br ' + opts.useBrake);
|
||||
const br = !!opts.useBrake ? 1 : 0;
|
||||
control.dmesg('Step 4.5 ' + br)
|
||||
b.setNumber(NumberFormat.Int8LE, 4 + 4 * 3, br)
|
||||
control.dmesg('STEP 5')
|
||||
writePWM(b)
|
||||
control.dmesg('end step')
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -25,7 +25,8 @@
|
||||
"dal.d.ts",
|
||||
"icons.jres",
|
||||
"ns.ts",
|
||||
"platform.h"
|
||||
"platform.h",
|
||||
"integrator.ts"
|
||||
],
|
||||
"testFiles": [
|
||||
"test.ts"
|
||||
|
@ -1,6 +1,6 @@
|
||||
|
||||
//% color="#68C3E2" weight=100 icon="\uf106"
|
||||
//% groups='["Buttons", "Screen"]'
|
||||
//% groups='["Buttons", "Screen", "Battery"]'
|
||||
//% labelLineWidth=60
|
||||
namespace brick {
|
||||
}
|
||||
|
@ -1,5 +1,7 @@
|
||||
// This is the last thing executed before user code
|
||||
|
||||
console.addListener(function(msg: string) {
|
||||
control.dmesg(msg.substr(0, msg.length - 1))
|
||||
})
|
||||
// pulse green, play startup sound, turn off light
|
||||
brick.setStatusLight(StatusLight.GreenPulse);
|
||||
// We pause for 100ms to give time to read sensor values, so they work in on_start block
|
||||
|
@ -3,6 +3,7 @@
|
||||
```cards
|
||||
sensors.gyro1.angle();
|
||||
sensors.gyro1.rate();
|
||||
sensors.gyro1.calibrate();
|
||||
sensors.gyro1.reset();
|
||||
```
|
||||
|
||||
@ -10,4 +11,6 @@ sensors.gyro1.reset();
|
||||
|
||||
[angle](/reference/sensors/gyro/angle),
|
||||
[rate](/reference/sensors/gyro/rate),
|
||||
[reset](/reference/sensors/gyro/calibrate),
|
||||
[reset](/reference/sensors/gyro/reset)
|
||||
|
||||
|
@ -12,7 +12,7 @@ When the brick changes its position, it's moved in the direction of one of the a
|
||||
|
||||
## Accuracy and calibration
|
||||
|
||||
Gyro sensors aren't perfectly accurate. Sometimes, because of temperature and changes in the way elecricity behaves in the sensor, the gyro returns a small error in it's measurement. This causes the gyro sensor to return an incorrect value for the amount of angle it detected. This might make your robot drive off course and not go to where you want it to.
|
||||
Gyro sensors aren't perfectly accurate. Sometimes, because of temperature and changes in the way electricity behaves in the sensor, the gyro returns a small error in it's measurement. This causes the gyro sensor to return an incorrect value for the amount of angle it detected. This might make your robot drive off course and not go to where you want it to.
|
||||
|
||||
### Drift
|
||||
|
||||
@ -20,7 +20,7 @@ If you want to turn the tank or robot you built to the left by 45 degrees, you m
|
||||
|
||||
The problem is that when you need to read the angle measurement frequently, the amount of error in the angle measurement may continue to increase. If the sensor thought it moved by 45 degrees the first time instead of really 49 degrees, your second turn will put you at 98 degrees when the sensor said 90 degrees. If you want a robot to turn right 90 degrees and drive for 5 meters, it might actually turn 98 degrees and drive 0.7 meters off course before it stops. This error in the sensor's measurement is called _drift_.
|
||||
|
||||
### Time to reset
|
||||
### Calibration
|
||||
|
||||
If errors in the angle values returned by the gyro sensor are making your project not work right, then it's time to **[reset](/reference/sensors/gyro/reset)**. A reset will return the gyro sensor's current angle value back to `0` and _calibrate_ for drift. Calibration is the process of finding out how much error there is in a sensor's measurement and then removing the error from the value returned to your program.
|
||||
|
||||
@ -42,7 +42,14 @@ Turn the brick and press ENTER to see the current rotation angle of `gyro 2`.
|
||||
|
||||
```blocks
|
||||
brick.buttonEnter.onEvent(ButtonEvent.Pressed, function () {
|
||||
brick.showNumber(sensors.gyro2.angle(), 1)
|
||||
sensors.gyro2.reset()
|
||||
})
|
||||
brick.buttonLeft.onEvent(ButtonEvent.Pressed, function () {
|
||||
sensors.gyro2.calibrate()
|
||||
})
|
||||
forever(function () {
|
||||
brick.showNumber(control.millis(), 1)
|
||||
brick.showNumber(sensors.gyro2.angle(), 2)
|
||||
})
|
||||
```
|
||||
|
||||
|
51
libs/gyro-sensor/docs/reference/sensors/gyro/calibrate.md
Normal file
@ -0,0 +1,51 @@
|
||||
# calibrate
|
||||
|
||||
Detects if the gyro is drifting and performs a full reset if needed.
|
||||
|
||||
```sig
|
||||
sensors.gyro2.calibrate()
|
||||
```
|
||||
|
||||
To make the gyro measure rotation angle from the current position of the brick, it is recalibrated. That is, the brick's current position is set to `0` degrees and rotation angle measurements start from there.
|
||||
|
||||
This function adds a few pauses to ensure that the robot is still. If you only want to reset the sensor, use [reset](/reference/gyro-sensor/reset).
|
||||
|
||||
|
||||
## ~hint
|
||||
|
||||
The current position is considered to be the [_horizon_](https://en.wikipedia.org/wiki/Attitude_indicator) or a place that is the _plane of reference_ (this is possibly someplace that's flat for a horizontal reference).
|
||||
|
||||
## ~
|
||||
|
||||
## ~hint
|
||||
|
||||
**Important**
|
||||
|
||||
To properly reset the gyro, the brick must remain still (undisturbed) while the reset operation takes place.
|
||||
|
||||
## ~
|
||||
|
||||
## Calibration states
|
||||
|
||||
Calibration happens in the following phases and each phase is tracked by the brick status light.
|
||||
|
||||
* **orange**: sensor initialization. This phase ensures that the sensor is in the desired mode and ready to collect data.
|
||||
* **orange pulse**: data collection. Light information is being collected, move the sensor over the various light sources to detect.
|
||||
* **green**: calibration success. The calibration data has been saved.
|
||||
* **red flash**: sensor failure. We were unable to connect to the sensor.
|
||||
|
||||
## Example
|
||||
Set the brick on a flat surface. Reset `gyro 2` and tilt the brick slightly. Reset it again while it's still tilted. Lay the brick down flat again and display the angle measurement.
|
||||
|
||||
```blocks
|
||||
brick.buttonLeft.onEvent(ButtonEvent.Pressed, function () {
|
||||
sensors.gyro2.calibrate()
|
||||
})
|
||||
forever(function() {
|
||||
brick.showNumber(sensors.gyro2.angle(), 1)
|
||||
})
|
||||
```
|
||||
|
||||
## See also
|
||||
|
||||
[angle](/reference/sensors/gyro/angle), [rate](/reference/sensors/gyro/rate)
|
@ -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)
|
||||
|