Compare commits

...

18 Commits

Author SHA1 Message Date
a7d002d949 0.0.45 2017-12-19 15:10:32 -08:00
93fd8c8c78 removing more icons 2017-12-19 15:10:13 -08:00
1765ca2d35 Merge pull request #116 from Microsoft/revert_screenopt
Use game loop instead of animation queue
2017-12-19 14:58:37 -08:00
1ab7ae6cfa minor PR feedback 2017-12-19 14:57:28 -08:00
3acf4e9ac5 More hardware tests (#119) 2017-12-19 14:55:48 -08:00
ef5fa9ae82 Minor fix to killing the animation when the sim is killed 2017-12-19 14:55:43 -08:00
e1f7a5b8cf Merge pull request #118 from Microsoft/ambient_light_threshold
Set high/low to 20/5 for ambient light mode
2017-12-19 14:42:54 -08:00
2c73bfc813 Set high/low to 20/5 2017-12-19 14:40:30 -08:00
d78d9c8686 basic ports view (#115)
* basic ports view

* slight adjustement of rendering
2017-12-19 14:26:57 -08:00
2157af3e63 Using game loop instead of queueAnimationUpdate 2017-12-19 14:20:35 -08:00
eac3e183c3 better test support 2017-12-19 13:10:40 -08:00
785ddff706 Reverting screen optimization to use SetInterval and didChange 2017-12-19 12:53:12 -08:00
e07d6e3a31 0.0.44 2017-12-19 11:53:50 -08:00
763ad3f763 0.0.43 2017-12-19 11:38:02 -08:00
919a03951c Removing icons (#114)
* removing icons

* added "pause for light"
2017-12-19 11:37:33 -08:00
9e427898ae added test framework (#113)
* added test framework

* added toString on motors

* enabling logs
2017-12-19 07:07:50 -08:00
60bf3a17d3 a mini-console support with scroll up / down (#112)
* a mini-console support with scroll up / down

* fix compile error
2017-12-18 22:36:32 -08:00
0529759a80 Fix ultrasonic value to use cm instead of 0.1 cm units (#110)
* Fix simulator

* use 250 instead of 255
2017-12-18 20:30:56 -08:00
56 changed files with 924 additions and 487 deletions

View File

@ -1,94 +0,0 @@
```typescript
let errors: string[] = [];
let tachoB = 0;
let tachoC = 0;
function assert(name: string, condition: boolean) {
if (!condition) {
errors.push(name)
brick.print("error:" + name, 0, 60)
}
}
function assertClose(name: string, expected: number, actual: number, tolerance = 5) {
const diff = Math.abs(expected - actual);
assert(name + ` ${expected}vs${actual}`, diff < tolerance);
}
function state(name: string, motor: motors.SingleMotor, line: number) {
brick.print(`${name}: ${motor.speed()}%,${motor.angle()}deg`, 0, 12 * line)
}
function test(name: string, f: () => void, check?: () => void) {
motors.stopAllMotors();
motors.largeB.clearCount()
motors.largeC.clearCount()
motors.largeB.setBrake(false)
motors.largeC.setBrake(false)
motors.largeB.setReversed(false);
motors.largeC.setReversed(false);
loops.pause(500);
tachoB = motors.largeB.angle()
tachoC = motors.largeC.angle()
brick.clearScreen()
brick.print(name, 0, 0)
state("B", motors.largeB, 1)
state("C", motors.largeC, 2)
f();
loops.pause(3000);
state("B", motors.largeB, 3)
state("C", motors.largeC, 4)
if (check)
check()
motors.stopAllMotors();
loops.pause(1000);
}
brick.buttonLeft.onEvent(ButtonEvent.Click, function () {
test("lgBC set speed 25", () => {
motors.largeBC.setSpeed(25)
}, () => {
assertClose("speedB", 25, motors.largeB.speed());
assertClose("speedC", 25, motors.largeC.speed());
});
})
brick.buttonEnter.onEvent(ButtonEvent.Click, function () {
test("lgB set speed 10", () => {
motors.largeB.setSpeed(10)
}, () => assertClose("speedB", 10, motors.largeB.speed()));
test("lgB set speed 25 (reversed)", () => {
motors.largeB.setReversed(true)
motors.largeB.setSpeed(25)
}, () => assertClose("speedB", -25, motors.largeB.speed()));
test("lgBC set speed 5", () => {
motors.largeBC.setSpeed(5)
}, () => {
assertClose("speedB", 5, motors.largeB.speed());
assertClose("speedC", 5, motors.largeC.speed());
});
test("lgBC steer 50% 2x", () => {
motors.largeBC.setBrake(true)
motors.largeBC.steer(50, 50, 2, MoveUnit.Rotations)
}, () => assertClose("largeB", 720, motors.largeB.angle()));
test("lgBC steer 50% 500deg", () => {
motors.largeBC.setBrake(true)
motors.largeBC.steer(50, 50, 500, MoveUnit.Degrees)
}, () => assertClose("largeB", 500, motors.largeB.angle()));
test("lgBC steer 50% 2s", () => {
motors.largeBC.setBrake(true)
motors.largeBC.steer(50, 50, 2000, MoveUnit.MilliSeconds)
})
test("lgBC tank 50% 720deg", () => {
motors.largeBC.setBrake(true)
motors.largeBC.tank(50, 50, 720, MoveUnit.Degrees)
}, () => assertClose("largeB", 720, motors.largeB.angle()));
brick.clearScreen()
brick.print(`${errors.length} errors`, 0, 0)
let l = 1;
for (const error of errors)
brick.print(`error: ${error}`, 0, l++ * 12)
})
```

View File

@ -123,11 +123,6 @@
"String.substr": "Return a substring of the current string.",
"String.substr|param|length": "number of characters to extract",
"String.substr|param|start": "first character index; can be negative from counting from the end, eg:0",
"console": "Reading and writing data to the console output.",
"console.log": "Write a line of text to the console output.",
"console.logValue": "Write a name:value pair as a line of text to the console output.",
"console.logValue|param|name": "name of the value stream, eg: \"x\"",
"console.logValue|param|value": "to write",
"control": "Program controls and events.",
"control.AnimationQueue.cancel": "Cancels the current running animation and clears the queue",
"control.AnimationQueue.runUntilDone": "Runs 'render' in a loop until it returns false or the 'stop' function is called",

View File

@ -19,9 +19,6 @@
"String.length|block": "length of %VALUE",
"String.substr|block": "substring of %this=text|from %start|of length %length",
"String|block": "String",
"console.logValue|block": "console|log value %name|= %value",
"console.log|block": "console|log %text",
"console|block": "console",
"control.assert|block": "assert %cond|with value %code",
"control.deviceSerialNumber|block": "device serial number",
"control.millis|block": "millis (ms)",
@ -46,7 +43,6 @@
"{id:category}Arrays": "Arrays",
"{id:category}Boolean": "Boolean",
"{id:category}Buffer": "Buffer",
"{id:category}Console": "Console",
"{id:category}Control": "Control",
"{id:category}Helpers": "Helpers",
"{id:category}Loops": "Loops",

View File

@ -17,8 +17,7 @@
"control.cpp",
"control.ts",
"serial.cpp",
"serial.ts",
"console.ts"
"serial.ts"
],
"testFiles": [
"test.ts"

View File

@ -8,5 +8,8 @@
"sensors.ColorSensor.onColorDetected|param|handler": "the code to run when detected",
"sensors.ColorSensor.onLightChanged": "Registers code to run when the ambient light changes.",
"sensors.ColorSensor.onLightChanged|param|condition": "the light condition",
"sensors.ColorSensor.onLightChanged|param|handler": "the code to run when detected"
"sensors.ColorSensor.onLightChanged|param|handler": "the code to run when detected",
"sensors.ColorSensor.pauseForColor": "Waits for the given color to be detected",
"sensors.ColorSensor.pauseForColor|param|color": "the color to detect",
"sensors.ColorSensor.pauseForLight": "Waits for the given color to be detected"
}

View File

@ -13,14 +13,16 @@
"LightCondition.Dark|block": "dark",
"LightIntensityMode.Ambient|block": "ambient light",
"LightIntensityMode.Reflected|block": "reflected light",
"sensors.ColorSensor.color|block": "`icons.colorSensor` %sensor| color",
"sensors.ColorSensor.light|block": "`icons.colorSensor` %sensor|%mode",
"sensors.ColorSensor.onColorDetected|block": "on `icons.colorSensor` %sensor|detected color %color",
"sensors.ColorSensor.onLightChanged|block": "on `icons.colorSensor` %sensor|%mode|%condition",
"sensors.color1|block": "1",
"sensors.color2|block": "2",
"sensors.color3|block": "3",
"sensors.color4|block": "4",
"sensors.ColorSensor.color|block": "%sensor| color",
"sensors.ColorSensor.light|block": "%sensor|%mode",
"sensors.ColorSensor.onColorDetected|block": "on %sensor|detected color %color",
"sensors.ColorSensor.onLightChanged|block": "on %sensor|%mode|%condition",
"sensors.ColorSensor.pauseForColor|block": "pause %sensor|for color %color",
"sensors.ColorSensor.pauseForLight|block": "pause %sensor|for %mode|light %condition",
"sensors.color1|block": "color 1",
"sensors.color2|block": "color 2",
"sensors.color3|block": "color 3",
"sensors.color4|block": "color 4",
"sensors|block": "sensors",
"{id:category}Sensors": "Sensors",
"{id:group}Color Sensor": "Color Sensor"

View File

@ -68,13 +68,20 @@ namespace sensors {
}
setMode(m: ColorSensorMode) {
if (m == ColorSensorMode.AmbientLightIntensity) {
this.thresholdDetector.setLowThreshold(5);
this.thresholdDetector.setHighThreshold(20);
} else {
this.thresholdDetector.setLowThreshold(20);
this.thresholdDetector.setHighThreshold(80);
}
this._setMode(m)
}
/**
* Gets the current color mode
*/
colorMode() {
colorMode() {
return <ColorSensorMode>this.mode;
}
@ -99,11 +106,9 @@ namespace sensors {
* @param handler the code to run when detected
*/
//% help=sensors/color-sensor/on-color-detected
//% block="on `icons.colorSensor` %sensor|detected color %color"
//% block="on %sensor|detected color %color"
//% blockId=colorOnColorDetected
//% parts="colorsensor"
//% sensor.fieldEditor="imagedropdown"
//% sensor.fieldOptions.columns=4
//% blockNamespace=sensors
//% weight=100 blockGap=8
//% group="Color Sensor"
@ -115,16 +120,33 @@ namespace sensors {
control.raiseEvent(this._id, v);
}
/**
* Waits for the given color to be detected
* @param color the color to detect
*/
//% help=sensors/color-sensor/pause-for-color
//% block="pause %sensor|for color %color"
//% blockId=colorPauseForColorDetected
//% parts="colorsensor"
//% blockNamespace=sensors
//% weight=99 blockGap=8
//% group="Color Sensor"
pauseForColor(color: ColorSensorColor) {
this.setMode(ColorSensorMode.Color);
if (this.color() != color) {
const v = this._colorEventValue(<number>color);
control.waitForEvent(this._id, v);
}
}
/**
* Get the current color from the color sensor.
* @param sensor the color sensor to query the request
*/
//% help=sensors/color-sensor/color
//% block="`icons.colorSensor` %sensor| color"
//% block="%sensor| color"
//% blockId=colorGetColor
//% parts="colorsensor"
//% sensor.fieldEditor="imagedropdown"
//% sensor.fieldOptions.columns=4
//% blockNamespace=sensors
//% weight=99
//% group="Color Sensor"
@ -139,17 +161,32 @@ namespace sensors {
* @param handler the code to run when detected
*/
//% help=sensors/color-sensor/on-light-changed
//% block="on `icons.colorSensor` %sensor|%mode|%condition"
//% block="on %sensor|%mode|%condition"
//% blockId=colorOnLightChanged
//% parts="colorsensor"
//% sensor.fieldEditor="imagedropdown"
//% sensor.fieldOptions.columns=4
//% blockNamespace=sensors
//% weight=89 blockGap=8
//% group="Color Sensor"
onLightChanged(mode: LightIntensityMode, condition: LightCondition, handler: () => void) {
control.onEvent(this._id, <number>condition, handler);
this.setMode(<ColorSensorMode><number>mode)
control.onEvent(this._id, <number>condition, handler);
}
/**
* Waits for the given color to be detected
* @param color the color to detect
*/
//% help=sensors/color-sensor/pause-for-light
//% block="pause %sensor|for %mode|light %condition"
//% blockId=colorPauseForLight
//% parts="colorsensor"
//% blockNamespace=sensors
//% weight=88 blockGap=8
//% group="Color Sensor"
pauseForLight(mode: LightIntensityMode, condition: LightCondition) {
this.setMode(<ColorSensorMode><number>mode)
if (this.thresholdDetector.state != <number>condition)
control.waitForEvent(this._id, <number>condition)
}
/**
@ -157,13 +194,11 @@ namespace sensors {
* @param sensor the color sensor port
*/
//% help=sensors/color-sensor/light
//% block="`icons.colorSensor` %sensor|%mode"
//% block="%sensor|%mode"
//% blockId=colorLight
//% parts="colorsensor"
//% sensor.fieldEditor="imagedropdown"
//% sensor.fieldOptions.columns=4
//% blockNamespace=sensors
//% weight=88
//% weight=87
//% group="Color Sensor"
light(mode: LightIntensityMode) {
this.setMode(<ColorSensorMode><number>mode)
@ -181,15 +216,15 @@ namespace sensors {
}
}
//% whenUsed block="1" weight=95 fixedInstance jres=icons.port1
//% whenUsed block="color 1" weight=95 fixedInstance jres=icons.port1
export const color1: ColorSensor = new ColorSensor(1)
//% whenUsed block="2" weight=90 fixedInstance jres=icons.port2
//% whenUsed block="color 2" weight=90 fixedInstance jres=icons.port2
export const color2: ColorSensor = new ColorSensor(2)
//% whenUsed block="3" weight=90 fixedInstance jres=icons.port3
//% whenUsed block="color 3" weight=90 fixedInstance jres=icons.port3
export const color3: ColorSensor = new ColorSensor(3)
//% whenUsed block="4" weight=90 fixedInstance jres=icons.port4
//% whenUsed block="color 4" weight=90 fixedInstance jres=icons.port4
export const color4: ColorSensor = new ColorSensor(4)
}

View File

@ -32,17 +32,25 @@
"brick.lightPattern": "Pattern block.",
"brick.lightPattern|param|pattern": "the lights pattern to use. eg: LightsPattern.Green",
"brick.print": "Show text on the screen.",
"brick.printPorts": "Prints the port states on the screen",
"brick.print|param|text": "the text to print on the screen, eg: \"Hello world\"",
"brick.print|param|x": "the starting position's x coordinate, eg: 0",
"brick.print|param|y": "the starting position's x coordinate, eg: 0",
"brick.setLight": "Set lights.",
"brick.setLight|param|pattern": "the lights pattern to use.",
"brick.setPixel": "Sets a pixel on or off",
"brick.setPixel|param|on": "a value indicating if the pixel should be on or off",
"brick.setPixel|param|x": "the starting position's x coordinate, eg: 0",
"brick.setPixel|param|y": "the starting position's x coordinate, eg: 0",
"brick.setStatusLight": "Set lights.",
"brick.setStatusLight|param|pattern": "the lights pattern to use.",
"brick.showImage": "Shows an image on screen",
"brick.showImage|param|image": "image to draw",
"console": "Reading and writing data to the console output.\n\nReading and writing data to the console output.",
"console.addListener": "Adds a listener for the log messages",
"console.log": "Write a line of text to the console output.",
"console.logValue": "Write a name:value pair as a line of text to the console output.",
"console.logValue|param|name": "name of the value stream, eg: \"x\"",
"console.logValue|param|value": "to write",
"console.sendToScreen": "Sends the log messages to the brick screen and uses the brick up and down buttons to scroll.",
"control": "Program controls and events.",
"control.allocateNotifyEvent": "Allocates the next user notification event",
"control.deviceFirmwareVersion": "Determine the version of system software currently running.",
@ -51,23 +59,24 @@
"control.raiseEvent": "Announce that an event happened to registered handlers.",
"control.raiseEvent|param|src": "ID of the Component that generated the event",
"control.raiseEvent|param|value": "Component specific code indicating the cause of the event.",
"motors.Motor.isReady": "Returns a value indicating if the motor is still running a previous command.",
"motors.Motor.move": "Moves the motor by a number of rotations, degress or seconds",
"motors.Motor.move|param|speed": "the speed from ``100`` full forward to ``-100`` full backward, eg: 50",
"motors.Motor.move|param|unit": "the meaning of the value",
"motors.Motor.move|param|value": "the move quantity, eg: 2",
"motors.Motor.pauseUntilReady": "Pauses the execution until the previous command finished.",
"motors.Motor.pauseUntilReady|param|timeOut": "optional maximum pausing time in milliseconds",
"motors.Motor.reset": "Resets the motor(s).",
"motors.Motor.setBrake": "Sets the automatic brake on or off when the motor is off",
"motors.Motor.setBrake|param|brake": "a value indicating if the motor should break when off",
"motors.Motor.setReversed": "Reverses the motor polarity",
"motors.Motor.setSpeed": "Sets the speed of the motor.",
"motors.Motor.setSpeed|param|speed": "the speed from ``100`` full forward to ``-100`` full backward, eg: 50",
"motors.Motor.stop": "Stops the motor(s).",
"motors.SingleMotor.angle": "Gets motor ration angle.",
"motors.SingleMotor.clearCount": "Clears the motor count",
"motors.SingleMotor.speed": "Gets motor actual speed.",
"motors.Motor.angle": "Gets motor ration angle.",
"motors.Motor.clearCount": "Clears the motor count",
"motors.Motor.speed": "Gets motor actual speed.",
"motors.Motor.toString": "Returns the status of the motor",
"motors.MotorBase.isReady": "Returns a value indicating if the motor is still running a previous command.",
"motors.MotorBase.move": "Moves the motor by a number of rotations, degress or seconds",
"motors.MotorBase.move|param|speed": "the speed from ``100`` full forward to ``-100`` full backward, eg: 50",
"motors.MotorBase.move|param|unit": "the meaning of the value",
"motors.MotorBase.move|param|value": "the move quantity, eg: 2",
"motors.MotorBase.pauseUntilReady": "Pauses the execution until the previous command finished.",
"motors.MotorBase.pauseUntilReady|param|timeOut": "optional maximum pausing time in milliseconds",
"motors.MotorBase.reset": "Resets the motor(s).",
"motors.MotorBase.setBrake": "Sets the automatic brake on or off when the motor is off",
"motors.MotorBase.setBrake|param|brake": "a value indicating if the motor should break when off",
"motors.MotorBase.setReversed": "Reverses the motor polarity",
"motors.MotorBase.setSpeed": "Sets the speed of the motor.",
"motors.MotorBase.setSpeed|param|speed": "the speed from ``100`` full forward to ``-100`` full backward, eg: 50",
"motors.MotorBase.stop": "Stops the motor(s).",
"motors.SynchedMotorPair.steer": "Turns the motor and the follower motor by a number of rotations",
"motors.SynchedMotorPair.steer|param|speed": "the speed from ``100`` full forward to ``-100`` full backward, eg: 50",
"motors.SynchedMotorPair.steer|param|steering": "the ratio of power sent to the follower motor, from ``-100`` to ``100``",
@ -77,7 +86,17 @@
"motors.SynchedMotorPair.tank|param|speedRight": "the speed on the right motor, eg: 50",
"motors.SynchedMotorPair.tank|param|unit": "@param speedLeft the speed on the left motor, eg: 50",
"motors.SynchedMotorPair.tank|param|value": "the amount of movement, eg: 2",
"motors.SynchedMotorPair.toString": "Returns the name(s) of the motor",
"motors.mkCmd": "Allocates a message buffer",
"motors.mkCmd|param|addSize": "required additional bytes",
"motors.mkCmd|param|cmd": "command id",
"motors.mkCmd|param|out": "ports",
"motors.readPWM": "Sends and receives a message from the motors device",
"motors.readPWM|param|buf": "message buffer",
"motors.resetAllMotors": "Resets all motors",
"motors.stopAllMotors": "Stops all motors",
"motors.writePWM": "Sends a command to the motors device",
"motors.writePWM|param|buf": "the command buffer",
"output.createBuffer": "Create a new zero-initialized buffer.",
"output.createBuffer|param|size": "number of bytes in the buffer",
"screen.clear": "Clear screen and reset font to normal.",

View File

@ -24,32 +24,37 @@
"Output.CD|block": "C+D",
"Output.C|block": "C",
"Output.D|block": "D",
"brick.Button.isPressed|block": "`icons.brickButtons` %button|is pressed",
"brick.Button.onEvent|block": "on `icons.brickButtons` %button|%event",
"brick.Button.pauseUntil|block": "pause until `icons.brickButtons` %button|%event",
"brick.Button.wasPressed|block": "`icons.brickButtons` %button|was pressed",
"brick.Button.isPressed|block": "%button|is pressed",
"brick.Button.onEvent|block": "on %button|%event",
"brick.Button.pauseUntil|block": "pause until %button|%event",
"brick.Button.wasPressed|block": "%button|was pressed",
"brick._imagePicker|block": "%image",
"brick.buttonDown|block": "down",
"brick.buttonEnter|block": "enter",
"brick.buttonLeft|block": "left",
"brick.buttonRight|block": "right",
"brick.buttonUp|block": "up",
"brick.clearScreen|block": "`icons.brickDisplay` clear screen",
"brick.clearScreen|block": "clear screen",
"brick.lightPattern|block": "%pattern",
"brick.print|block": "`icons.brickDisplay` print %text| at x: %x| y: %y",
"brick.setPixel|block": "`icons.brickDisplay` set pixel %on| at x: %x| y: %y",
"brick.setStatusLight|block": "set `icons.brickButtons` to %pattern=led_pattern",
"brick.showImage|block": "`icons.brickDisplay` show image %image=screen_image_picker",
"brick.printPorts|block": "print ports",
"brick.print|block": "print %text| at x: %x| y: %y",
"brick.setLight|block": "set light to %pattern=led_pattern",
"brick.setPixel|block": "set pixel %on| at x: %x| y: %y",
"brick.showImage|block": "show image %image=screen_image_picker",
"brick|block": "brick",
"console.logValue|block": "console|log value %name|= %value",
"console.log|block": "console|log %text",
"console.sendToScreen|block": "send console to screen",
"console|block": "console",
"control.raiseEvent|block": "raise event|from %src|with value %value",
"control|block": "control",
"motors.Motor.move|block": "move `icons.motorLarge` %motor|for %value|%unit|at %speed|%",
"motors.Motor.pauseUntilReady|block": "%motor|pause until ready",
"motors.Motor.setBrake|block": "set `icons.motorLarge` %motor|brake %brake",
"motors.Motor.setReversed|block": "set `icons.motorLarge` %motor|reversed %reversed",
"motors.Motor.setSpeed|block": "set speed of `icons.motorLarge` %motor|to %speed|%",
"motors.SingleMotor.angle|block": "`icons.motorLarge` %motor|angle",
"motors.SingleMotor.speed|block": "`icons.motorLarge` %motor|speed",
"motors.Motor.angle|block": "%motor|angle",
"motors.Motor.speed|block": "%motor|speed",
"motors.MotorBase.move|block": "move %motor|for %value|%unit|at %speed|%",
"motors.MotorBase.pauseUntilReady|block": "%motor|pause until ready",
"motors.MotorBase.setBrake|block": "set %motor|brake %brake",
"motors.MotorBase.setReversed|block": "set %motor|reversed %reversed",
"motors.MotorBase.setSpeed|block": "set speed of %motor|to %speed|%",
"motors.SynchedMotorPair.steer|block": "steer %chassis|%steering|%|at speed %speed|%|by %value|%unit",
"motors.SynchedMotorPair.tank|block": "tank %chassis|left %speedLeft|%|right %speedRight|%|by %value|%unit",
"motors.largeAB|block": "large A+B",
@ -71,6 +76,7 @@
"sensors|block": "sensors",
"serial|block": "serial",
"{id:category}Brick": "Brick",
"{id:category}Console": "Console",
"{id:category}Control": "Control",
"{id:category}Image": "Image",
"{id:category}Images": "Images",

View File

@ -85,7 +85,7 @@ namespace brick {
* @param button the button to query the request
*/
//% help=input/button/is-pressed
//% block="`icons.brickButtons` %button|is pressed"
//% block="%button|is pressed"
//% blockId=buttonIsPressed
//% parts="brick"
//% blockNamespace=brick
@ -100,7 +100,7 @@ namespace brick {
* @param button the button to query the request
*/
//% help=input/button/was-pressed
//% block="`icons.brickButtons` %button|was pressed"
//% block="%button|was pressed"
//% blockId=buttonWasPressed
//% parts="brick"
//% blockNamespace=brick
@ -119,7 +119,7 @@ namespace brick {
* @param body code to run when the event is raised
*/
//% help=input/button/on-event
//% blockId=buttonEvent block="on `icons.brickButtons` %button|%event"
//% blockId=buttonEvent block="on %button|%event"
//% parts="brick"
//% blockNamespace=brick
//% weight=99 blockGap=8
@ -133,7 +133,7 @@ namespace brick {
* @param ev the event to wait for
*/
//% help=input/button/pause-until
//% blockId=buttonWaitUntil block="pause until `icons.brickButtons` %button|%event"
//% blockId=buttonWaitUntil block="pause until %button|%event"
//% parts="brick"
//% blockNamespace=brick
//% weight=98 blockGap=8
@ -248,9 +248,9 @@ namespace brick {
* Set lights.
* @param pattern the lights pattern to use.
*/
//% blockId=setLights block="set `icons.brickButtons` to %pattern=led_pattern"
//% weight=100 group="Light"
export function setStatusLight(pattern: number): void {
//% blockId=setLights block="set light to %pattern=led_pattern"
//% weight=100 group="Buttons"
export function setLight(pattern: number): void {
if (currPattern === pattern)
return
currPattern = pattern

106
libs/core/console.ts Normal file
View File

@ -0,0 +1,106 @@
/// <reference no-default-lib="true"/>
/**
* Reading and writing data to the console output.
*/
//% weight=12 color=#002050 icon="\uf120"
//% advanced=true
namespace console {
type Listener = (text: string) => void;
const listeners: Listener[] = [
(text: string) => serial.writeLine(text)
];
/**
* Write a line of text to the console output.
* @param value to send
*/
//% weight=90
//% help=console/log blockGap=8
//% blockId=console_log block="console|log %text"
export function log(text: string): void {
for (let i = 0; i < listeners.length; ++i)
listeners[i](text);
}
/**
* Write a name:value pair as a line of text to the console output.
* @param name name of the value stream, eg: "x"
* @param value to write
*/
//% weight=88 blockGap=8
//% help=console/log-value
//% blockId=console_log_value block="console|log value %name|= %value"
export function logValue(name: string, value: number): void {
log(`${name}: ${value}`)
}
/**
* Adds a listener for the log messages
* @param listener
*/
//%
export function addListener(listener: (text: string) => void) {
if (!listener) return;
listeners.push(listener);
}
/**
* Sends the log messages to the brick screen and uses the brick up and down buttons to scroll.
*/
//% blockId=logsendtostreen block="send console to screen"
//% weight=1
export function sendToScreen(): void {
console.screen.attach();
}
}
namespace console.screen {
const maxLines = 100;
const screenLines = 8;
let lines: string[];
let scrollPosition = 0;
export function attach() {
if (!lines) {
lines = [];
console.addListener(log);
brick.buttonUp.onEvent(ButtonEvent.Click, () => scroll(-1))
brick.buttonDown.onEvent(ButtonEvent.Click, () => scroll(1))
}
}
function printLog() {
brick.clearScreen()
if (!lines) return;
for (let i = 0; i < screenLines; ++i) {
const line = lines[i + scrollPosition];
if (line)
brick.print(line, 0, 4 + i * brick.LINE_HEIGHT)
}
}
function scroll(pos: number) {
if (!pos) return;
scrollPosition += pos >> 0;
if (scrollPosition >= lines.length) scrollPosition = lines.length - 1;
if (scrollPosition < 0) scrollPosition = 0;
printLog();
}
function log(msg: string): void {
lines.push(msg);
if (lines.length + 5 > maxLines) {
lines.splice(0, maxLines - lines.length);
scrollPosition = Math.min(scrollPosition, lines.length - 1)
}
// move down scroll once it gets large than the screen
if (lines.length > screenLines
&& lines.length >= scrollPosition + screenLines) {
scrollPosition++;
}
printLog();
}
}

View File

@ -74,6 +74,11 @@ namespace sensors.internal {
}
export function getActiveSensors(): Sensor[] {
init();
return sensorInfos.filter(si => si.sensor && si.sensor.isActive()).map(si => si.sensor);
}
function readUartInfo(port: number, mode: number) {
let buf = output.createBuffer(UartCtlOff.Size)
buf[UartCtlOff.Port] = port
@ -196,7 +201,7 @@ namespace sensors.internal {
private lowThreshold: number;
private highThreshold: number;
private level: number;
private state: ThresholdState;
public state: ThresholdState;
constructor(id: number, min = 0, max = 100, lowThreshold = 20, highThreshold = 80) {
this.id = id;

View File

@ -53,32 +53,56 @@ namespace motors {
motorMM = control.mmap("/dev/lms_motor", MotorDataOff.Size * DAL.NUM_OUTPUTS, 0)
if (!motorMM) control.fail("no motor file")
resetMotors()
resetAllMotors()
let buf = output.createBuffer(1)
const buf = output.createBuffer(1)
buf[0] = DAL.opProgramStart
writePWM(buf)
}
function writePWM(buf: Buffer): void {
/**
* Sends a command to the motors device
* @param buf the command buffer
*/
//%
export function writePWM(buf: Buffer): void {
init()
pwmMM.write(buf)
}
function readPWM(buf: Buffer): number {
/**
* Sends and receives a message from the motors device
* @param buf message buffer
*/
//%
export function readPWM(buf: Buffer): void {
init()
return pwmMM.read(buf);
pwmMM.read(buf);
}
function mkCmd(out: Output, cmd: number, addSize: number) {
/**
* Allocates a message buffer
* @param out ports
* @param cmd command id
* @param addSize required additional bytes
*/
//%
export function mkCmd(out: Output, cmd: number, addSize: number) {
const b = output.createBuffer(2 + addSize)
b.setNumber(NumberFormat.UInt8LE, 0, cmd)
b.setNumber(NumberFormat.UInt8LE, 1, out)
return b
}
function resetMotors() {
reset(Output.ALL)
function outputToName(out: Output): string {
let r = "";
for (let i = 0; i < DAL.NUM_OUTPUTS; ++i) {
if (out & (1 << i)) {
if (r.length > 0) r += "+";
r += "ABCD"[i];
}
}
return r;
}
/**
@ -92,9 +116,18 @@ namespace motors {
writePWM(b)
}
/**
* Resets all motors
*/
//% group="Motion"
export function resetAllMotors() {
reset(Output.ALL)
}
//% fixedInstances
export class Motor extends control.Component {
export class MotorBase extends control.Component {
protected _port: Output;
protected _portName: string;
protected _brake: boolean;
private _initialized: boolean;
private _init: () => void;
@ -104,6 +137,7 @@ namespace motors {
constructor(port: Output, init: () => void, setSpeed: (speed: number) => void, move: (steps: boolean, stepsOrTime: number, speed: number) => void) {
super();
this._port = port;
this._portName = outputToName(this._port);
this._brake = false;
this._initialized = false;
this._init = init;
@ -125,7 +159,7 @@ namespace motors {
* Sets the automatic brake on or off when the motor is off
* @param brake a value indicating if the motor should break when off
*/
//% blockId=outputMotorSetBrakeMode block="set `icons.motorLarge` %motor|brake %brake"
//% blockId=outputMotorSetBrakeMode block="set %motor|brake %brake"
//% brake.fieldEditor=toggleonoff
//% weight=60 blockGap=8
//% group="Motion"
@ -137,7 +171,7 @@ namespace motors {
/**
* Reverses the motor polarity
*/
//% blockId=motorSetReversed block="set `icons.motorLarge` %motor|reversed %reversed"
//% blockId=motorSetReversed block="set %motor|reversed %reversed"
//% reversed.fieldEditor=toggleonoff
//% weight=59
//% group="Motion"
@ -170,7 +204,7 @@ namespace motors {
* Sets the speed of the motor.
* @param speed the speed from ``100`` full forward to ``-100`` full backward, eg: 50
*/
//% blockId=motorSetSpeed block="set speed of `icons.motorLarge` %motor|to %speed|%"
//% blockId=motorSetSpeed block="set speed of %motor|to %speed|%"
//% on.fieldEditor=toggleonoff
//% weight=99 blockGap=8
//% speed.min=-100 speed.max=100
@ -190,7 +224,7 @@ namespace motors {
* @param unit the meaning of the value
* @param speed the speed from ``100`` full forward to ``-100`` full backward, eg: 50
*/
//% blockId=motorMove block="move `icons.motorLarge` %motor|for %value|%unit|at %speed|%"
//% blockId=motorMove block="move %motor|for %value|%unit|at %speed|%"
//% weight=98 blockGap=8
//% speed.min=-100 speed.max=100
//% group="Motion"
@ -227,9 +261,16 @@ namespace motors {
//%
isReady(): boolean {
this.init();
const r = readPWM(mkCmd(this._port, DAL.opOutputTest, 0))
// 0 = ready, 1 = busy
return r == 0;
const buf = mkCmd(this._port, DAL.opOutputTest, 2);
readPWM(buf)
const flags = buf.getNumber(NumberFormat.UInt8LE, 2);
// TODO: FIX with ~ support
for(let i = 0; i < DAL.NUM_OUTPUTS; ++i) {
const flag = 1 << i;
if ((this._port & flag) && (flags & flag))
return false;
}
return true;
}
/**
@ -244,7 +285,7 @@ namespace motors {
}
//% fixedInstances
export class SingleMotor extends Motor {
export class Motor extends MotorBase {
private _large: boolean;
constructor(port: Output, large: boolean) {
@ -288,7 +329,7 @@ namespace motors {
* Gets motor actual speed.
* @param motor the port which connects to the motor
*/
//% blockId=motorSpeed block="`icons.motorLarge` %motor|speed"
//% blockId=motorSpeed block="%motor|speed"
//% weight=72 blockGap=8
//% group="Sensors"
speed(): number {
@ -300,7 +341,7 @@ namespace motors {
* Gets motor ration angle.
* @param motor the port which connects to the motor
*/
//% blockId=motorTachoCount block="`icons.motorLarge` %motor|angle"
//% blockId=motorTachoCount block="%motor|angle"
//% weight=70
//% group="Sensors"
angle(): number {
@ -322,34 +363,42 @@ namespace motors {
}
}
}
/**
* Returns the status of the motor
*/
//%
toString(): string {
return `${this._large ? "" : "M"}${this._portName} ${this.speed()}% ${this.angle()}>`;
}
}
//% whenUsed fixedInstance block="large A"
export const largeA = new SingleMotor(Output.A, true);
export const largeA = new Motor(Output.A, true);
//% whenUsed fixedInstance block="large B"
export const largeB = new SingleMotor(Output.B, true);
export const largeB = new Motor(Output.B, true);
//% whenUsed fixedInstance block="large C"
export const largeC = new SingleMotor(Output.C, true);
export const largeC = new Motor(Output.C, true);
//% whenUsed fixedInstance block="large D"
export const largeD = new SingleMotor(Output.D, true);
export const largeD = new Motor(Output.D, true);
//% whenUsed fixedInstance block="medium A"
export const mediumA = new SingleMotor(Output.A, false);
export const mediumA = new Motor(Output.A, false);
//% whenUsed fixedInstance block="medium B"
export const mediumB = new SingleMotor(Output.B, false);
export const mediumB = new Motor(Output.B, false);
//% whenUsed fixedInstance block="medium C"
export const mediumC = new SingleMotor(Output.C, false);
export const mediumC = new Motor(Output.C, false);
//% whenUsed fixedInstance block="medium D"
export const mediumD = new SingleMotor(Output.D, false);
export const mediumD = new Motor(Output.D, false);
//% fixedInstances
export class SynchedMotorPair extends Motor {
export class SynchedMotorPair extends MotorBase {
constructor(ports: Output) {
super(ports, () => this.__init(), (speed) => this.__setSpeed(speed), (steps, stepsOrTime, speed) => this.__move(steps, stepsOrTime, speed));
@ -458,6 +507,20 @@ namespace motors {
const steering = (speedRight * 100 / speedLeft) >> 0;
this.steer(speedLeft, steering, value, unit);
}
/**
* Returns the name(s) of the motor
*/
//%
toString(): string {
let r = outputToName(this._port);
for (let i = 0; i < DAL.NUM_OUTPUTS; ++i) {
if (this._port & (1 << i)) {
r += ` ${getMotorData(1 << i).actualSpeed}%`
}
}
return r;
}
}
//% whenUsed fixedInstance block="large B+C"
@ -473,8 +536,8 @@ namespace motors {
export const largeCD = new SynchedMotorPair(Output.CD);
function reset(out: Output) {
let b = mkCmd(out, DAL.opOutputReset, 0)
writePWM(b)
writePWM(mkCmd(out, DAL.opOutputReset, 0))
writePWM(mkCmd(out, DAL.opOutputClearCount, 0))
}
function outOffset(out: Output) {
@ -485,7 +548,7 @@ namespace motors {
return 0
}
interface MotorData {
export interface MotorData {
actualSpeed: number; // -100..+100
tachoCount: number;
count: number;
@ -494,7 +557,7 @@ namespace motors {
// only a single output at a time
function getMotorData(out: Output): MotorData {
init()
let buf = motorMM.slice(outOffset(out), MotorDataOff.Size)
const buf = motorMM.slice(outOffset(out), MotorDataOff.Size)
return {
actualSpeed: buf.getNumber(NumberFormat.Int8LE, MotorDataOff.Speed),
tachoCount: buf.getNumber(NumberFormat.Int32LE, MotorDataOff.TachoCounts),
@ -502,6 +565,11 @@ namespace motors {
}
}
export function getAllMotorData(): MotorData[] {
init();
return [Output.A, Output.B, Output.C, Output.D].map(out => getMotorData(out));
}
interface SyncOptions {
useSteps?: boolean;
speed: number;

View File

@ -10,6 +10,7 @@
"linux.cpp",
"mmap.cpp",
"control.cpp",
"console.ts",
"serialnumber.cpp",
"buttons.ts",
"png.cpp",

View File

@ -1,4 +1,6 @@
namespace brick {
export const LINE_HEIGHT = 12;
//% shim=screen::_setPixel
function _setPixel(p0: uint32, p1: uint32, mode: Draw): void { }
@ -84,7 +86,7 @@ namespace brick {
* @param x the starting position's x coordinate, eg: 0
* @param y the starting position's x coordinate, eg: 0
*/
//% blockId=screen_setpixel block="`icons.brickDisplay` set pixel %on| at x: %x| y: %y"
//% blockId=screen_setpixel block="set pixel %on| at x: %x| y: %y"
//% weight=98 group="Screen"
//% x.min=0 x.max=178 y.min=0 y.max=128 on.fieldEditor=toggleonoff
export function setPixel(on: boolean, x: number, y: number) {
@ -100,7 +102,7 @@ namespace brick {
* @param x the starting position's x coordinate, eg: 0
* @param y the starting position's x coordinate, eg: 0
*/
//% blockId=screen_print block="`icons.brickDisplay` print %text| at x: %x| y: %y"
//% blockId=screen_print block="print %text| at x: %x| y: %y"
//% weight=99 group="Screen" inlineInputMode="inline" blockGap=8
//% x.min=0 x.max=178 y.min=0 y.max=128
export function print(text: string, x: number, y: number, mode = Draw.Normal) {
@ -137,7 +139,7 @@ namespace brick {
* Shows an image on screen
* @param image image to draw
*/
//% blockId=screen_show_image block="`icons.brickDisplay` show image %image=screen_image_picker"
//% blockId=screen_show_image block="show image %image=screen_image_picker"
//% weight=95 group="Screen" blockGap=8
export function showImage(image: Image, delay: number = 400) {
if (!image) return;
@ -163,7 +165,7 @@ namespace brick {
/**
* Clears the screen
*/
//% blockId=screen_clear_screen block="`icons.brickDisplay` clear screen"
//% blockId=screen_clear_screen block="clear screen"
//% weight=94 group="Screen" blockGap=8
export function clearScreen() {
screen.clear();
@ -208,5 +210,29 @@ namespace brick {
setLineCore(x, x1, y, mode);
}
/**
* Prints the port states on the screen
*/
//% blockId=brickPrintPorts block="print ports"
//% weight=1 group="Screen"
export function printPorts() {
clearScreen();
// motors
const datas = motors.getAllMotorData();
for(let i = 0; i < datas.length; ++i) {
const x = i * 52;
const data = datas[i];
print(`${data.actualSpeed}%`, x, brick.LINE_HEIGHT)
print(`${data.count}>`, x, 2 * brick.LINE_HEIGHT)
}
// sensors
const sis = sensors.internal.getActiveSensors();
for(let i =0; i < sis.length; ++i) {
const si = sis[i];
const x = (si.port() - 1) * 52;
print(`${si._query()}`, x, 9 * brick.LINE_HEIGHT)
}
}
}

View File

@ -2,7 +2,7 @@ screen.clear()
brick.print("PXT!", 10, 30, Draw.Quad)
brick.drawRect(40, 40, 20, 10, Draw.Fill)
brick.setStatusLight(LightsPattern.Orange)
brick.setLight(LightsPattern.Orange)
brick.heart.doubled().draw(100, 50, Draw.Double | Draw.Transparent)
@ -12,7 +12,7 @@ brick.buttonEnter.onEvent(ButtonEvent.Click, () => {
brick.buttonLeft.onEvent(ButtonEvent.Click, () => {
brick.drawRect(10, 70, 20, 10, Draw.Fill)
brick.setStatusLight(LightsPattern.Red)
brick.setLight(LightsPattern.Red)
brick.setFont(brick.microbitFont())
})

View File

@ -1,6 +1,6 @@
//% color="#68C3E2" weight=100
//% groups='["Light", "Buttons", "Screen"]'
//% groups='["Buttons", "Screen"]'
//% labelLineWidth=0
namespace brick {
}
@ -24,7 +24,7 @@ namespace music {
}
//% color="#48150C"
//% color="#5F3109"
namespace control {
}

View File

@ -1,10 +1,10 @@
{
"sensors.GyroSensor.angle|block": "`icons.gyroSensor` %sensor|angle",
"sensors.GyroSensor.rate|block": "`icons.gyroSensor` %sensor|rotation rate",
"sensors.gyro1|block": "1",
"sensors.gyro2|block": "2",
"sensors.gyro3|block": "3",
"sensors.gyro4|block": "4",
"sensors.GyroSensor.angle|block": "%sensor|angle",
"sensors.GyroSensor.rate|block": "%sensor|rotation rate",
"sensors.gyro1|block": "gyro 1",
"sensors.gyro2|block": "gyro 2",
"sensors.gyro3|block": "gyro 3",
"sensors.gyro4|block": "gyro 4",
"{id:category}Sensors": "Sensors",
"{id:group}Gyro Sensor": "Gyro Sensor"
}

View File

@ -24,11 +24,9 @@ namespace sensors {
* @param sensor the gyroscope to query the request
*/
//% help=input/gyro/angle
//% block="`icons.gyroSensor` %sensor|angle"
//% block="%sensor|angle"
//% blockId=gyroGetAngle
//% parts="gyroscope"
//% sensor.fieldEditor="imagedropdown"
//% sensor.fieldOptions.columns=4
//% blockNamespace=sensors
//% weight=65 blockGap=8
//% group="Gyro Sensor"
@ -42,11 +40,9 @@ namespace sensors {
* @param sensor the gyroscope to query the request
*/
//% help=input/gyro/rate
//% block="`icons.gyroSensor` %sensor|rotation rate"
//% block="%sensor|rotation rate"
//% blockId=gyroGetRate
//% parts="gyroscope"
//% sensor.fieldEditor="imagedropdown"
//% sensor.fieldOptions.columns=4
//% blockNamespace=sensors
//% weight=65 blockGap=8
//% group="Gyro Sensor"
@ -56,15 +52,15 @@ namespace sensors {
}
}
//% fixedInstance whenUsed block="1" jres=icons.port1
//% fixedInstance whenUsed block="gyro 1" jres=icons.port1
export const gyro1: GyroSensor = new GyroSensor(1)
//% fixedInstance whenUsed block="2" weight=95 jres=icons.port2
//% fixedInstance whenUsed block="gyro 2" weight=95 jres=icons.port2
export const gyro2: GyroSensor = new GyroSensor(2)
//% fixedInstance whenUsed block="3" jres=icons.port3
//% fixedInstance whenUsed block="gyro 3" jres=icons.port3
export const gyro3: GyroSensor = new GyroSensor(3)
//% fixedInstance whenUsed block="4" jres=icons.port4
//% fixedInstance whenUsed block="gyro 4" jres=icons.port4
export const gyro4: GyroSensor = new GyroSensor(4)
}

View File

@ -1,17 +1,17 @@
{
"InfraredSensorEvent.ObjectDetected|block": "object detected",
"InfraredSensorEvent.ObjectNear|block": "object near",
"sensors.InfraredSensor.onEvent|block": "on `icons.infraredSensor` %sensor|%event",
"sensors.InfraredSensor.pauseUntil|block": "pause until `icons.infraredSensor` %sensor| %event",
"sensors.InfraredSensor.proximity|block": "`icons.infraredSensor` %sensor|proximity",
"sensors.InfraredSensor.remoteCommand|block": "`icons.infraredSensor` %sensor|remote command",
"sensors.RemoteInfraredBeaconButton.isPressed|block": "`icons.infraredSensor` %button|is pressed",
"sensors.RemoteInfraredBeaconButton.onEvent|block": "on `icons.infraredSensor` %button|%event",
"sensors.RemoteInfraredBeaconButton.wasPressed|block": "`icons.infraredSensor` %button|was pressed",
"sensors.infraredSensor1|block": "1",
"sensors.infraredSensor2|block": "2",
"sensors.infraredSensor3|block": "3",
"sensors.infraredSensor4|block": "4",
"sensors.InfraredSensor.onEvent|block": "on %sensor|%event",
"sensors.InfraredSensor.pauseUntil|block": "pause until %sensor| %event",
"sensors.InfraredSensor.proximity|block": "%sensor|proximity",
"sensors.InfraredSensor.remoteCommand|block": "%sensor|remote command",
"sensors.RemoteInfraredBeaconButton.isPressed|block": "%button|is pressed",
"sensors.RemoteInfraredBeaconButton.onEvent|block": "on %button|%event",
"sensors.RemoteInfraredBeaconButton.wasPressed|block": "%button|was pressed",
"sensors.infraredSensor1|block": "infrared 1",
"sensors.infraredSensor2|block": "infrared 2",
"sensors.infraredSensor3|block": "infrared 3",
"sensors.infraredSensor4|block": "infrared 4",
"sensors.remoteButtonBottomLeft|block": "bottom-left",
"sensors.remoteButtonBottomRight|block": "bottom-right",
"sensors.remoteButtonCenter|block": "center",

View File

@ -93,7 +93,7 @@ namespace sensors {
* @param button the remote button to query the request
*/
//% help=input/remote-infrared-beacon/is-pressed
//% block="`icons.infraredSensor` %button|is pressed"
//% block="%button|is pressed"
//% blockId=remoteButtonIsPressed
//% parts="remote"
//% blockNamespace=sensors
@ -108,7 +108,7 @@ namespace sensors {
* @param button the remote button to query the request
*/
//% help=input/remote-infrared-beacon/was-pressed
//% block="`icons.infraredSensor` %button|was pressed"
//% block="%button|was pressed"
//% blockId=remotebuttonWasPressed
//% parts="remote"
//% blockNamespace=sensors
@ -125,7 +125,7 @@ namespace sensors {
* @param body code to run when the event is raised
*/
//% help=input/remote-infrared-beacon/on-event
//% blockId=remotebuttonEvent block="on `icons.infraredSensor` %button|%event"
//% blockId=remotebuttonEvent block="on %button|%event"
//% parts="remote"
//% blockNamespace=sensors
//% weight=99 blockGap=8
@ -189,11 +189,9 @@ namespace sensors {
* @param handler the code to run when detected
*/
//% help=input/infrared/on
//% block="on `icons.infraredSensor` %sensor|%event"
//% block="on %sensor|%event"
//% blockId=infraredOn
//% parts="infraredsensor"
//% sensor.fieldEditor="imagedropdown"
//% sensor.fieldOptions.columns=4
//% blockNamespace=sensors
//% weight=100 blockGap=8
//% group="Infrared Sensor"
@ -205,11 +203,9 @@ namespace sensors {
* Waits for the event to occur
*/
//% help=input/ultrasonic/wait
//% block="pause until `icons.infraredSensor` %sensor| %event"
//% block="pause until %sensor| %event"
//% blockId=infraredwait
//% parts="infraredsensor"
//% sensor.fieldEditor="imagedropdown"
//% sensor.fieldOptions.columns=4
//% blockNamespace=sensors
//% weight=99 blockGap=8
//% group="Infrared Sensor"
@ -222,11 +218,9 @@ namespace sensors {
* @param sensor the infrared sensor
*/
//% help=input/infrared/proximity
//% block="`icons.infraredSensor` %sensor|proximity"
//% block="%sensor|proximity"
//% blockId=infraredGetProximity
//% parts="infrared"
//% sensor.fieldEditor="imagedropdown"
//% sensor.fieldOptions.columns=4
//% blockNamespace=sensors
//% weight=65 blockGap=8
//% group="Infrared Sensor"
@ -240,11 +234,9 @@ namespace sensors {
* @param sensor the infrared sensor
*/
//% help=input/infrared/remote-command
//% block="`icons.infraredSensor` %sensor|remote command"
//% block="%sensor|remote command"
//% blockId=infraredGetRemoteCommand
//% parts="infrared"
//% sensor.fieldEditor="imagedropdown"
//% sensor.fieldOptions.columns=4
//% blockNamespace=sensors
//% weight=65 blockGap=8
//% group="Infrared Sensor"
@ -260,16 +252,16 @@ namespace sensors {
}
}
//% fixedInstance whenUsed block="1" jres=icons.port1
//% fixedInstance whenUsed block="infrared 1" jres=icons.port1
export const infraredSensor1: InfraredSensor = new InfraredSensor(1)
//% fixedInstance whenUsed block="2" jres=icons.port2
//% fixedInstance whenUsed block="infrared 2" jres=icons.port2
export const infraredSensor2: InfraredSensor = new InfraredSensor(2)
//% fixedInstance whenUsed block="3" jres=icons.port3
//% fixedInstance whenUsed block="infrared 3" jres=icons.port3
export const infraredSensor3: InfraredSensor = new InfraredSensor(3)
//% fixedInstance whenUsed block="4" jres=icons.port4
//% fixedInstance whenUsed block="infrared 4" jres=icons.port4
export const infraredSensor4: InfraredSensor = new InfraredSensor(4)

35
libs/tests/README.md Normal file
View File

@ -0,0 +1,35 @@
# tests
A unit test framework
## Defining tests
Tests are registered as event handlers. They will automatically run once ``on start`` is finished.
```blocks
tests.test("lgB set speed 10", () => {
motors.largeB.setSpeed(10);
loops.pause(100)
tests.assertClose("speedB", 10, motors.largeB.speed(), 2)
});
```
## Assertions
The library has various asserts that will register fault. Note that since exceptions are not available, assertion failure **do not** stop the program execution.
* **assert** checks a boolean condition
```blocks
tests.assert("speed positive", motors.largeB.speed() > 0)
```
* **assert close** checks that a numberical value is within a particular range
```blocks
tests.assertClose("speed", motors.largeB.speed(), 10, 2)
```
```package
tests
```

View File

@ -0,0 +1,9 @@
{
"tests": "Unit tests framework",
"tests.assert": "Checks a boolean condition",
"tests.assertClose": "Checks that 2 values are close to each other",
"tests.assertClose|param|actual": "what the value was",
"tests.assertClose|param|expected": "what the value should be",
"tests.assertClose|param|tolerance": "the acceptable error margin",
"tests.test": "Registers a test to run"
}

View File

@ -0,0 +1,6 @@
{
"tests.assert|block": "assert %message|%condition",
"tests.test|block": "test %name",
"tests|block": "tests",
"{id:category}Tests": "Tests"
}

14
libs/tests/pxt.json Normal file
View File

@ -0,0 +1,14 @@
{
"name": "tests",
"description": "A unit test library",
"files": [
"README.md",
"tests.ts"
],
"testFiles": [
],
"public": true,
"dependencies": {
"core": "file:../core"
}
}

94
libs/tests/tests.ts Normal file
View File

@ -0,0 +1,94 @@
/**
* Unit tests framework
*/
//% weight=100 color=#0fbc11 icon=""
namespace tests {
class Test {
name: string;
handler: () => void;
errors: string[];
constructor(name: string, handler: () => void) {
this.name = name;
this.handler = handler;
this.errors = [];
}
reset() {
motors.stopAllMotors();
motors.resetAllMotors();
}
run() {
// clear state
this.reset();
console.log(`> ${this.name}`)
this.handler()
if (this.errors.length)
console.log('')
// ensure clean state after test
this.reset();
}
}
let _tests: Test[] = undefined;
let _currentTest: Test = undefined;
function run() {
if (!_tests) return;
const start = control.millis();
console.sendToScreen();
console.log(`${_tests.length} tests found`)
console.log(` `)
for (let i = 0; i < _tests.length; ++i) {
const t = _currentTest = _tests[i];
t.run();
_currentTest = undefined;
}
console.log(` `)
console.log(`${_tests.length} tests, ${_tests.map(t => t.errors.length).reduce((p, c) => p + c, 0)} errs in ${Math.ceil((control.millis() - start) / 1000)}s`)
}
/**
* Registers a test to run
*/
//% blockId=testtest block="test %name"
export function test(name: string, handler: () => void): void {
if (!name || !handler) return;
if (!_tests) {
_tests = [];
control.runInBackground(function () {
// should run after on start
loops.pause(100)
run()
})
}
_tests.push(new Test(name, handler));
}
/**
* Checks a boolean condition
*/
//% blockId=testAssert block="assert %message|%condition"
export function assert(message: string, condition: boolean) {
if (!condition) {
console.log(`!!! ${message || ''}`)
if (_currentTest)
_currentTest.errors.push(message);
}
}
/**
* Checks that 2 values are close to each other
* @param expected what the value should be
* @param actual what the value was
* @param tolerance the acceptable error margin
*/
export function assertClose(name: string, expected: number, actual: number, tolerance: number) {
assert(`${name} ${expected} != ${actual} +-${tolerance}`, Math.abs(expected - actual) <= tolerance);
}
}

View File

@ -2,14 +2,14 @@
"TouchSensorEvent.Bumped|block": "bumped",
"TouchSensorEvent.Pressed|block": "pressed",
"TouchSensorEvent.Released|block": "released",
"sensors.TouchSensor.isPressed|block": "`icons.touchSensor` %sensor|is pressed",
"sensors.TouchSensor.onEvent|block": "on `icons.touchSensor` %sensor|%event",
"sensors.TouchSensor.pauseUntil|block": "pause until `icons.touchSensor` %sensor|%event",
"sensors.TouchSensor.wasPressed|block": "`icons.touchSensor` %sensor|was pressed",
"sensors.touchSensor1|block": "1",
"sensors.touchSensor2|block": "2",
"sensors.touchSensor3|block": "3",
"sensors.touchSensor4|block": "4",
"sensors.TouchSensor.isPressed|block": "%sensor|is pressed",
"sensors.TouchSensor.onEvent|block": "on %sensor|%event",
"sensors.TouchSensor.pauseUntil|block": "pause until %sensor|%event",
"sensors.TouchSensor.wasPressed|block": "%sensor|was pressed",
"sensors.touchSensor1|block": "touch 1",
"sensors.touchSensor2|block": "touch 2",
"sensors.touchSensor3|block": "touch 3",
"sensors.touchSensor4|block": "touch 4",
"{id:category}Sensors": "Sensors",
"{id:group}Touch Sensor": "Touch Sensor"
}

View File

@ -42,10 +42,8 @@ namespace sensors {
* @param body code to run when the event is raised
*/
//% help=input/touch-sensor/on-event
//% blockId=touchEvent block="on `icons.touchSensor` %sensor|%event"
//% blockId=touchEvent block="on %sensor|%event"
//% parts="touch"
//% sensor.fieldEditor="imagedropdown"
//% sensor.fieldOptions.columns=4
//% blockNamespace=sensors
//% weight=99 blockGap=8
//% group="Touch Sensor"
@ -59,10 +57,8 @@ namespace sensors {
* @param event the kind of button gesture that needs to be detected
*/
//% help=input/touch-sensor/pause-until
//% blockId=touchWaitUntil block="pause until `icons.touchSensor` %sensor|%event"
//% blockId=touchWaitUntil block="pause until %sensor|%event"
//% parts="touch"
//% sensor.fieldEditor="imagedropdown"
//% sensor.fieldOptions.columns=4
//% blockNamespace=sensors
//% weight=98 blockGap=8
//% group="Touch Sensor"
@ -75,11 +71,9 @@ namespace sensors {
* @param sensor the port to query the request
*/
//% help=input/touch-sensor/is-pressed
//% block="`icons.touchSensor` %sensor|is pressed"
//% block="%sensor|is pressed"
//% blockId=touchIsPressed
//% parts="touch"
//% sensor.fieldEditor="imagedropdown"
//% sensor.fieldOptions.columns=4
//% blockNamespace=sensors
//% weight=81 blockGap=8
//% group="Touch Sensor"
@ -92,11 +86,9 @@ namespace sensors {
* @param sensor the port to query the request
*/
//% help=input/touch-sensor/was-pressed
//% block="`icons.touchSensor` %sensor|was pressed"
//% block="%sensor|was pressed"
//% blockId=touchWasPressed
//% parts="touch"
//% sensor.fieldEditor="imagedropdown"
//% sensor.fieldOptions.columns=4
//% blockNamespace=sensors
//% weight=81 blockGap=8
//% group="Touch Sensor"
@ -105,12 +97,12 @@ namespace sensors {
}
}
//% whenUsed block="1" weight=95 fixedInstance jres=icons.port1
//% whenUsed block="touch 1" weight=95 fixedInstance jres=icons.port1
export const touchSensor1: TouchSensor = new TouchSensor(1)
//% whenUsed block="2" weight=95 fixedInstance jres=icons.port2
//% whenUsed block="touch 2" weight=95 fixedInstance jres=icons.port2
export const touchSensor2: TouchSensor = new TouchSensor(2)
//% whenUsed block="3" weight=95 fixedInstance jres=icons.port3
//% whenUsed block="touch 3" weight=95 fixedInstance jres=icons.port3
export const touchSensor3: TouchSensor = new TouchSensor(3)
//% whenUsed block="4" weight=95 fixedInstance jres=icons.port4
//% whenUsed block="touch 4" weight=95 fixedInstance jres=icons.port4
export const touchSensor4: TouchSensor = new TouchSensor(4)
}

View File

@ -2,13 +2,13 @@
"UltrasonicSensorEvent.ObjectDetected|block": "object detected",
"UltrasonicSensorEvent.ObjectFar|block": "object far",
"UltrasonicSensorEvent.ObjectNear|block": "object near",
"sensors.UltraSonicSensor.distance|block": "`icons.ultrasonicSensor` %sensor|distance",
"sensors.UltraSonicSensor.onEvent|block": "on `icons.ultrasonicSensor` %sensor|%event",
"sensors.UltraSonicSensor.pauseUntil|block": "pause until `icons.ultrasonicSensor` %sensor| %event",
"sensors.ultrasonic1|block": "1",
"sensors.ultrasonic2|block": "2",
"sensors.ultrasonic3|block": "3",
"sensors.ultrasonic4|block": "4",
"sensors.UltraSonicSensor.distance|block": "%sensor|distance",
"sensors.UltraSonicSensor.onEvent|block": "on %sensor|%event",
"sensors.UltraSonicSensor.pauseUntil|block": "pause until %sensor| %event",
"sensors.ultrasonic1|block": "ultrasonic 1",
"sensors.ultrasonic2|block": "ultrasonic 2",
"sensors.ultrasonic3|block": "ultrasonic 3",
"sensors.ultrasonic4|block": "ultrasonic 4",
"{id:category}Sensors": "Sensors",
"{id:group}Ultrasonic Sensor": "Ultrasonic Sensor"
}

View File

@ -25,7 +25,7 @@ namespace sensors {
}
_query(): number {
return this.getNumber(NumberFormat.UInt16LE, 0) & 0x0fff;
return ((this.getNumber(NumberFormat.UInt16LE, 0) & 0x0fff) / 10) >> 0; // range is 0..2550, in 0.1 cm increments.
}
_update(prev: number, curr: number) {
@ -43,10 +43,8 @@ namespace sensors {
*/
//% help=input/ultrasonic/on
//% blockId=ultrasonicOn
//% block="on `icons.ultrasonicSensor` %sensor|%event"
//% block="on %sensor|%event"
//% parts="ultrasonicsensor"
//% sensor.fieldEditor="imagedropdown"
//% sensor.fieldOptions.columns=4
//% blockNamespace=sensors
//% weight=100 blockGap=8
//% group="Ultrasonic Sensor"
@ -58,11 +56,9 @@ namespace sensors {
* Waits for the event to occur
*/
//% help=input/ultrasonic/wait
//% block="pause until `icons.ultrasonicSensor` %sensor| %event"
//% block="pause until %sensor| %event"
//% blockId=ultrasonicWait
//% parts="ultrasonicsensor"
//% sensor.fieldEditor="imagedropdown"
//% sensor.fieldOptions.columns=4
//% blockNamespace=sensors
//% weight=99 blockGap=8
//% group="Ultrasonic Sensor"
@ -75,30 +71,28 @@ namespace sensors {
* @param sensor the ultrasonic sensor port
*/
//% help=input/ultrasonic/distance
//% block="`icons.ultrasonicSensor` %sensor|distance"
//% block="%sensor|distance"
//% blockId=sonarGetDistance
//% parts="ultrasonicsensor"
//% sensor.fieldEditor="imagedropdown"
//% sensor.fieldOptions.columns=4
//% blockNamespace=sensors
//% weight=65 blockGap=8
//% group="Ultrasonic Sensor"
distance(): number {
// it supposedly also has an inch mode, but we stick to cm
this._setMode(0)
return this.getNumber(NumberFormat.UInt16LE, 0) & 0x0fff; // range is 0..255
return this._query();
}
}
//% fixedInstance whenUsed block="1" jres=icons.port1
//% fixedInstance whenUsed block="ultrasonic 1" jres=icons.port1
export const ultrasonic1: UltraSonicSensor = new UltraSonicSensor(1)
//% fixedInstance whenUsed block="2" jres=icons.port2
//% fixedInstance whenUsed block="ultrasonic 2" jres=icons.port2
export const ultrasonic2: UltraSonicSensor = new UltraSonicSensor(2)
//% fixedInstance whenUsed block="3" jres=icons.port3
//% fixedInstance whenUsed block="ultrasonic 3" jres=icons.port3
export const ultrasonic3: UltraSonicSensor = new UltraSonicSensor(3)
//% fixedInstance whenUsed block="4" jres=icons.port4
//% fixedInstance whenUsed block="ultrasonic 4" jres=icons.port4
export const ultrasonic4: UltraSonicSensor = new UltraSonicSensor(4)
}

View File

@ -1,6 +1,6 @@
{
"name": "pxt-ev3",
"version": "0.0.42",
"version": "0.0.45",
"description": "LEGO Mindstorms EV3 for Microsoft MakeCode",
"private": true,
"keywords": [

View File

@ -15,7 +15,8 @@
"libs/ultrasonic-sensor",
"libs/infrared-sensor",
"libs/gyro-sensor",
"libs/ev3"
"libs/ev3",
"libs/tests"
],
"simulator": {
"autoRun": true,
@ -52,8 +53,10 @@
"serial": {
"vendorId": "0x0694",
"productId": "0x0005",
"rawHID": true
},
"rawHID": true,
"useEditor": true,
"log": true
},
"runtime": {
"mathBlocks": true,
"loopsBlocks": true,
@ -129,10 +132,10 @@
"logic": "#1E5AA8",
"math": "#9DC3F7",
"variables": "#B40000",
"text": "#F0890A",
"text": "#FCAC00",
"advanced": "#969696",
"functions": "#064597",
"arrays": "#890058"
"functions": "#19325A",
"arrays": "#901F76"
},
"monacoColors": {
"editor.background": "#ecf6ff"

View File

@ -38,7 +38,7 @@ namespace pxsim {
data[AnalogOff.InConn + port] = node.isUart() ? DAL.CONN_INPUT_UART : DAL.CONN_INPUT_DUMB;
if (node.isAnalog() && node.hasData()) {
//data[AnalogOff.InPin6 + 2 * port] = node.getValue();
util.map16Bit(data, AnalogOff.InPin6 + 2 * port, node.getValue())
util.map16Bit(data, AnalogOff.InPin6 + 2 * port, Math.floor(node.getValue()));
}
}
}

View File

@ -34,9 +34,6 @@ namespace pxsim {
this.color = color;
this.changed = true;
this.valueChanged = true;
runtime.queueDisplayUpdate();
}
getValue() {

View File

@ -22,20 +22,14 @@ namespace pxsim {
setAngle(angle: number) {
if (this.angle != angle) {
this.angle = angle;
this.changed = true;
this.valueChanged = true;
runtime.queueDisplayUpdate();
this.setChangedState();
}
}
setRate(rate: number) {
if (this.rate != rate) {
this.rate = rate;
this.changed = true;
this.valueChanged = true;
runtime.queueDisplayUpdate();
this.setChangedState();
}
}

View File

@ -12,8 +12,11 @@ namespace pxsim {
namespace pxsim.output {
export function setLights(pattern: number) {
const lightState = ev3board().getBrickNode().lightState;
lightState.lightPattern = pattern;
runtime.queueDisplayUpdate();
const brickState = ev3board().getBrickNode();
const lightState = brickState.lightState;
if (lightState.lightPattern != pattern) {
lightState.lightPattern = pattern;
brickState.setChangedState();
}
}
}

View File

@ -1,9 +1,9 @@
namespace pxsim {
export class MotorNode extends BaseNode {
export abstract class MotorNode extends BaseNode {
isOutput = true;
public angle: number = 0;
protected angle: number = 0;
private speed: number;
private large: boolean;
@ -18,7 +18,8 @@ namespace pxsim {
if (this.speed != speed) {
this.speed = speed;
this.changed = true;
runtime.queueDisplayUpdate();
this.setChangedState();
this.playMotorAnimation();
}
}
@ -50,8 +51,14 @@ namespace pxsim {
start() {
// TODO: implement
runtime.queueDisplayUpdate();
this.setChangedState();
}
public getAngle() {
return this.angle;
}
protected abstract playMotorAnimation(): void;
}
export class MediumMotorNode extends MotorNode {
@ -60,6 +67,32 @@ namespace pxsim {
constructor(port: number) {
super(port);
}
protected lastMotorAnimationId: number;
protected playMotorAnimation() {
// Max medium motor RPM is 250 according to http://www.cs.scranton.edu/~bi/2015s-html/cs358/EV3-Motor-Guide.docx
const rotationsPerMinute = 250; // 250 rpm at speed 100
const rotationsPerSecond = rotationsPerMinute / 60;
const fps = GAME_LOOP_FPS;
const rotationsPerFrame = rotationsPerSecond / fps;
let now;
let then = Date.now();
let interval = 1000 / fps;
let delta;
let that = this;
function draw() {
that.lastMotorAnimationId = requestAnimationFrame(draw);
now = Date.now();
delta = now - then;
if (delta > interval) {
then = now - (delta % interval);
const rotations = that.getSpeed() / 100 * rotationsPerFrame;
const angle = rotations * 360;
that.angle += angle;
}
}
draw();
}
}
export class LargeMotorNode extends MotorNode {
@ -69,5 +102,30 @@ namespace pxsim {
super(port);
}
protected lastMotorAnimationId: number;
protected playMotorAnimation() {
// Max medium motor RPM is 170 according to http://www.cs.scranton.edu/~bi/2015s-html/cs358/EV3-Motor-Guide.docx
const rotationsPerMinute = 170; // 170 rpm at speed 100
const rotationsPerSecond = rotationsPerMinute / 60;
const fps = GAME_LOOP_FPS;
const rotationsPerFrame = rotationsPerSecond / fps;
let now;
let then = Date.now();
let interval = 1000 / fps;
let delta;
let that = this;
function draw() {
that.lastMotorAnimationId = requestAnimationFrame(draw);
now = Date.now();
delta = now - then;
if (delta > interval) {
then = now - (delta % interval);
const rotations = that.getSpeed() / 100 * rotationsPerFrame;
const angle = rotations * 360;
that.angle += angle;
}
}
draw();
}
}
}

View File

@ -32,5 +32,9 @@ namespace pxsim {
this.changed = false;
return res;
}
setChangedState() {
this.changed = true;
}
}
}

View File

@ -7,6 +7,7 @@ namespace pxsim {
export class EV3ScreenState {
changed: boolean = true;
points: Uint8Array;
constructor() {
this.points = new Uint8Array(visuals.SCREEN_WIDTH * visuals.SCREEN_HEIGHT)
@ -23,13 +24,13 @@ namespace pxsim {
setPixel(x: number, y: number, v: number) {
this.applyMode(OFF(x, y), v)
runtime.queueDisplayUpdate();
this.changed = true;
}
clear() {
for (let i = 0; i < this.points.length; ++i)
this.points[i] = 0;
runtime.queueDisplayUpdate();
this.changed = true;
}
blitLineCore(x: number, y: number, w: number, buf: RefBuffer, mode: Draw, offset = 0) {
@ -58,7 +59,7 @@ namespace pxsim {
}
}
runtime.queueDisplayUpdate();
this.changed = true;
}
clearLine(x: number, y: number, w: number) {
@ -72,6 +73,12 @@ namespace pxsim {
off++
}
}
didChange() {
const res = this.changed;
this.changed = false;
return res;
}
}
}

View File

@ -43,6 +43,11 @@ namespace pxsim {
this.valueChanged = false;
return res;
}
setChangedState() {
this.changed = true;
this.valueChanged = false;
}
}
export class AnalogSensorNode extends SensorNode {

View File

@ -91,7 +91,7 @@ namespace pxsim {
if (node) {
// Actual
const index = 0; //UartOff.Actual + port * 2;
data[UartOff.Raw + DAL.MAX_DEVICE_DATALENGTH * 300 * port + DAL.MAX_DEVICE_DATALENGTH * index] = node.getValue();
util.map16Bit(data, UartOff.Raw + DAL.MAX_DEVICE_DATALENGTH * 300 * port + DAL.MAX_DEVICE_DATALENGTH * index, Math.floor(node.getValue()))
// Status
data[UartOff.Status + port] = node.valueChange() ? UartStatus.UART_PORT_CHANGED : UartStatus.UART_DATA_READY;
}

View File

@ -4,7 +4,7 @@ namespace pxsim {
export class UltrasonicSensorNode extends UartSensorNode {
id = NodeType.UltrasonicSensor;
private distance: number = 50;
private distance: number = 127; // in cm
constructor(port: number) {
super(port);
@ -17,15 +17,12 @@ namespace pxsim {
setDistance(distance: number) {
if (this.distance != distance) {
this.distance = distance;
this.changed = true;
this.valueChanged = true;
runtime.queueDisplayUpdate();
this.setChangedState();
}
}
getValue() {
return this.distance;
return this.distance * 10; // convert to 0.1 cm
}
}
}

View File

@ -1,7 +1,7 @@
namespace pxsim.util {
export function map16Bit(buffer: Uint8Array, index: number, value: number) {
buffer[index] = (value >> 8) & 0xff;
buffer[index+1] = value & 0xff;
buffer[index] = value & 0xFF;
buffer[index + 1] = (value >> 8) & 0xFF;
}
}

View File

@ -1,5 +1,9 @@
/// <reference path="./layoutView.ts" />
namespace pxsim {
export const GAME_LOOP_FPS = 32;
}
namespace pxsim.visuals {
const EV3_STYLE = `
@ -136,6 +140,12 @@ namespace pxsim.visuals {
this.board.updateSubscribers.push(() => this.updateState());
this.updateState();
}
Runtime.messagePosted = (msg) => {
switch (msg.type || "") {
case "status": if ((msg as pxsim.SimulatorStateMessage).state == "killed") this.kill(); break;
}
}
}
public getView(): SVGAndSize<SVGSVGElement> {
@ -167,54 +177,6 @@ namespace pxsim.visuals {
this.layoutView.updateTheme(theme);
}
public updateState() {
this.updateVisibleNodes();
this.updateScreen();
}
private updateVisibleNodes() {
const inputNodes = ev3board().getInputNodes();
inputNodes.forEach((node, index) => {
const view = this.getDisplayViewForNode(node.id, index);
if (view) {
this.layoutView.setInput(index, view);
view.updateState();
}
});
this.getDisplayViewForNode(ev3board().getBrickNode().id, -1).updateState();
const outputNodes = ev3board().getMotors();
outputNodes.forEach((node, index) => {
const view = this.getDisplayViewForNode(node.id, index);
if (view) {
this.layoutView.setOutput(index, view);
view.updateState();
}
});
const selected = this.layoutView.getSelected();
if (selected && (selected.getId() !== this.selectedNode || selected.getPort() !== this.selectedPort)) {
this.selectedNode = selected.getId();
this.selectedPort = selected.getPort();
this.controlGroup.clear();
const control = this.getControlForNode(this.selectedNode, selected.getPort());
if (control) {
this.controlView = control;
this.controlGroup.addView(control);
}
this.closeIconView.setVisible(true);
} else if (!selected) {
this.controlGroup.clear();
this.controlView = undefined;
this.selectedNode = undefined;
this.selectedPort = undefined;
this.closeIconView.setVisible(false);
}
this.resize();
}
public resize() {
const bounds = this.element.getBoundingClientRect();
this.width = bounds.width;
@ -242,7 +204,7 @@ namespace pxsim.visuals {
this.controlView.translate(controlCoords.x, controlCoords.y);
}
this.updateScreen();
//this.updateScreen();
}
private getControlForNode(id: NodeType, port: number) {
@ -355,7 +317,7 @@ namespace pxsim.visuals {
this.closeIconView.setVisible(false);
this.resize();
this.updateState();
//this.updateState();
// Add Screen canvas to board
this.buildScreenCanvas();
@ -394,8 +356,84 @@ namespace pxsim.visuals {
this.screenCanvasTemp.style.display = 'none';
}
private updateScreen() {
private kill() {
if (this.lastAnimationId) cancelAnimationFrame(this.lastAnimationId);
}
private lastAnimationId: number;
public updateState() {
if (this.lastAnimationId) cancelAnimationFrame(this.lastAnimationId);
const fps = GAME_LOOP_FPS;
let now;
let then = Date.now();
let interval = 1000 / fps;
let delta;
let that = this;
function loop() {
that.lastAnimationId = requestAnimationFrame(loop);
now = Date.now();
delta = now - then;
if (delta > interval) {
then = now - (delta % interval);
that.updateStateStep();
}
}
loop();
}
private updateStateStep() {
const selected = this.layoutView.getSelected();
const inputNodes = ev3board().getInputNodes();
inputNodes.forEach((node, index) => {
if (!node.didChange()) return;
const view = this.getDisplayViewForNode(node.id, index);
if (view) {
this.layoutView.setInput(index, view);
view.updateState();
}
});
const brickNode = ev3board().getBrickNode();
if (brickNode.didChange()) {
this.getDisplayViewForNode(ev3board().getBrickNode().id, -1).updateState();
}
const outputNodes = ev3board().getMotors();
outputNodes.forEach((node, index) => {
if (!node.didChange()) return;
const view = this.getDisplayViewForNode(node.id, index);
if (view) {
this.layoutView.setOutput(index, view);
view.updateState();
}
});
if (selected && (selected.getId() !== this.selectedNode || selected.getPort() !== this.selectedPort)) {
this.selectedNode = selected.getId();
this.selectedPort = selected.getPort();
this.controlGroup.clear();
const control = this.getControlForNode(this.selectedNode, selected.getPort());
if (control) {
this.controlView = control;
this.controlGroup.addView(control);
}
this.closeIconView.setVisible(true);
this.resize();
} else if (!selected) {
this.controlGroup.clear();
this.controlView = undefined;
this.selectedNode = undefined;
this.selectedPort = undefined;
this.closeIconView.setVisible(false);
this.resize();
}
this.updateScreenStep();
}
private updateScreenStep() {
let state = ev3board().screenState;
if (!state.didChange()) return;
const bBox = this.layoutView.getBrick().getScreenBBox();
if (!bBox || bBox.width == 0) return;

View File

@ -78,20 +78,19 @@ namespace pxsim.visuals {
return this.getInnerHeight() * 0.6;
}
onBoardStateChanged() {
updateState() {
if (!this.isVisible) {
return;
}
const node = this.state;
const percentage = node.getValue();
const y = this.getContentHeight() * percentage / 100;
const percentage = node.getValue() / 10; /* convert back to cm */
const y = this.getContentHeight() * percentage / this.getMax();
this.slider.setAttribute("transform", `translate(0, ${y - DistanceSliderControl.SLIDER_HANDLE_HEIGHT / 2})`);
}
onComponentVisible() {
super.onComponentVisible();
this.isVisible = true;
this.onBoardStateChanged();
}
onComponentHidden() {
@ -104,7 +103,15 @@ namespace pxsim.visuals {
let t = Math.max(0, Math.min(1, (this.getTopPadding() + height + this.top / this.scaleFactor - cur.y / this.scaleFactor) / height))
const state = this.state;
state.setDistance((1 - t) * (100));
state.setDistance((1 - t) * (this.getMax()));
}
private getMin() {
return 0;
}
private getMax() {
return 250; //cm
}
private getGradientDefinition(): LinearGradientDefinition {

View File

@ -60,7 +60,7 @@ namespace pxsim.visuals {
return this.getInnerHeight() / 4;
}
onBoardStateChanged() {
updateState() {
if (!this.isVisible) {
return;
}
@ -74,7 +74,6 @@ namespace pxsim.visuals {
onComponentVisible() {
super.onComponentVisible();
this.isVisible = true;
this.onBoardStateChanged();
}
onComponentHidden() {

View File

@ -35,10 +35,6 @@ namespace pxsim.visuals {
return false;
}
public shouldUpdateState() {
return true;
}
public updateState() {
this.updateLight();
}

View File

@ -18,36 +18,10 @@ namespace pxsim.visuals {
if (this.lastMotorAnimationId) cancelAnimationFrame(this.lastMotorAnimationId);
if (!speed) return;
this.playMotorAnimation(motorState);
this.setMotorAngle(motorState.getAngle());
}
private playMotorAnimation(state: MotorNode) {
// Max medium motor RPM is 170 according to http://www.cs.scranton.edu/~bi/2015s-html/cs358/EV3-Motor-Guide.docx
const rotationsPerMinute = 170; // 170 rpm at speed 100
const rotationsPerSecond = rotationsPerMinute / 60;
const fps = MOTOR_ROTATION_FPS;
const rotationsPerFrame = rotationsPerSecond / fps;
let now;
let then = Date.now();
let interval = 1000 / fps;
let delta;
let that = this;
function draw() {
that.lastMotorAnimationId = requestAnimationFrame(draw);
now = Date.now();
delta = now - then;
if (delta > interval) {
then = now - (delta % interval);
that.playMotorAnimationStep(state.angle);
const rotations = state.getSpeed() / 100 * rotationsPerFrame;
const angle = rotations * 360;
state.angle += angle;
}
}
draw();
}
private playMotorAnimationStep(angle: number) {
private setMotorAngle(angle: number) {
const holeEl = this.content.getElementById(this.normalizeId(LargeMotorView.ROTATING_ECLIPSE_ID))
const width = 34;
const height = 34;

View File

@ -28,36 +28,10 @@ namespace pxsim.visuals {
if (this.lastMotorAnimationId) cancelAnimationFrame(this.lastMotorAnimationId);
if (!speed) return;
this.playMotorAnimation(motorState);
this.setMotorAngle(motorState.getAngle());
}
private playMotorAnimation(state: MotorNode) {
// Max medium motor RPM is 250 according to http://www.cs.scranton.edu/~bi/2015s-html/cs358/EV3-Motor-Guide.docx
const rotationsPerMinute = 250; // 250 rpm at speed 100
const rotationsPerSecond = rotationsPerMinute / 60;
const fps = MOTOR_ROTATION_FPS;
const rotationsPerFrame = rotationsPerSecond / fps;
let now;
let then = Date.now();
let interval = 1000 / fps;
let delta;
let that = this;
function draw() {
that.lastMotorAnimationId = requestAnimationFrame(draw);
now = Date.now();
delta = now - then;
if (delta > interval) {
then = now - (delta % interval);
that.playMotorAnimationStep(state.angle);
const rotations = state.getSpeed() / 100 * rotationsPerFrame;
const angle = rotations * 360;
state.angle += angle;
}
}
draw();
}
private playMotorAnimationStep(angle: number) {
private setMotorAngle(angle: number) {
const holeEl = this.content.getElementById(this.normalizeId(MediumMotorView.ROTATING_ECLIPSE_ID))
const width = 47.9;
const height = 47.2;

View File

@ -61,10 +61,6 @@ namespace pxsim.visuals {
}
}
public shouldUpdateState() {
return true;
}
public updateState() {
}
@ -179,23 +175,8 @@ namespace pxsim.visuals {
public setSelected(selected: boolean) { }
protected getView() {
if (!this.rendered) {
this.subscribe();
}
return super.getView();
}
protected onBoardStateChanged() {
// To be implemented by sub class
}
protected subscribe() {
board().updateSubscribers.push(() => {
if (this.state.didChange()) {
this.onBoardStateChanged();
}
});
}
}
export class ViewContainer extends View {

35
tests/colors.ts Normal file
View File

@ -0,0 +1,35 @@
tests.test("Detect color red", function () {
brick.print("Point sensor to red", 0, 50)
brick.print("and click enter", 0, 60)
brick.buttonEnter.pauseUntil(ButtonEvent.Click)
brick.clearScreen()
let actualColor = sensors.color1.color()
tests.assertClose("Color", actualColor, ColorSensorColor.Red, 0)
})
tests.test("Bright ambient light", function () {
brick.print("Point sensor to ceiling", 0, 50)
brick.print("light and click enter", 0, 60)
brick.buttonEnter.pauseUntil(ButtonEvent.Click)
brick.clearScreen()
let actualLight: number
for (let i = 0; i < 4; i++) {
actualLight = sensors.color1.ambientLight()
loops.pause(500)
}
tests.assertClose("Light", actualLight, 20, 15)
})
tests.test("Bright reflected light", function () {
brick.print("Point sensor to white", 0, 50)
brick.print("desk surface", 0, 60)
brick.print("and click enter", 0, 70)
brick.buttonEnter.pauseUntil(ButtonEvent.Click)
brick.clearScreen()
let actualLight: number
for (let i = 0; i < 4; i++) {
actualLight = sensors.color1.reflectedLight()
loops.pause(500)
}
tests.assertClose("Light", actualLight, 17, 14)
})

42
tests/motors.ts Normal file
View File

@ -0,0 +1,42 @@
// add tests package
tests.test("lgB set speed 10", () => {
motors.largeB.setSpeed(10);
loops.pause(500)
tests.assertClose("speedB", 10, motors.largeB.speed(), 2)
});
tests.test("lgB set speed 25 (reversed)", () => {
motors.largeB.setReversed(true)
motors.largeB.setSpeed(25)
loops.pause(500)
tests.assertClose("speedB", -25, motors.largeB.speed(), 2)
});
tests.test("lgBC set speed 5", () => {
motors.largeBC.setSpeed(5)
loops.pause(500)
tests.assertClose("speedB", 5, motors.largeB.speed(), 1);
tests.assertClose("speedC", 5, motors.largeC.speed(), 1);
});
tests.test("lgBC steer 50% 2x", () => {
motors.largeBC.setBrake(true)
motors.largeBC.steer(50, 50, 1, MoveUnit.Rotations)
loops.pause(2000)
tests.assertClose("largeB", 360, motors.largeB.angle(), 5)
motors.largeBC.setBrake(false)
})
tests.test("lgBC steer 360deg", () => {
motors.largeBC.setBrake(true)
motors.largeBC.steer(50, 50, 360, MoveUnit.Degrees)
loops.pause(2000)
tests.assertClose("largeB", 360, motors.largeB.angle(), 5)
});
tests.test("lgBC steer 50% 1s", () => {
motors.largeBC.setBrake(true)
motors.largeBC.steer(10, 50, 1000, MoveUnit.MilliSeconds)
loops.pause(2000)
})
tests.test("lgBC tank 50% 180deg", () => {
motors.largeBC.setBrake(true)
motors.largeBC.tank(50, 50, 180, MoveUnit.Degrees)
loops.pause(1000)
tests.assertClose("largeB", 180, motors.largeB.angle(), 5)
});

7
tests/touch.ts Normal file
View File

@ -0,0 +1,7 @@
tests.test("Touch sensor pressed", function () {
brick.print("Press touch sensor", 0, 50)
brick.print("and click enter", 0, 60)
brick.buttonEnter.pauseUntil(ButtonEvent.Click)
brick.clearScreen()
tests.assert("Pressed", sensors.touchSensor1.isPressed())
})

9
tests/ultrasonic.ts Normal file
View File

@ -0,0 +1,9 @@
tests.test("Ultrasonic sensor", function () {
brick.print("Place object ", 0, 50)
brick.print("one finger's length", 0, 60)
brick.print("in front of sensor", 0, 70)
brick.print("and click enter", 0, 80)
brick.buttonEnter.pauseUntil(ButtonEvent.Click)
brick.clearScreen()
tests.assertClose("Distance", sensors.ultrasonic1.distance(), 7, 6)
})

View File

@ -28,9 +28,14 @@
div.blocklyTreeRow {
border-radius: 0px;
-webkit-box-shadow: inset 0px 0px 0px 3px rgba(0,0,0,0.2);
margin-bottom: 7px;
/*-webkit-box-shadow: inset 0px 0px 0px 3px rgba(0,0,0,0.2);
-moz-box-shadow: inset 0px 0px 0px 3px rgba(0,0,0,0.2);
box-shadow: inset 0px 0px 0px 3px rgba(0,0,0,0.2);
box-shadow: inset 0px 0px 0px 3px rgba(0,0,0,0.2);*/
}
div.blocklyTreeSeparator {
border-bottom: solid #CEA403 2px;
}
/* Remove shadow around blockly blocks */
@ -51,6 +56,7 @@ span.blocklyTreeLabel {
margin: 0.5rem;
margin-left: 1rem;
margin-right: 1rem;
margin-bottom: 0.8rem;
}
.blocklySearchInputField {
border-radius: 1rem !important;

View File

@ -10,7 +10,7 @@
@emSize : 14px;
@fontSize : 13px;
@primaryColor: @red;
@primaryColor: @blue;
@secondaryColor: @yellow;
@teal:#08415C;
@ -88,6 +88,8 @@
@pageBackground: #fff;
@positiveColor: @blue;
@defaultBorderRadius: 0px;
@inputPlaceholderColor: lighten(@inputColor, 80);