Compare commits

..

13 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
35 changed files with 365 additions and 229 deletions

View File

@ -68,6 +68,13 @@ 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)
}

View File

@ -32,15 +32,16 @@
"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.",

View File

@ -24,22 +24,23 @@
"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",

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

View File

@ -59,7 +59,6 @@ namespace console {
namespace console.screen {
const maxLines = 100;
const screenLines = 8;
const lineHeight = 12;
let lines: string[];
let scrollPosition = 0;
@ -67,8 +66,8 @@ namespace console.screen {
if (!lines) {
lines = [];
console.addListener(log);
brick.buttonUp.onEvent(ButtonEvent.Click, () => scroll(1))
brick.buttonDown.onEvent(ButtonEvent.Click, () => scroll(-1))
brick.buttonUp.onEvent(ButtonEvent.Click, () => scroll(-1))
brick.buttonDown.onEvent(ButtonEvent.Click, () => scroll(1))
}
}
@ -78,7 +77,7 @@ namespace console.screen {
for (let i = 0; i < screenLines; ++i) {
const line = lines[i + scrollPosition];
if (line)
brick.print(line, 0, 4 + i * lineHeight)
brick.print(line, 0, 4 + i * brick.LINE_HEIGHT)
}
}

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

View File

@ -75,9 +75,9 @@ namespace motors {
* @param buf message buffer
*/
//%
export function readPWM(buf: Buffer): number {
export function readPWM(buf: Buffer): void {
init()
return pwmMM.read(buf);
pwmMM.read(buf);
}
/**
@ -261,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;
}
/**
@ -529,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) {
@ -541,7 +548,7 @@ namespace motors {
return 0
}
interface MotorData {
export interface MotorData {
actualSpeed: number; // -100..+100
tachoCount: number;
count: number;
@ -550,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),
@ -558,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

@ -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

@ -23,9 +23,12 @@ namespace tests {
// clear state
this.reset();
console.log(`# ${this.name}`)
console.log(`> ${this.name}`)
this.handler()
if (this.errors.length)
console.log('')
// ensure clean state after test
this.reset();
}
@ -39,13 +42,15 @@ namespace tests {
const start = control.millis();
console.sendToScreen();
console.log(`${_tests.length} tests`)
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(`${_tests.map(t => t.errors.length).reduce((p, c) => p + c, 0)} X, ${Math.ceil((control.millis() - start) / 1000)}s`)
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`)
}
/**
@ -71,7 +76,7 @@ namespace tests {
//% blockId=testAssert block="assert %message|%condition"
export function assert(message: string, condition: boolean) {
if (!condition) {
console.log(` X ${message || ''}`)
console.log(`!!! ${message || ''}`)
if (_currentTest)
_currentTest.errors.push(message);
}

View File

@ -25,7 +25,7 @@ namespace sensors {
}
_query(): number {
return (this.getNumber(NumberFormat.UInt16LE, 0) & 0x0fff) * 0.1; // range is 0..2550, in 0.1 cm increments.
return ((this.getNumber(NumberFormat.UInt16LE, 0) & 0x0fff) / 10) >> 0; // range is 0..2550, in 0.1 cm increments.
}
_update(prev: number, curr: number) {
@ -80,7 +80,7 @@ namespace sensors {
distance(): number {
// it supposedly also has an inch mode, but we stick to cm
this._setMode(0)
return (this.getNumber(NumberFormat.UInt16LE, 0) & 0x0fff) * 0.1; // range is 0..2550, in 0.1 cm increments.
return this._query();
}
}

View File

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

View File

@ -132,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

@ -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

@ -17,10 +17,7 @@ namespace pxsim {
setDistance(distance: number) {
if (this.distance != distance) {
this.distance = distance;
this.changed = true;
this.valueChanged = true;
runtime.queueDisplayUpdate();
this.setChangedState();
}
}

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,7 +78,7 @@ namespace pxsim.visuals {
return this.getInnerHeight() * 0.6;
}
onBoardStateChanged() {
updateState() {
if (!this.isVisible) {
return;
}
@ -91,7 +91,6 @@ namespace pxsim.visuals {
onComponentVisible() {
super.onComponentVisible();
this.isVisible = true;
this.onBoardStateChanged();
}
onComponentHidden() {

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)
})

View File

@ -1,47 +1,42 @@
```typescript
// add tests package
tests.test("lgB set speed 10", () => {
motors.largeB.setSpeed(10);
loops.pause(100)
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(100)
loops.pause(500)
tests.assertClose("speedB", -25, motors.largeB.speed(), 2)
});
tests.test("lgBC set speed 5", () => {
motors.largeBC.setSpeed(5)
loops.pause(100)
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(1000)
loops.pause(2000)
tests.assertClose("largeB", 360, motors.largeB.angle(), 5)
motors.largeBC.setBrake(false)
})
tests.test("lgBC steer 50% 500deg", () => {
tests.test("lgBC steer 360deg", () => {
motors.largeBC.setBrake(true)
motors.largeBC.steer(50, 50, 135, MoveUnit.Degrees)
loops.pause(1000)
tests.assertClose("largeB", 135, motors.largeB.angle(), 5)
motors.largeBC.steer(50, 50, 360, MoveUnit.Degrees)
loops.pause(2000)
tests.assertClose("largeB", 360, motors.largeB.angle(), 5)
});
tests.test("lgBC steer 50% 2s", () => {
tests.test("lgBC steer 50% 1s", () => {
motors.largeBC.setBrake(true)
motors.largeBC.steer(50, 50, 500, MoveUnit.MilliSeconds)
loops.pause(1000)
motors.largeBC.steer(10, 50, 1000, MoveUnit.MilliSeconds)
loops.pause(2000)
})
tests.test("lgBC tank 50% 720deg", () => {
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)
});
```
```package
tests
```

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);