Compare commits

...

12 Commits

Author SHA1 Message Date
Peli de Halleux
540a097198 driving base sim 2019-10-01 22:17:47 -07:00
Peli de Halleux
93cfb76f68 add city shaper 2019-10-01 22:02:56 -07:00
Peli de Halleux
8bc3fdc8ba 1.2.21 2019-10-01 15:56:22 -07:00
Peli de Halleux
e8a1e73cf5 1.2.20 2019-10-01 14:54:42 -07:00
Peli de Halleux
e9b2b239ad More gyro lessons (#933)
* updated pauseuntil

* updated youtube link

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

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

* add is calibrating function

* fix integrator

* added example

* docs

* poll faster
2019-10-01 10:11:58 -07:00
21 changed files with 334 additions and 98 deletions

View File

@@ -38,10 +38,7 @@ Next you need to enable the experimental features (this may change in the future
## Download over Bluetooth ## Download over Bluetooth
* go to the **beta** editor https://makecode.mindstorms.com/beta * go to https://makecode.mindstorms.com/
This feature is not yet released so make sure to use the beta editor.
* click on **Download** to start a file download as usual * click on **Download** to start a file download as usual
* on the download dialog, you should see a **Bluetooth** button. Click on the * on the download dialog, you should see a **Bluetooth** button. Click on the
**Bluetooth** button to enable the mode. **Bluetooth** button to enable the mode.
@@ -51,8 +48,9 @@ This feature is not yet released so make sure to use the beta editor.
## Choosing the correct serial port ## Choosing the correct serial port
Unfortunately, the browser dialog does not make it easy to select which serial port is the brick. 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. * 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 ## Known issues

View File

@@ -1,3 +1,3 @@
{ {
"appref": "v1.2.15" "appref": "v1.2.18"
} }

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

@@ -24,6 +24,18 @@
"name": "Bluetooth download (beta)", "name": "Bluetooth download (beta)",
"description": "EXPERIMENTAL! Learn how to download code via Bluetooth.", "description": "EXPERIMENTAL! Learn how to download code via Bluetooth.",
"youTubeId": "VIq8-6Egtqs" "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"
}] }]
``` ```

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

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

View File

@@ -9,11 +9,23 @@
"cardType": "tutorial", "cardType": "tutorial",
"url":"/tutorials/calibrate-gyro", "url":"/tutorials/calibrate-gyro",
"imageUrl":"/static/tutorials/calibrate-gyro.png" "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", "name": "Move Straight",
"description": "Use the gyro to correct the trajectory of the robot", "description": "Use the gyro to correct the trajectory of the robot",
"cardType": "tutorial", "cardType": "tutorial",
"url":"/tutorials/move-straight-with-gyro", "url":"/tutorials/move-straight-with-gyro",
"imageUrl":"/static/tutorials/move-straight-with-gyro.png" "imageUrl":"/static/tutorials/move-straight-with-gyro.png"
}, {
"name": "Drifter",
"description": "Explore how the gyro is drifting",
"cardType": "example",
"url":"/tutorials/drifter",
"imageUrl":"/static/tutorials/drifter.png"
}] }]
``` ```

View File

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

View File

@@ -73,13 +73,7 @@ namespace sensors {
} }
setMode(m: ColorSensorMode) { setMode(m: ColorSensorMode) {
if (m == ColorSensorMode.AmbientLightIntensity) { // don't change threshold after initialization
this.thresholdDetector.setLowThreshold(5);
this.thresholdDetector.setHighThreshold(20);
} else {
this.thresholdDetector.setLowThreshold(20);
this.thresholdDetector.setHighThreshold(80);
}
this._setMode(m) this._setMode(m)
} }

View File

