Compare commits

..

35 Commits

Author SHA1 Message Date
6c9ff804c8 1.2.13 2019-09-29 09:49:35 -07:00
7581b5af9e Drift support (#926)
* upgrading drift support

* updated showports

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

* render once at least

* better rendering

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

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

* cleanup life cycle

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

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

* plumbing

* logging

* more notes

* fixing typing

* more plumbing

* more plumbing

* different baud rate

* talking to the brick

* first over the air drop

* fix buffer

* tweak paraetmers

* formatting fixing double upload

* reduce console.log

* cleanup

* add BLE button to download dialog

* changed label

* recover from broken COM port

* fix function call

* reduce log level

* adding ticks

* some help

* updated support matrix

* more docs

* updated browser help

* more docs

* add link

* add device

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

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

* arrange blocks

* updated blocks

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

* poke in sensors

* more poking

* typo
2019-09-17 14:30:02 -07:00
64a9930c2e 1.2.4 2019-09-17 12:36:35 -07:00
5815e16647 update pxt reference (#914) 2019-09-17 12:36:11 -07:00
c4f5e425c2 1.2.3 2019-09-17 11:26:52 -07:00
361ae7a2d2 adding a few pause to allow motors to settle 2019-09-17 11:23:40 -07:00
42 changed files with 1229 additions and 438 deletions

View File

@ -4,6 +4,7 @@
* [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)

View File

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

64
docs/bluetooth.md Normal file
View File

@ -0,0 +1,64 @@
# 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
* [Edge Insider desktop](https://www.microsoftedgeinsider.com), version 77 and higher, Windows 10
To make sure your browser is up to date, go to the '...' menu, click "Help" then "About".
Next you need to enable the experimental features (this may change in the future)
* go to **chrome://flags/#enable-experimental-web-platform-features** and **enable**
**Experimental Web Platform features**
![A screenshot of the flags page in chrome](/static/bluetooth/experimental.png)
## Machine Setup
* pair your EV3 brick with your computer over Bluetooth. This is the usual pairing procedure.
## Download over Bluetooth
* go to the **beta** editor https://makecode.mindstorms.com/beta
This feature is not yet released so make sure to use the beta editor.
* 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, it typically reads "Standard Serial over Bluetooth" and you may
have multiple of those if you've paired different bricks.
## Known issues
* We do not detect properly that the program is running on the brick. Make sure to stop the program before starting the download procedure.
* The list of programs on the brick screen is not updated when uploading via bluetooth.
## Feedback
Please send us your feedback through https://forum.makecode.com.

View File

@ -29,7 +29,7 @@ We have compiled a guide for EV3 LabView users at https://makecode.mindstorms.co
Go to https://makecode.mindstorms.com. The home screen is filled with videos, tutorials and examples that might be relevant for your missions.
On the home page, scroll down to the **FLL / City Shaper / Crane Mission** section for specific lessons related to Mission 2.
On the home page, scroll down to the **FLL / City Shaper** section for specific lessons related to Mission 2.
### Can I load both LEGO MINDSTORMS EV3 Software and MakeCode programs onto my EV3?
@ -37,7 +37,7 @@ Yes.
### Does it work without internet?
To make sure the editor works without internet, install the [offline app](/offline-app)!
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?
@ -92,6 +92,12 @@ You can share your projects by clicking on the **share** button in the top left
Sharing programs is also shown in the [Tips and Tricks](https://legoeducation.videomarketingplatform.co/v.ihtml/player.html?token=5c594c2373367f7870196f519f3bfc7a&source=embed&photo%5fid=35719472) video.
### 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.

View File

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

View File

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

View File

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

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

View File

@ -27,6 +27,12 @@
"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"
}]
```

View File

@ -0,0 +1,34 @@
# 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"
}]
```
## See Also
[Robot 1](/tutorials/city-shaper/robot-1),
[Robot 2](/tutorials/city-shaper/robot-2)

View File

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

View File

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

View File

@ -21,6 +21,12 @@
"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.",

View File

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

View File

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

View File

@ -2,72 +2,237 @@
/// <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) {
this.openAsync();
}
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("bluetooth.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 +272,51 @@ export function deployCoreAsync(resp: pxtc.CompileResult) {
return Promise.resolve();
}
if (noHID) return saveUF2Async()
if (!useHID) return saveUF2Async()
return initAsync()
pxt.tickEvent("bluetooth.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 on the EV3.")}</li>
<li>${lf("Check your battery level.")}</li>
</ul>`,
hasCloseIcon: true,
hideCancel: true,
hideAgree: false,
agreeLbl: lf("Try again"),
}).then(() => w.disconnectAsync())
.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(() => {
pxt.tickEvent("bluetooth.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("bluetooth.fail");
useHID = false;
useWebSerial = false;
// if we failed to initalize, tell the user to retry
return Promise.reject(e)
})
}

View File

@ -1,28 +1,20 @@
/// <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> {
pxt.debug('loading pxt-ev3 target extensions...')
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 +76,45 @@ 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) {
enableWebSerialAsync().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.")}
${lf("On Windows, look for 'Standard Serial over Bluetooth link'.")}
${lf("If you have paired multiple EV3, you might have to try out multiple ports until you find the correct one.")}
</p>
`
}).then(() => enableWebSerialAsync())
.then(() => confirmAsync({
header: lf("Bluetooth is ready!"),
hasCloseIcon: true,
hideCancel: true,
htmlBody: `<p>
${lf("Click Download again to send your code to the EV3 over Bluetooth.")}
</p>
`
}))
}
}
} : undefined, downloadAgain ? {
label: fn,
icon: "download",
className: "lightgrey focused",

View File

@ -1,285 +1,281 @@
namespace pxt.editor {
import HF2 = pxt.HF2
import U = pxt.U
import HF2 = pxt.HF2
import U = pxt.U
function log(msg: string) {
pxt.log("EWRAP: " + msg)
function log(msg: string) {
pxt.debug("EWRAP: " + msg)
}
export interface DirEntry {
name: string;
md5?: string;
size?: number;
}
const runTemplate = "C00882010084XX0060640301606400"
const usbMagic = 0x3d3f
export class Ev3Wrapper {
msgs = new U.PromiseBuffer<Uint8Array>()
private cmdSeq = U.randomUint32() & 0xffff;
private lock = new U.PromiseQueue();
isStreaming = false;
dataDump = false;
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, 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)
}))
}
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()
}
}

