Compare commits

..

16 Commits

Author SHA1 Message Date
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
3769402ade 1.2.2 2019-09-17 10:52:14 -07:00
5c3c83eb52 add a run to avoid tight loop hogging (#913) 2019-09-17 10:52:00 -07:00
0f07a89981 updated docs on offline-app 2019-09-11 19:37:26 -07:00
11d887a213 Releaseing offline app version 1.1.20 (#912) 2019-09-11 18:24:47 -07:00
8150b2dbb0 fix turtle link 2019-09-11 13:23:09 -07:00
22 changed files with 299 additions and 71 deletions

View File

@ -19,6 +19,7 @@ If you found a bug, please try if it hasn't been fixed yet! Go to https://makeco
* You will need to install the latest EV3 firmware on your brick. Instructions on how to do that are located here: https://makecode.mindstorms.com/troubleshoot.
* You will need 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?
@ -28,12 +29,16 @@ We have compiled a guide for EV3 LabView users at https://makecode.mindstorms.co
Go to https://makecode.mindstorms.com. The home screen is filled with videos, tutorials and examples that might be relevant for your missions.
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?
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.

View File

@ -388,12 +388,12 @@
}
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");
window.open("https://makecode.com/api/release/ev3/v1.1.20/win64");
tickEvent("offlineapp.download", { "target": "ev3", "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");
window.open("https://makecode.com/api/release/ev3/v1.1.20/mac64");
tickEvent("offlineapp.download", { "target": "ev3", "platform": "mac64" });
}
</script>

View File

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

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,25 @@
# 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"
}, {
"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)

View File

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

View File

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

View File

@ -0,0 +1,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

@ -179,6 +179,7 @@ namespace sensors {
//% group="Color Sensor"
//% blockGap=8
color(): ColorSensorColor {
this.poke();
this.setMode(ColorSensorMode.Color)
return this.getNumber(NumberFormat.UInt8LE, 0)
}
@ -196,6 +197,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,6 +251,7 @@ namespace sensors {
//% weight=87 blockGap=8
//% group="Color Sensor"
light(mode: LightIntensityMode) {
this.poke();
this.setMode(<ColorSensorMode><number>mode)
switch(mode) {
case LightIntensityMode.ReflectedRaw:
@ -279,6 +282,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,
@ -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

@ -123,7 +123,8 @@ namespace motors {
//% help=motors/stop-all
export function stopAll() {
const b = mkCmd(Output.ALL, DAL.opOutputStop, 0)
writePWM(b)
writePWM(b);
pause(1);
}
/**
@ -132,6 +133,7 @@ namespace motors {
//% group="Move"
export function resetAllMotors() {
reset(Output.ALL)
pause(1);
}
interface MoveSchedule {
@ -259,6 +261,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) {
@ -267,6 +271,9 @@ namespace motors {
this.pauseUntilReady();
// allow robot to settle
this.settle();
} else {
// give a breather to the event system in tight loops
pause(1);
}
}
@ -334,6 +341,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;
}
@ -494,7 +502,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);
@ -602,6 +610,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
@ -697,7 +732,7 @@ namespace motors {
this.init();
speed = Math.clamp(-100, 100, speed >> 0);
if (!speed) {
stop(this._port, this._brake);
this.stop();
return;
}

View File

@ -45,6 +45,7 @@ namespace sensors {
//% weight=64 blockGap=8
//% group="Gyro Sensor"
angle(): number {
this.poke();
if (this.calibrating)
pauseUntil(() => !this.calibrating, 2000);
@ -65,6 +66,7 @@ namespace sensors {
//% weight=65 blockGap=8
//% group="Gyro Sensor"
rate(): number {
this.poke();
if (this.calibrating)
pauseUntil(() => !this.calibrating, 2000);

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

@ -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.1",
"version": "1.2.7",
"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

@ -15,13 +15,13 @@
"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",
"Videos": "videos"
},
"electronManifest": {
"latest": "v1.0.11"
"latest": "v1.1.20"
}
}