@@ -87,7 +87,7 @@ namespace sensors.internal {
this.devType = DAL.DEVICE_TYPE_NONE this.devType = DAL.DEVICE_TYPE_NONE
this.iicid = '' this.iicid = ''
this.sensors = [] this.sensors = []
this.poller = new Poller(50, () => this.query(), (prev, curr) => this.update(prev, curr)); this.poller = new Poller(25, () => this.query(), (prev, curr) => this.update(prev, curr));
} }
poke() { poke() {
@@ -121,7 +121,7 @@ namespace sensors.internal {
powerMM = control.mmap("/dev/lms_power", 2, 0) powerMM = control.mmap("/dev/lms_power", 2, 0)
devPoller = new Poller(500, () => { return hashDevices(); }, devPoller = new Poller(250, () => { return hashDevices(); },
(prev, curr) => { (prev, curr) => {
detectDevices(); detectDevices();
}); });
@@ -814,10 +814,10 @@ namespace sensors {
export class ThresholdDetector { export class ThresholdDetector {
public id: number; public id: number;
public min: number; private min: number;
public max: number; private max: number;
public lowThreshold: number; private lowThreshold: number;
public highThreshold: number; private highThreshold: number;
public level: number; public level: number;
public state: ThresholdState; public state: ThresholdState;

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

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

View File

@@ -264,8 +264,9 @@ namespace motors {
// allow 500ms for robot to settle // allow 500ms for robot to settle
if (this._brake && this._brakeSettleTime > 0) if (this._brake && this._brakeSettleTime > 0)
pause(this._brakeSettleTime); pause(this._brakeSettleTime);
else else {
pause(1); // give a tiny breather pause(1);
}
} }
protected pauseOnRun(stepsOrTime: number) { protected pauseOnRun(stepsOrTime: number) {
@@ -275,7 +276,6 @@ namespace motors {
// allow robot to settle // allow robot to settle
this.settle(); this.settle();
} else { } else {
// give a breather to the event system in tight loops
pause(1); pause(1);
} }
} }

View File

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

View File

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

View File

@@ -7,12 +7,15 @@ const enum GyroSensorMode {
namespace sensors { namespace sensors {
//% fixedInstances //% fixedInstances
export class GyroSensor extends internal.UartSensor { export class GyroSensor extends internal.UartSensor {
private calibrating: boolean; private _calibrating: boolean;
private _drift: number; private _drift: number;
private _angle: control.EulerIntegrator;
constructor(port: number) { constructor(port: number) {
super(port) super(port)
this.calibrating = false; this._calibrating = false;
this._drift = 0; this._drift = 0;
this._angle = new control.EulerIntegrator();
this._setMode(GyroSensorMode.Rate);
this.setMode(GyroSensorMode.Rate); this.setMode(GyroSensorMode.Rate);
} }
@@ -21,13 +24,17 @@ namespace sensors {
} }
_query(): number { _query(): number {
return this.getNumber(NumberFormat.Int16LE, 0); const v = this.getNumber(NumberFormat.Int16LE, 0);
this._angle.integrate(v - this._drift);
return v;
} }
setMode(m: GyroSensorMode) { setMode(m: GyroSensorMode) {
if (m == GyroSensorMode.Rate && this.mode != m) // decrecated
this._drift = 0; }
this._setMode(m)
isCalibrating(): boolean {
return this._calibrating;
} }
/** /**
@@ -40,15 +47,14 @@ namespace sensors {
//% parts="gyroscope" //% parts="gyroscope"
//% blockNamespace=sensors //% blockNamespace=sensors
//% this.fieldEditor="ports" //% this.fieldEditor="ports"
//% weight=64 //% weight=64 blockGap=8
//% group="Gyro Sensor" //% group="Gyro Sensor"
angle(): number { angle(): number {
this.poke(); this.poke();
if (this.calibrating) if (this._calibrating)
pauseUntil(() => !this.calibrating, 2000); pauseUntil(() => !this._calibrating, 2000);
this.setMode(GyroSensorMode.Angle); return Math.round(this._angle.value);
return this._query();
} }
/** /**
@@ -65,10 +71,8 @@ namespace sensors {
//% group="Gyro Sensor" //% group="Gyro Sensor"
rate(): number { rate(): number {
this.poke(); this.poke();
if (this.calibrating) if (this._calibrating)
pauseUntil(() => !this.calibrating, 2000); pauseUntil(() => !this._calibrating, 2000);
this.setMode(GyroSensorMode.Rate);
return this._query() - this._drift; return this._query() - this._drift;
} }
@@ -85,12 +89,12 @@ namespace sensors {
//% weight=51 blockGap=8 //% weight=51 blockGap=8
//% group="Gyro Sensor" //% group="Gyro Sensor"
calibrate(): void { calibrate(): void {
if (this.calibrating) return; // already in calibration mode if (this._calibrating) return; // already in calibration mode
const statusLight = brick.statusLight(); // save current status light const statusLight = brick.statusLight(); // save current status light
brick.setStatusLight(StatusLight.Orange); brick.setStatusLight(StatusLight.Orange);
this.calibrating = true; this._calibrating = true;
// may be triggered by a button click, // may be triggered by a button click,
// give time for robot to settle // give time for robot to settle
pause(700); pause(700);
@@ -104,7 +108,8 @@ namespace sensors {
brick.setStatusLight(statusLight); // resture previous light brick.setStatusLight(statusLight); // resture previous light
// and we're done // and we're done
this.calibrating = false; this._angle.reset();
this._calibrating = false;
return; return;
} }
@@ -116,22 +121,22 @@ namespace sensors {
// wait till sensor is live // wait till sensor is live
pauseUntil(() => this.isActive(), 7000); pauseUntil(() => this.isActive(), 7000);
// mode toggling // mode toggling
this.setMode(GyroSensorMode.Rate); this._setMode(GyroSensorMode.Rate);
this.setMode(GyroSensorMode.Angle); this._setMode(GyroSensorMode.Angle);
this._setMode(GyroSensorMode.Rate);
// check sensor is ready // check sensor is ready
if (!this.isActive()) { if (!this.isActive()) {
brick.setStatusLight(StatusLight.RedFlash); // didn't work brick.setStatusLight(StatusLight.RedFlash); // didn't work
pause(2000); pause(2000);
brick.setStatusLight(statusLight); // restore previous light brick.setStatusLight(statusLight); // restore previous light
this.calibrating = false; this._angle.reset();
this._calibrating = false;
return; return;
} }
// switch to rate mode // switch to rate mode
this.computeDriftNoCalibration(); this.computeDriftNoCalibration();
// switch back to the desired mode
this.setMode(this.mode);
// and done // and done
brick.setStatusLight(StatusLight.Green); // success brick.setStatusLight(StatusLight.Green); // success
@@ -139,7 +144,8 @@ namespace sensors {
brick.setStatusLight(statusLight); // resture previous light brick.setStatusLight(statusLight); // resture previous light
// and we're done // and we're done
this.calibrating = false; this._angle.reset();
this._calibrating = false;
} }
/** /**
@@ -154,13 +160,37 @@ namespace sensors {
//% weight=50 blockGap=8 //% weight=50 blockGap=8
//% group="Gyro Sensor" //% group="Gyro Sensor"
reset(): void { reset(): void {
if (this.calibrating) return; // already in calibration mode if (this._calibrating) return; // already in calibration mode
this._calibrating = true;
const statusLight = brick.statusLight(); // save current status light
brick.setStatusLight(StatusLight.Orange);
this.calibrating = true;
// send a reset command // send a reset command
super.reset(); super.reset();
this._drift = 0;
this._angle.reset();
pauseUntil(() => this.isActive(), 7000);
// check sensor is ready
if (!this.isActive()) {
brick.setStatusLight(StatusLight.RedFlash); // didn't work
pause(2000);
brick.setStatusLight(statusLight); // restore previous light
this._angle.reset();
this._calibrating = false;
return;
}
this._setMode(GyroSensorMode.Rate);
// and done // and done
this.calibrating = false; brick.setStatusLight(StatusLight.Green); // success
pause(1000);
brick.setStatusLight(statusLight); // resture previous light
// and done
this._angle.reset();
this._calibrating = false;
} }
/** /**
@@ -190,15 +220,35 @@ namespace sensors {
//% weight=10 blockGap=8 //% weight=10 blockGap=8
//% group="Gyro Sensor" //% group="Gyro Sensor"
computeDrift() { computeDrift() {
if (this.calibrating) if (this._calibrating)
pauseUntil(() => !this.calibrating, 2000); pauseUntil(() => !this._calibrating, 2000);
pause(1000); // let the robot settle pause(1000); // let the robot settle
this.computeDriftNoCalibration(); this.computeDriftNoCalibration();
} }
/**
* Pauses the program until the gyro detected
* that the angle changed by the desired amount of degrees.
* @param degrees the degrees to turn
*/
//% help=sensors/gyro/pause-until-rotated
//% block="pause until **gyro** %this|rotated %degrees=rotationPicker|degrees"
//% blockId=gyroPauseUntilRotated
//% parts="gyroscope"
//% blockNamespace=sensors
//% this.fieldEditor="ports"
//% degrees.defl=90
//% weight=63
//% group="Gyro Sensor"
pauseUntilRotated(degrees: number, timeOut?: number): void {
let a = this.angle();
const end = a + degrees;
const direction = (end - a) > 0 ? 1 : -1;
pauseUntil(() => (end - this.angle()) * direction <= 0, timeOut);
}
private computeDriftNoCalibration() { private computeDriftNoCalibration() {
// clear drift // clear drift
this.setMode(GyroSensorMode.Rate);
this._drift = 0; this._drift = 0;
const n = 10; const n = 10;
let d = 0; let d = 0;
@@ -207,22 +257,17 @@ namespace sensors {
pause(20); pause(20);
} }
this._drift = d / n; this._drift = d / n;
this._angle.reset();
} }
_info(): string { _info(): string {
if (this.calibrating) if (this._calibrating)
return "cal..."; return "cal...";
switch (this.mode) { let r = `${this._query()}r`;
case GyroSensorMode.Angle: if (this._drift != 0)
return `${this._query()}>`; r += `-${this._drift | 0}`;
case GyroSensorMode.Rate: return r;
let r = `${this._query()}r`;
if (this._drift != 0)
r += `-${this._drift | 0}`;
return r;
}
return "";
} }
} }
@@ -237,4 +282,17 @@ namespace sensors {
//% fixedInstance whenUsed block="4" jres=icons.port4 //% fixedInstance whenUsed block="4" jres=icons.port4
export const gyro4: GyroSensor = new GyroSensor(4) export const gyro4: GyroSensor = new GyroSensor(4)
/**
* Get the rotation angle field editor
* @param degrees angle in degrees, eg: 90
*/
//% blockId=rotationPicker block="%degrees"
//% blockHidden=true shim=TD_ID
//% colorSecondary="#FFFFFF"
//% degrees.fieldEditor="numberdropdown" degrees.fieldOptions.decompileLiterals=true
//% degrees.fieldOptions.data='[["30", 30], ["45", 45], ["60", 60], ["90", 90], ["180", 180], ["-30", -30], ["-45", -45], ["-60", -60], ["-90", -90], ["-180", -180]]'
export function __rotationPicker(degrees: number): number {
return degrees;
}
} }

View File

@@ -1,6 +1,6 @@
{ {
"name": "pxt-ev3", "name": "pxt-ev3",
"version": "1.2.17", "version": "1.2.21",
"description": "LEGO MINDSTORMS EV3 for Microsoft MakeCode", "description": "LEGO MINDSTORMS EV3 for Microsoft MakeCode",
"private": false, "private": false,
"keywords": [ "keywords": [

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

View File

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

View File

@@ -2,12 +2,11 @@
namespace pxsim.visuals { namespace pxsim.visuals {
const MAX_RATE = 40; const MAX_RATE = 40;
const MAX_ANGLE = 360;
export class RotationSliderControl extends ControlView<GyroSensorNode> { export class RotationSliderControl extends ControlView<GyroSensorNode> {
private group: SVGGElement; private group: SVGGElement;
private slider: SVGGElement; private slider: SVGGElement;
private text: SVGTextElement; private rateText: SVGTextElement;
private static SLIDER_WIDTH = 70; private static SLIDER_WIDTH = 70;
//private static SLIDER_HEIGHT = 78; //private static SLIDER_HEIGHT = 78;
@@ -26,8 +25,8 @@ namespace pxsim.visuals {
pxsim.svg.child(this.slider, "circle", { 'cx': 9, 'cy': 50, 'r': 13, 'style': 'fill: #f12a21' }); pxsim.svg.child(this.slider, "circle", { 'cx': 9, 'cy': 50, 'r': 13, 'style': 'fill: #f12a21' });
pxsim.svg.child(this.slider, "circle", { 'cx': 9, 'cy': 50, 'r': 12.5, 'style': 'fill: none;stroke: #b32e29' }); pxsim.svg.child(this.slider, "circle", { 'cx': 9, 'cy': 50, 'r': 12.5, 'style': 'fill: none;stroke: #b32e29' });
this.text = pxsim.svg.child(this.group, "text", { this.rateText = pxsim.svg.child(this.group, "text", {
'x': RotationSliderControl.SLIDER_WIDTH / 2, 'x': this.getInnerWidth() / 2,
'y': RotationSliderControl.SLIDER_WIDTH * 1.2, 'y': RotationSliderControl.SLIDER_WIDTH * 1.2,
'text-anchor': 'middle', 'dominant-baseline': 'middle', 'text-anchor': 'middle', 'dominant-baseline': 'middle',
'style': 'font-size: 16px', 'style': 'font-size: 16px',
@@ -72,17 +71,10 @@ namespace pxsim.visuals {
return; return;
} }
const node = this.state; const node = this.state;
let percentage = 50; const rate = node.getRate();
if (node.getMode() == GyroSensorMode.Rate) { this.rateText.textContent = `${rate}°/s`
const rate = node.getValue(); // cap rate at 40deg/s
this.text.textContent = `${rate}°/s` const percentage = 50 + Math.sign(rate) * Math.min(MAX_RATE, Math.abs(rate)) / MAX_RATE * 50;
// cap rate at 40deg/s
percentage = 50 + Math.sign(rate) * Math.min(MAX_RATE, Math.abs(rate)) / MAX_RATE * 50;
} else { //angle
const angle = node.getValue();
this.text.textContent = `${angle}°`
percentage = 50 + Math.sign(angle) * Math.min(MAX_ANGLE, Math.abs(angle)) / MAX_ANGLE * 50;
}
const x = RotationSliderControl.SLIDER_WIDTH * percentage / 100; const x = RotationSliderControl.SLIDER_WIDTH * percentage / 100;
const y = Math.abs((percentage - 50) / 50) * 10; const y = Math.abs((percentage - 50) / 50) * 10;
this.slider.setAttribute("transform", `translate(${x}, ${y})`); this.slider.setAttribute("transform", `translate(${x}, ${y})`);
@@ -97,11 +89,7 @@ namespace pxsim.visuals {
t = -(t - 0.5) * 2; // [-1,1] t = -(t - 0.5) * 2; // [-1,1]
const state = this.state; const state = this.state;
if (state.getMode() == GyroSensorMode.Rate) { state.setRate(MAX_RATE * t);
state.setRate(MAX_RATE * t);
} else {
state.setAngle(MAX_ANGLE * t)
}
} }
} }

View File

@@ -0,0 +1,55 @@
namespace pxsim {
export class RobotGameTable {
readonly ctx: CanvasRenderingContext2D;
readonly data: ImageData;
cx: number; // cm
cy: number; // cm
angle: number; // radians
cwidth: number; // cm
constructor(public canvas: HTMLCanvasElement, public scale: number) {
this.ctx = this.canvas.getContext("2d");
this.data = this.ctx.getImageData(0, 0, this.canvas.width, this.canvas.height);
this.cx = this.width / 2;
this.cy = 0;
}
/**
* Gets the width in cm
*/
get width() {
return this.canvas.width * this.scale;
}
/**
* gets the height in cm
*/
get height() {
return this.canvas.height * this.scale;
}
color(): number {
// compute color sensor position from center;
// todo
const px = Math.max(0, Math.min(this.data.width, (this.cx ) / this.scale));
const py = Math.max(0, Math.min(this.data.height, (this.cy ) / this.scale));
// get color
const i = px * this.data.width + py;
let c =
(this.data.data[i] << 16) | (this.data.data[i + 1] << 8) | (this.data.data[i + 2]);
// map color to known color
return c;
}
intensity(): number {
const c = this.color();
return ((c >> 16 & 0xff) + (c >> 8 & 0xff) + (c & 0xff)) / 3;
}
ultrasonicDistance() {
// obstacles?
}
}
}

View File

@@ -18,7 +18,7 @@
"Touch Sensor Tutorials": "tutorials/touch-sensor", "Touch Sensor Tutorials": "tutorials/touch-sensor",
"Color Sensor Tutorials": "tutorials/color-sensor", "Color Sensor Tutorials": "tutorials/color-sensor",
"Ultrasonic Sensor Tutorials": "tutorials/ultrasonic-sensor", "Ultrasonic Sensor Tutorials": "tutorials/ultrasonic-sensor",
"Gyro Sensor Tutorials": "tutorials/gyro", "Gyro Tutorials": "tutorials/gyro",
"Infrared Sensor Tutorials": "tutorials/infrared-sensor", "Infrared Sensor Tutorials": "tutorials/infrared-sensor",
"FLL / City Shaper": "tutorials/city-shaper", "FLL / City Shaper": "tutorials/city-shaper",
"Design Engineering": "design-engineering", "Design Engineering": "design-engineering",