View File

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

View File

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

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

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

View File

@ -111,6 +111,9 @@ namespace sensors {
"red",
"white",
"brown"][this._query()];
case ColorSensorMode.AmbientLightIntensity:
case ColorSensorMode.ReflectedLightIntensity:
return `${this._query()}%`;
default:
return this._query().toString();
}
@ -179,6 +182,7 @@ namespace sensors {
//% group="Color Sensor"
//% blockGap=8
color(): ColorSensorColor {
this.poke();
this.setMode(ColorSensorMode.Color)
return this.getNumber(NumberFormat.UInt8LE, 0)
}
@ -196,6 +200,7 @@ namespace sensors {
//% 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)];
}
@ -249,8 +254,9 @@ namespace sensors {
//% 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:
@ -279,6 +285,7 @@ namespace sensors {
*/
//%
reflectedLightRaw(): number {
this.poke();
this.setMode(ColorSensorMode.RefRaw);
return this.getNumber(NumberFormat.UInt16LE, 0);
}

View File

@ -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() {
@ -167,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))
})
@ -182,6 +189,10 @@ namespace brick {
initBtns()
buttons.push(this)
}
protected poke() {
buttonPoller.poke();
}
}
initBtns() // always ON as it handles ESCAPE button

View File

@ -1,28 +1,51 @@
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 {
@ -38,6 +61,7 @@ namespace sensors.internal {
let IICMM: MMap
let powerMM: MMap
let devcon: Buffer
let devPoller: Poller
let sensorInfos: SensorInfo[];
let batteryInfo: {
@ -55,6 +79,7 @@ namespace sensors.internal {
connType: number
devType: number
iicid: string
poller: Poller;
constructor(p: number) {
this.port = p
@ -62,6 +87,20 @@ namespace sensors.internal {
this.devType = DAL.DEVICE_TYPE_NONE
this.iicid = ''
this.sensors = []
this.poller = new Poller(50, () => this.query(), (prev, curr) => this.update(prev, curr));
}
poke() {
this.poller.poke();
}
private query() {
if (this.sensor) return this.sensor._query();
return 0;
}
private update(prev: number, curr: number) {
if (this.sensor) this.sensor._update(prev, curr)
}
}
@ -82,20 +121,11 @@ namespace sensors.internal {
powerMM = control.mmap("/dev/lms_power", 2, 0)
unsafePollForChanges(500,
() => { return hashDevices(); },
devPoller = new Poller(500, () => { return hashDevices(); },
(prev, curr) => {
detectDevices();
});
sensorInfos.forEach(info => {
unsafePollForChanges(50, () => {
if (info.sensor) return info.sensor._query()
return 0
}, (prev, curr) => {
if (info.sensor) info.sensor._update(prev, curr)
})
})
}
}
export function getActiveSensors(): Sensor[] {
init();
@ -164,13 +194,13 @@ namespace sensors.internal {
batteryVMax = ACCU_INDICATOR_HIGH;
}
}
batteryInfo = {
CinCnt: CinCnt,
batteryInfo = {
CinCnt: CinCnt,
CoutCnt: CoutCnt,
VinCnt: VinCnt
};
// update in background
control.runInParallel(() => forever(updateBatteryInfo));
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;
@ -178,11 +208,11 @@ namespace sensors.internal {
}
}
export function getBatteryInfo(): {
level: number;
Ibatt: number,
Vbatt: number,
Imotor: number
export function getBatteryInfo(): {
level: number;
Ibatt: number,
Vbatt: number,
Imotor: number
} {
init();
if (!batteryInfo) updateBatteryInfo();
@ -225,13 +255,13 @@ void cUiUpdatePower(void)
#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)));
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,
@ -280,7 +310,7 @@ void cUiUpdatePower(void)
// TODO? for now assume touch
sensorInfo.devType = DAL.DEVICE_TYPE_TOUCH
} else if (newConn == DAL.CONN_NONE || newConn == 0) {
control.dmesg(`disconnect at port ${sensorInfo.port}`)
//control.dmesg(`disconnect at port ${sensorInfo.port}`)
} else {
control.dmesg(`unknown connection type: ${newConn} at ${sensorInfo.port}`)
}
@ -298,7 +328,7 @@ void cUiUpdatePower(void)
if (numChanged == 0 && nonActivated == 0)
return
control.dmesg(`updating sensor status`)
//control.dmesg(`updating sensor status`)
nonActivated = 0;
for (const sensorInfo of sensorInfos) {
if (sensorInfo.devType == DAL.DEVICE_TYPE_IIC_UNKNOWN) {
@ -337,6 +367,11 @@ void cUiUpdatePower(void)
this.markUsed();
}
poke() {
if (this.isActive())
sensorInfos[this._port].poke();
}
markUsed() {
sensors.__sensorUsed(this._port, this._deviceType());
}

View File

@ -446,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;
}

View File

@ -62,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
@ -118,20 +118,25 @@ 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 {
@ -259,6 +264,8 @@ namespace motors {
// allow 500ms for robot to settle
if (this._brake && this._brakeSettleTime > 0)
pause(this._brakeSettleTime);
else
pause(1); // give a tiny breather
}
protected pauseOnRun(stepsOrTime: number) {
@ -297,9 +304,17 @@ namespace motors {
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;
@ -337,6 +352,7 @@ namespace motors {
// special: 0 is infinity
if (schedule.steps[0] + schedule.steps[1] + schedule.steps[2] == 0) {
this._run(schedule.speed);
pause(1);
return;
}
@ -411,28 +427,28 @@ namespace motors {
temp = Math.max(0, (value * 360) | 0);
if (phase == MovePhase.Acceleration)
this._accelerationSteps = temp;
else
else
this._decelerationSteps = temp;
break;
case MoveUnit.Degrees:
temp = Math.max(0, value | 0);
if (phase == MovePhase.Acceleration)
this._accelerationSteps = temp;
else
else
this._decelerationSteps = temp;
break;
case MoveUnit.Seconds:
temp = Math.max(0, (value * 1000) | 0);
if (phase == MovePhase.Acceleration)
this._accelerationTime = temp;
else
else
this._decelerationTime = temp;
break;
case MoveUnit.MilliSeconds:
temp = Math.max(0, value | 0);
if (phase == MovePhase.Acceleration)
this._accelerationTime = temp;
else
else
this._decelerationTime = temp;
break;
}
@ -497,7 +513,7 @@ 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);
@ -605,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
@ -700,7 +743,7 @@ namespace motors {
this.init();
speed = Math.clamp(-100, 100, speed >> 0);
if (!speed) {
stop(this._port, this._brake);
this.stop();
return;
}
@ -709,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;

View File

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

View File

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

View File

@ -9,12 +9,10 @@ namespace sensors {
export class GyroSensor extends internal.UartSensor {
private calibrating: boolean;
private _drift: number;
private _driftCorrection: boolean;
constructor(port: number) {
super(port)
this.calibrating = false;
this._drift = 0;
this._driftCorrection = false;
this.setMode(GyroSensorMode.Rate);
}
@ -42,9 +40,10 @@ namespace sensors {
//% parts="gyroscope"
//% blockNamespace=sensors
//% this.fieldEditor="ports"
//% weight=64 blockGap=8
//% weight=64
//% group="Gyro Sensor"
angle(): number {
this.poke();
if (this.calibrating)
pauseUntil(() => !this.calibrating, 2000);
@ -65,17 +64,12 @@ namespace sensors {
//% weight=65 blockGap=8
//% group="Gyro Sensor"
rate(): number {
this.poke();
if (this.calibrating)
pauseUntil(() => !this.calibrating, 2000);
this.setMode(GyroSensorMode.Rate);
let curr = this._query();
if (Math.abs(curr) < 4 && this._driftCorrection) {
const p = 0.01;
this._drift = (1 - p) * this._drift + p * curr;
curr = Math.round(curr - this._drift);
}
return curr;
return this._query() - this._drift;
}
/**
@ -111,8 +105,6 @@ namespace sensors {
// mode toggling
this.setMode(GyroSensorMode.Rate);
this.setMode(GyroSensorMode.Angle);
// switch back to the desired mode
this.setMode(this.mode);
// check sensor is ready
if (!this.isActive()) {
@ -123,17 +115,12 @@ namespace sensors {
return;
}
// compute drift
this._drift = 0;
if (this._driftCorrection && this.mode == GyroSensorMode.Rate) {
const n = 100;
for (let i = 0; i < n; ++i) {
this._drift += this._query();
pause(4);
}
this._drift /= n;
}
// switch to rate mode
this.computeDriftNoCalibration();
// switch back to the desired mode
this.setMode(this.mode);
// and done
brick.setStatusLight(StatusLight.Green); // success
pause(1000);
brick.setStatusLight(statusLight); // resture previous light
@ -151,7 +138,7 @@ namespace sensors {
//% parts="gyroscope"
//% blockNamespace=sensors
//% this.fieldEditor="ports"
//% weight=50
//% weight=50 blockGap=8
//% group="Gyro Sensor"
reset(): void {
if (this.calibrating) return; // already in calibration mode
@ -166,19 +153,62 @@ namespace sensors {
/**
* Gets the computed rate drift
*/
//%
//% help=sensors/gyro/drift
//% block="**gyro** %this|drift"
//% blockId=gyroDrift
//% parts="gyroscope"
//% blockNamespace=sensors
//% this.fieldEditor="ports"
//% weight=9 blockGap=8
//% group="Gyro Sensor"
drift(): number {
return this._drift;
}
/**
* Enables or disable drift correction
* @param enabled
* Computes the current sensor drift when using rate measurements.
*/
//%
setDriftCorrection(enabled: boolean) {
this._driftCorrection = enabled;
//% help=sensors/gyro/compute-drift
//% block="compute **gyro** %this|drift"
//% blockId=gyroComputeDrift
//% parts="gyroscope"
//% blockNamespace=sensors
//% this.fieldEditor="ports"
//% weight=10 blockGap=8
//% group="Gyro Sensor"
computeDrift() {
if (this.calibrating)
pauseUntil(() => !this.calibrating, 2000);
this.computeDriftNoCalibration();
}
private computeDriftNoCalibration() {
// clear drift
this.setMode(GyroSensorMode.Rate);
this._drift = 0;
const n = 100;
let d = 0;
for (let i = 0; i < n; ++i) {
d += this._query();
pause(4);
}
this._drift = d / n;
}
_info(): string {
if (this.calibrating)
return "cal...";
switch (this.mode) {
case GyroSensorMode.Angle:
return `${this._query()}>`;
case GyroSensorMode.Rate:
let r = `${this._query()}>/s`;
if (this._drift)
r += `-${this._drift | 0}`;
return r;
}
return "";
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -5,6 +5,10 @@
"LEGO"
],
"approvedRepos": [
"microsoft/pxt-automation"
],
"preferredRepos": [
"microsoft/pxt-automation"
]
},
"galleries": {
@ -15,7 +19,7 @@
"Color Sensor Tutorials": "tutorials/color-sensor",
"Ultrasonic Sensor Tutorials": "tutorials/ultrasonic-sensor",
"Infrared Sensor Tutorials": "tutorials/infrared-sensor",
"FLL / City Shaper / Crane Mission": "tutorials/city-shaper/crane-mission",
"FLL / City Shaper": "tutorials/city-shaper",
"Design Engineering": "design-engineering",
"Coding": "coding",
"Maker": "maker",

View File

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