Compare commits

..

11 Commits

24 changed files with 290 additions and 78 deletions

View File

@ -2,14 +2,10 @@
import * as fs from 'fs';
require("./editor/deploy")
declare namespace pxt.editor {
function deployCoreAsync(resp: pxtc.CompileResult, disconnect?: boolean): Promise<void>;
}
const deploy = require("./editor/deploy")
export function deployCoreAsync(resp: pxtc.CompileResult) {
return pxt.editor.deployCoreAsync(resp, process.env["PXT_SERIAL"] ? false : true)
return deploy.deployCoreAsync(resp, process.env["PXT_SERIAL"] ? false : true)
.then(() => {
fs.writeFileSync("built/full-" + pxtc.BINARY_UF2, resp.outfiles[pxtc.BINARY_UF2], {
encoding: "base64"

1
docs/static/loader_back.svg vendored Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="301.499" height="179.742" viewBox="0 0 282.655 168.508"><g transform="translate(-41.005 -446.364)"><rect ry="30" rx="30" y="458.77" x="53.41" height="143.698" width="105.312" stroke="#000" stroke-width="17.724" stroke-linecap="round" stroke-linejoin="round"/><rect width="105.668" height="144.183" x="53.232" y="458.527" rx="30.101" ry="30.101" fill="#fff"/></g><g transform="translate(111.528 -446.364)"><rect width="105.312" height="143.698" x="53.41" y="458.77" rx="30" ry="30" stroke="#000" stroke-width="17.724" stroke-linecap="round" stroke-linejoin="round"/><rect ry="30.101" rx="30.101" y="458.527" x="53.232" height="144.183" width="105.668" fill="#fff"/></g></svg>

After

Width:  |  Height:  |  Size: 721 B

1
docs/static/loader_front.svg vendored Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="301.499" height="179.742" viewBox="0 0 282.655 168.508"><g stroke="#000" stroke-linecap="round" stroke-linejoin="round"><g transform="translate(124.66 -243.829)"><circle r="25.968" cy="356.872" cx="-38.891" fill="none"/><circle cx="-38.891" cy="356.872" r="21.013" stroke-width=".809"/><path d="M-30.581 341.741a13.835 13.835 0 0 1 5.76 4.504l-11.099 8.26z" fill="#fff" stroke-width=".533"/></g><g transform="translate(277.193 -243.829)"><circle cx="-38.891" cy="356.872" r="25.968" fill="none"/><circle r="21.013" cy="356.872" cx="-38.891" stroke-width=".809"/><path d="M-30.581 341.741a13.835 13.835 0 0 1 5.76 4.504l-11.099 8.26z" fill="#fff" stroke-width=".533"/></g></g></svg>

After

Width:  |  Height:  |  Size: 728 B

1
docs/static/loader_full.svg vendored Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="301.499" height="179.742" viewBox="0 0 282.655 168.508"><g transform="translate(-41.005 -446.364)"><rect ry="30" rx="30" y="458.77" x="53.41" height="143.698" width="105.312" stroke="#000" stroke-width="17.724" stroke-linecap="round" stroke-linejoin="round"/><rect width="105.668" height="144.183" x="53.232" y="458.527" rx="30.101" ry="30.101" fill="#fff"/></g><g transform="translate(124.66 -243.829)" stroke="#000" stroke-linecap="round" stroke-linejoin="round"><circle r="25.968" cy="356.872" cx="-38.891" fill="none"/><circle cx="-38.891" cy="356.872" r="21.013" stroke-width=".809"/><path d="M-30.581 341.741a13.835 13.835 0 0 1 5.76 4.504l-11.099 8.26z" fill="#fff" stroke-width=".533"/></g><g transform="translate(111.528 -446.364)"><rect width="105.312" height="143.698" x="53.41" y="458.77" rx="30" ry="30" stroke="#000" stroke-width="17.724" stroke-linecap="round" stroke-linejoin="round"/><rect ry="30.101" rx="30.101" y="458.527" x="53.232" height="144.183" width="105.668" fill="#fff"/></g><g transform="translate(277.193 -243.829)" stroke="#000" stroke-linecap="round" stroke-linejoin="round"><circle cx="-38.891" cy="356.872" r="25.968" fill="none"/><circle r="21.013" cy="356.872" cx="-38.891" stroke-width=".809"/><path d="M-30.581 341.741a13.835 13.835 0 0 1 5.76 4.504l-11.099 8.26z" fill="#fff" stroke-width=".533"/></g></svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -29,7 +29,8 @@ export function initAsync() {
let canHID = false
if (pxt.U.isNodeJS) {
canHID = true
// doesn't seem to work ATM
canHID = false
} else {
const forceHexDownload = /forceHexDownload/i.test(window.location.href);
if (pxt.Cloud.isLocalHost() && pxt.Cloud.localToken && !forceHexDownload)

View File

@ -21,6 +21,7 @@
"brick.Button.pauseUntil": "Waits until the event is raised",
"brick.Button.pauseUntil|param|ev": "the event to wait for",
"brick.Button.wasPressed": "See if the button was pressed again since the last time you checked.",
"brick.batteryLevel": "Returns the current battery level",
"brick.buttonDown": "Down button on the EV3 Brick.",
"brick.buttonEnter": "Enter button on the EV3 Brick.",
"brick.buttonLeft": "Left button on the EV3 Brick.",

View File

@ -28,6 +28,7 @@
"brick.Button.onEvent|block": "on %button|%event",
"brick.Button.pauseUntil|block": "pause until %button|%event",
"brick.Button.wasPressed|block": "%button|was pressed",
"brick.batteryLevel|block": "battery level",
"brick.buttonDown|block": "down",
"brick.buttonEnter|block": "enter",
"brick.buttonLeft|block": "left",
@ -89,6 +90,7 @@
"{id:group}Buttons": "Buttons",
"{id:group}Chassis": "Chassis",
"{id:group}Light": "Light",
"{id:group}More": "More",
"{id:group}Motion": "Motion",
"{id:group}Screen": "Screen",
"{id:group}Sensors": "Sensors"

12
libs/core/battery.ts Normal file
View File

@ -0,0 +1,12 @@
namespace brick {
/**
* Returns the current battery level
*/
//% blockId=brickBatteryLevel block="battery level"
//% group="More"
export function batteryLevel(): number {
const info = sensors.internal.getBatteryInfo();
return info.current;
}
}

View File

@ -163,6 +163,10 @@ namespace brick {
if (sl[i])
ret |= 1 << i
}
// this needs to be done in query(), which is run without the main JS execution mutex
// otherwise, while(true){} will lock the device
if (ret & DAL.BUTTON_ID_ESCAPE)
control.reset()
return ret
}
@ -172,8 +176,6 @@ namespace brick {
if (!btnsMM) control.fail("no buttons?")
buttons = []
sensors.internal.unsafePollForChanges(50, readButtons, (prev, curr) => {
if (curr & DAL.BUTTON_ID_ESCAPE)
control.reset()
for (let b of buttons)
b._update(!!(curr & b.mask))
})

View File

@ -89,6 +89,14 @@ namespace sensors.internal {
//serial.writeLine("UART " + port + " / " + mode + " - " + info)
}
export function getBatteryInfo(): { temp: number; current: number } {
init();
return {
temp: analogMM.getNumber(NumberFormat.Int16LE, AnalogOff.BatteryTemp),
current: analogMM.getNumber(NumberFormat.Int16LE, AnalogOff.BatteryCurrent)
}
}
function detectDevices() {
let conns = analogMM.slice(AnalogOff.InConn, DAL.NUM_INPUTS)
let numChanged = 0
@ -301,6 +309,10 @@ namespace sensors.internal {
return 0
return getUartNumber(fmt, off, this._port)
}
protected reset() {
if (this.isActive()) uartReset(this._port);
}
}
function uartReset(port: number) {

View File

@ -16,6 +16,7 @@
"png.cpp",
"screen.cpp",
"screen.ts",
"battery.ts",
"output.cpp",
"output.ts",
"core.ts",

View File

@ -1,4 +1,5 @@
{
"sensors.GyroSensor.angle": "Get the current angle from the gyroscope.",
"sensors.GyroSensor.rate": "Get the current rotation rate from the gyroscope."
"sensors.GyroSensor.calibrate": "Forces a calibration of the gyro. Must be called when the sensor is completely still.",
"sensors.GyroSensor.rotationRate": "Get the current rotation rate from the gyroscope."
}

View File

@ -1,6 +1,7 @@
{
"sensors.GyroSensor.angle|block": "%sensor|angle",
"sensors.GyroSensor.rate|block": "%sensor|rotation rate",
"sensors.GyroSensor.calibrate|block": "%sensor|calibrate",
"sensors.GyroSensor.rotationRate|block": "%sensor|rotation rate",
"sensors.gyro1|block": "gyro 1",
"sensors.gyro2|block": "gyro 2",
"sensors.gyro3|block": "gyro 3",

View File

@ -7,8 +7,10 @@ const enum GyroSensorMode {
namespace sensors {
//% fixedInstances
export class GyroSensor extends internal.UartSensor {
private calibrating: boolean;
constructor(port: number) {
super(port)
this.calibrating = false;
}
_deviceType() {
@ -32,6 +34,9 @@ namespace sensors {
//% weight=65 blockGap=8
//% group="Gyro Sensor"
angle(): number {
if (this.calibrating)
pauseUntil(() => !this.calibrating, 2000);
this.setMode(GyroSensorMode.Angle)
return this.getNumber(NumberFormat.Int16LE, 0)
}
@ -40,7 +45,7 @@ namespace sensors {
* Get the current rotation rate from the gyroscope.
* @param sensor the gyroscope to query the request
*/
//% help=input/gyro/rate
//% help=input/gyro/rotation-rate
//% block="%sensor|rotation rate"
//% blockId=gyroGetRate
//% parts="gyroscope"
@ -48,18 +53,53 @@ namespace sensors {
//% sensor.fieldEditor="ports"
//% weight=65 blockGap=8
//% group="Gyro Sensor"
rate(): number {
rotationRate(): number {
if (this.calibrating)
pauseUntil(() => !this.calibrating, 2000);
this.setMode(GyroSensorMode.Rate)
return this.getNumber(NumberFormat.Int16LE, 0)
}
}
//% fixedInstance whenUsed block="gyro 1" jres=icons.port1
export const gyro1: GyroSensor = new GyroSensor(1)
/**
* Forces a calibration of the gyro. Must be called when the sensor is completely still.
*/
//% help=input/gyro/calibrate
//% block="%sensor|calibrate"
//% blockId=gyroCalibrate
//% parts="gyroscope"
//% blockNamespace=sensors
//% sensor.fieldEditor="ports"
//% weight=65 blockGap=8
//% group="Gyro Sensor"
calibrate(): void {
if (this.calibrating) return; // already in calibration mode
this.calibrating = true;
// may be triggered by a button click, give time to settle
loops.pause(500);
// send a reset command
this.reset();
// we need to switch mode twice to perform a calibration
if (this.mode == GyroSensorMode.Rate)
this.setMode(GyroSensorMode.Angle);
else
this.setMode(GyroSensorMode.Rate);
// switch back and wait
if (this.mode == GyroSensorMode.Rate)
this.setMode(GyroSensorMode.Angle);
else
this.setMode(GyroSensorMode.Rate);
this.calibrating = false;
}
}
//% fixedInstance whenUsed block="gyro 2" weight=95 jres=icons.port2
export const gyro2: GyroSensor = new GyroSensor(2)
//% fixedInstance whenUsed block="gyro 1" jres=icons.port1
export const gyro1: GyroSensor = new GyroSensor(1)
//% fixedInstance whenUsed block="gyro 3" jres=icons.port3
export const gyro3: GyroSensor = new GyroSensor(3)

View File

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

View File

@ -3,24 +3,6 @@
/// <reference path="../built/common-sim.d.ts"/>
namespace pxsim {
export enum CPlayPinName {
A0,
A1,
A2,
A3,
A4,
A5,
A6,
A7,
A8,
A9,
D4,
D5,
D6,
D7,
D8,
D13
}
export class EV3Board extends CoreBoard {
view: SVGSVGElement;
@ -36,7 +18,7 @@ namespace pxsim {
brickNode: BrickNode;
outputNodes: MotorNode[] = [];
private motorMap: pxt.Map<number> = {
public motorMap: pxt.Map<number> = {
0x01: 0,
0x02: 1,
0x04: 2,
@ -115,27 +97,40 @@ namespace pxsim {
return this.brickNode;
}
motorUsed(port:number, large: boolean) {
for(let i = 0; i < DAL.NUM_OUTPUTS; ++i) {
motorUsed(port: number, large: boolean) {
for (let i = 0; i < DAL.NUM_OUTPUTS; ++i) {
const p = 1 << i;
if (port & p) {
const motorPort = this.motorMap[p];
if (!this.outputNodes[motorPort])
this.outputNodes[motorPort] = new MotorNode(motorPort, large);
}
}
}
}
}
hasMotor(port: number) {
for (let i = 0; i < DAL.NUM_OUTPUTS; ++i) {
const p = 1 << i;
if (port & p) {
const motorPort = this.motorMap[p];
const outputNode = this.outputNodes[motorPort];
if (outputNode)
return true;
}
}
return false;
}
getMotor(port: number, large?: boolean): MotorNode[] {
const r = [];
for(let i = 0; i < DAL.NUM_OUTPUTS; ++i) {
for (let i = 0; i < DAL.NUM_OUTPUTS; ++i) {
const p = 1 << i;
if (port & p) {
const motorPort = this.motorMap[p];
const outputNode = this.outputNodes[motorPort];
if (outputNode)
r.push(outputNode);
}
}
}
return r;
}
@ -144,6 +139,10 @@ namespace pxsim {
return this.outputNodes;
}
hasSensor(port: number) {
return !!this.inputNodes[port];
}
getSensor(port: number, type: number): SensorNode {
if (!this.inputNodes[port]) {
switch (type) {
@ -168,6 +167,7 @@ namespace pxsim {
runtime.postError = (e) => {
// TODO
runtime.updateDisplay();
console.log('runtime error: ' + e);
}
}

View File

@ -24,13 +24,14 @@ namespace pxsim {
}
export class EV3AnalogState {
constructor() {
let data = new Uint8Array(5172)
MMapMethods.register("/dev/lms_analog", {
data,
beforeMemRead: () => {
//console.log("analog before read");
data[AnalogOff.BatteryTemp] = 21; // TODO simulate this
data[AnalogOff.BatteryCurrent] = 100; // TODO simulate this
const inputNodes = ev3board().getInputNodes();
for (let port = 0; port < DAL.NUM_INPUTS; port++) {
const node = inputNodes[port];

View File

@ -1,10 +1,17 @@
import lf = pxsim.localization.lf;
namespace pxsim.motors {
export function __motorUsed(port: number, large: boolean) {
//console.log("MOTOR INIT " + port);
ev3board().motorUsed(port, large);
runtime.queueDisplayUpdate();
if (!ev3board().hasMotor(port)) {
ev3board().motorUsed(port, large);
runtime.queueDisplayUpdate();
} else {
U.userError(`${lf("Multiple motors are connected to Port")} ${String.fromCharCode('A'.charCodeAt(0) + ev3board().motorMap[port])}`);
}
}
}
@ -12,7 +19,11 @@ namespace pxsim.sensors {
export function __sensorUsed(port: number, type: number) {
//console.log("SENSOR INIT " + port + ", type: " + type);
const sensor = ev3board().getSensor(port, type);
runtime.queueDisplayUpdate();
if (!ev3board().hasSensor(port)) {
const sensor = ev3board().getSensor(port, type);
runtime.queueDisplayUpdate();
} else {
U.userError(`${lf("Multiple sensors are connected to Port")} ${port + 1}`);
}
}
}

View File

@ -88,7 +88,7 @@ namespace pxsim {
}
updateState(elapsed: number) {
console.log(`motor: ${elapsed}ms - ${this.speed}% - ${this.angle}> - ${this.tacho}|`)
//console.log(`motor: ${elapsed}ms - ${this.speed}% - ${this.angle}> - ${this.tacho}|`)
const interval = Math.min(20, elapsed);
let t = 0;
while (t < elapsed) {

View File

@ -1,26 +1,47 @@
/// <reference path="./moduleView.ts" />
/// <reference path="./motorView.ts" />
namespace pxsim.visuals {
export class LargeMotorView extends ModuleView implements LayoutElement {
private static ROTATING_ECLIPSE_ID = "hole";
export class LargeMotorView extends MotorView implements LayoutElement {
constructor(port: number) {
super(LARGE_MOTOR_SVG, "large-motor", NodeType.LargeMotor, port);
super(LARGE_MOTOR_SVG, "large-motor", NodeType.LargeMotor, port, "hole");
}
private syncedMotor: MotorNode;
private syncedLabelG: SVGGElement;
updateState() {
super.updateState();
const motorState = ev3board().getMotors()[this.port];
if (!motorState) return;
const speed = motorState.getSpeed();
if (!speed) return;
this.setMotorAngle(motorState.getAngle());
const syncedMotor = motorState.getSynchedMotor();
if ((syncedMotor || this.syncedMotor) && syncedMotor != this.syncedMotor) {
this.syncedMotor = syncedMotor;
this.showSyncedLabel(motorState, syncedMotor);
}
}
private setMotorAngle(angle: number) {
const holeEl = this.content.getElementById(this.normalizeId(LargeMotorView.ROTATING_ECLIPSE_ID))
private showSyncedLabel(motorNode: MotorNode, syncedMotor: MotorNode) {
const a = String.fromCharCode('A'.charCodeAt(0) + motorNode.port);
const b = String.fromCharCode('A'.charCodeAt(0) + syncedMotor.port);
this.syncedLabelG = pxsim.svg.child(this.element, 'g', {'transform': 'scale(0.5)'}) as SVGGElement;
pxsim.svg.child(this.syncedLabelG, 'rect', {'rx': 15, 'ry': 15, 'x': 0, 'y': 0, 'width': 84, 'height': 34, 'fill': '#A8A9A8'});
pxsim.svg.child(this.syncedLabelG, 'circle', {'cx': 17, 'cy': 17, 'r': 15, 'fill': 'white'});
const leftLabel = pxsim.svg.child(this.syncedLabelG, 'text', {'transform': 'translate(11, 22)', 'style': 'isolation: isolate;font-size: 16px;fill: #A8A9A8;font-family: ArialMT, Arial'});
leftLabel.textContent = a;
pxsim.svg.child(this.syncedLabelG, 'rect', {'rx': 0, 'ry': 0, 'x': 37, 'y': 12, 'width': 10, 'height': 3, 'fill': '#ffffff'});
pxsim.svg.child(this.syncedLabelG, 'rect', {'rx': 0, 'ry': 0, 'x': 37, 'y': 18, 'width': 10, 'height': 3, 'fill': '#ffffff'});
pxsim.svg.child(this.syncedLabelG, 'circle', {'cx': 67, 'cy': 17, 'r': 15, 'fill': 'white'});
const rightLabel = pxsim.svg.child(this.syncedLabelG, 'text', {'transform': 'translate(61, 22)', 'style': 'isolation: isolate;font-size: 16px;fill: #A8A9A8;font-family: ArialMT, Arial'});
rightLabel.textContent = b;
}
protected renderMotorAngle(holeEl: Element, angle: number) {
const width = 125.92;
const height = 37.9;
const transform = `rotate(${angle} ${width / 2} ${height / 2})`;

View File

@ -2,35 +2,17 @@
namespace pxsim.visuals {
export const MOTOR_ROTATION_FPS = 32;
export class MediumMotorView extends ModuleView implements LayoutElement {
private static ROTATING_ECLIPSE_ID = "medmotor_Hole";
private hasPreviousAngle: boolean;
private previousAngle: number;
export class MediumMotorView extends MotorView implements LayoutElement {
constructor(port: number) {
super(MEDIUM_MOTOR_SVG, "medium-motor", NodeType.MediumMotor, port);
super(MEDIUM_MOTOR_SVG, "medium-motor", NodeType.MediumMotor, port, "medmotor_Hole");
}
public getPaddingRatio() {
return 1 / 5;
}
updateState() {
super.updateState();
const motorState = ev3board().getMotors()[this.port];
if (!motorState) return;
const speed = motorState.getSpeed();
if (!speed) return;
this.setMotorAngle(motorState.getAngle());
}
private setMotorAngle(angle: number) {
const holeEl = this.content.getElementById(this.normalizeId(MediumMotorView.ROTATING_ECLIPSE_ID))
protected renderMotorAngle(holeEl: Element, angle: number) {
const width = 44.45;
const height = 44.45;
const transform = `translate(2 1.84) rotate(${angle} ${width / 2} ${height / 2})`;

View File

@ -0,0 +1,33 @@
/// <reference path="./moduleView.ts" />
namespace pxsim.visuals {
export abstract class MotorView extends ModuleView implements LayoutElement {
constructor(xml: string, prefix: string, id: NodeType, port: NodeType,
protected rotating_hole_id: string) {
super(xml, prefix, id, port);
}
updateState() {
super.updateState();
const motorState = ev3board().getMotors()[this.port];
if (!motorState) return;
const speed = motorState.getSpeed();
if (!speed) return;
this.setMotorAngle(motorState.getAngle());
}
private setMotorAngle(angle: number) {
const holeEl = this.content.getElementById(this.normalizeId(this.rotating_hole_id))
this.renderMotorAngle(holeEl, angle);
}
protected abstract renderMotorAngle(holeEl: Element, angle: number): void;
getWiringRatio() {
return 0.37;
}
}
}

View File

@ -1,3 +1,79 @@
/*******************************
Site Overrides
*******************************/
.ui.loader:before {
border: none;
border-radius: 0px;
box-shadow: none;
background: transparent @loaderBackImage no-repeat center center;
background-size: 100%;
}
.ui.loader:after {
border: none;
box-shadow: none;
border-radius: 0px;
margin: 40px auto;
background: transparent @loaderImage no-repeat center center;
background-size: 100%;
-webkit-animation: @loaderAnimation @loaderSpeed infinite linear;
animation: @loaderAnimation @loaderSpeed infinite linear;
}
.ui.loader.avatar:after {
border: none;
box-shadow: none;
border-radius: 0px;
margin: 40px auto;
background: transparent @avatarImage no-repeat center center;
background-size: 100%;
-webkit-animation: @loaderAnimation @loaderSpeed infinite linear;
animation: @loaderAnimation @loaderSpeed infinite linear;
}
@-webkit-keyframes loader-pxt-ev3 {
0% {
-webkit-transform: translateX(@loaderStartPoint);
transform: translateX(@loaderStartPoint);
}
20% {
-webkit-transform: translateX(-@loaderMiddlePoint);
transform: translateX(-@loaderMiddlePoint);
}
50% {
-webkit-transform: translateX(-@loaderEndPoint);
transform: translateX(-@loaderEndPoint);
}
75% {
-webkit-transform: translateX(-@loaderMiddlePoint);
transform: translateX(-@loaderMiddlePoint);
}
100% {
-webkit-transform: translateX(@loaderStartPoint);
transform: translateX(@loaderStartPoint);
}
}
@keyframes loader-pxt-ev3 {
0% {
-webkit-transform: translateX(@loaderStartPoint);
transform: translateX(@loaderStartPoint);
}
20% {
-webkit-transform: translateX(-@loaderMiddlePoint);
transform: translateX(-@loaderMiddlePoint);
}
50% {
-webkit-transform: translateX(-@loaderEndPoint);
transform: translateX(-@loaderEndPoint);
}
75% {
-webkit-transform: translateX(-@loaderMiddlePoint);
transform: translateX(-@loaderMiddlePoint);
}
100% {
-webkit-transform: translateX(@loaderStartPoint);
transform: translateX(@loaderStartPoint);
}
}

View File

@ -1,3 +1,19 @@
/*******************************
User Variable Overrides
*******************************/
@loaderBackImageUrl: "../docs/static/loader_back.svg";
@loaderBackImage: data-uri(@loaderBackImageUrl);
@loaderImageUrl: "../docs/static/loader_front.svg";
@loaderImage: data-uri(@loaderImageUrl);
@loaderSpeed: 2s;
@loaderStartPoint: 0px;
@loaderMiddlePoint: 14px;
@loaderEndPoint: 28px;
@loaderAnimation: loader-pxt-ev3;
@large : 200px;