UNPKG

pxt-ev3

Version:

LEGO MINDSTORMS EV3 for Microsoft MakeCode

1,221 lines (1,220 loc) 288 kB
/// <reference path="../node_modules/pxt-core/built/pxtsim.d.ts"/> /// <reference path="../node_modules/pxt-core/localtypings/pxtarget.d.ts"/> /// <reference path="../built/common-sim.d.ts"/> var pxsim; (function (pxsim) { class EV3Board extends pxsim.CoreBoard { constructor() { super(); this.inputNodes = []; this.outputNodes = []; this.motorMap = { 0x01: 0, 0x02: 1, 0x04: 2, 0x08: 3 }; this.bus.setNotify(10000 /* DAL.DEVICE_ID_NOTIFY */, 10001 /* DAL.DEVICE_ID_NOTIFY_ONE */); this.brickNode = new pxsim.BrickNode(); this.outputState = new pxsim.EV3OutputState(); this.analogState = new pxsim.EV3AnalogState(); this.uartState = new pxsim.EV3UArtState(); this.motorState = new pxsim.EV3MotorState(); this.screenState = new pxsim.ScreenState(["#97b5a6", "#000000"], pxsim.visuals.SCREEN_WIDTH, pxsim.visuals.SCREEN_HEIGHT); this.audioState = new pxsim.AudioState(); this.remoteState = new pxsim.RemoteState(); } receiveMessage(msg) { if (!pxsim.runtime || pxsim.runtime.dead) return; switch (msg.type || "") { case "eventbus": { let ev = msg; this.bus.queue(ev.id, ev.eventid, ev.value); break; } case "serial": { let data = msg.data || ""; // TODO break; } } } initAsync(msg) { super.initAsync(msg); const options = (msg.options || {}); const boardDef = msg.boardDefinition; const cmpsList = msg.parts; const cmpDefs = msg.partDefinitions || {}; const fnArgs = msg.fnArgs; const opts = { state: this, boardDef: boardDef, partsList: cmpsList, partDefs: cmpDefs, fnArgs: fnArgs, maxWidth: "100%", maxHeight: "100%", highContrast: msg.highContrast, light: msg.light }; this.viewHost = new pxsim.visuals.BoardHost(pxsim.visuals.mkBoardView({ boardDef, visual: boardDef.visual, highContrast: msg.highContrast, light: msg.light }), opts); document.body.innerHTML = ""; // clear children document.body.className = msg.light ? "light" : ""; document.body.appendChild(this.view = this.viewHost.getView()); this.inputNodes = []; this.outputNodes = []; this.highcontrastMode = msg.highContrast; this.lightMode = msg.light; return Promise.resolve(); } screenshotAsync(width) { return this.viewHost.screenshotAsync(width); } getBrickNode() { return this.brickNode; } motorUsed(ports, large) { for (let i = 0; i < 4 /* DAL.NUM_OUTPUTS */; ++i) { const p = 1 << i; if (ports & p) { const motorPort = this.motorMap[p]; const outputNode = this.outputNodes[motorPort]; if (!outputNode) { this.outputNodes[motorPort] = new pxsim.MotorNode(motorPort, large); continue; } if (outputNode && outputNode.isLarge() != large) return false; } } return true; } getMotor(port, large) { const r = []; for (let i = 0; i < 4 /* 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; } getMotors() { return this.outputNodes; } hasSensor(port) { return !!this.inputNodes[port]; } getSensor(port, type) { if (!this.inputNodes[port]) { switch (type) { case 32 /* DAL.DEVICE_TYPE_GYRO */: this.inputNodes[port] = new pxsim.GyroSensorNode(port); break; case 29 /* DAL.DEVICE_TYPE_COLOR */: this.inputNodes[port] = new pxsim.ColorSensorNode(port); break; case 16 /* DAL.DEVICE_TYPE_TOUCH */: this.inputNodes[port] = new pxsim.TouchSensorNode(port); break; case 30 /* DAL.DEVICE_TYPE_ULTRASONIC */: this.inputNodes[port] = new pxsim.UltrasonicSensorNode(port); break; case 33 /* DAL.DEVICE_TYPE_IR */: this.inputNodes[port] = new pxsim.InfraredSensorNode(port); break; case 2 /* DAL.DEVICE_TYPE_NXT_LIGHT */: this.inputNodes[port] = new pxsim.NXTLightSensorNode(port); break; } } return this.inputNodes[port]; } getInputNodes() { return this.inputNodes; } } pxsim.EV3Board = EV3Board; function initRuntimeWithDalBoard() { pxsim.U.assert(!pxsim.runtime.board); let b = new EV3Board(); pxsim.runtime.board = b; pxsim.runtime.postError = (e) => { // TODO pxsim.runtime.updateDisplay(); console.log('runtime error: ' + e); }; } pxsim.initRuntimeWithDalBoard = initRuntimeWithDalBoard; function ev3board() { return pxsim.runtime.board; } pxsim.ev3board = ev3board; function inLightMode() { return /light=1/i.test(window.location.href) || ev3board().lightMode; } pxsim.inLightMode = inLightMode; function inHighcontrastMode() { return ev3board().highcontrastMode; } pxsim.inHighcontrastMode = inHighcontrastMode; if (!pxsim.initCurrentRuntime) { pxsim.initCurrentRuntime = initRuntimeWithDalBoard; } })(pxsim || (pxsim = {})); var pxsim; (function (pxsim) { let AnalogOff; (function (AnalogOff) { AnalogOff[AnalogOff["InPin1"] = 0] = "InPin1"; AnalogOff[AnalogOff["InPin6"] = 8] = "InPin6"; AnalogOff[AnalogOff["OutPin5"] = 16] = "OutPin5"; AnalogOff[AnalogOff["BatteryTemp"] = 24] = "BatteryTemp"; AnalogOff[AnalogOff["MotorCurrent"] = 26] = "MotorCurrent"; AnalogOff[AnalogOff["BatteryCurrent"] = 28] = "BatteryCurrent"; AnalogOff[AnalogOff["Cell123456"] = 30] = "Cell123456"; AnalogOff[AnalogOff["Pin1"] = 32] = "Pin1"; AnalogOff[AnalogOff["Pin6"] = 2432] = "Pin6"; AnalogOff[AnalogOff["Actual"] = 4832] = "Actual"; AnalogOff[AnalogOff["LogIn"] = 4840] = "LogIn"; AnalogOff[AnalogOff["LogOut"] = 4848] = "LogOut"; AnalogOff[AnalogOff["NxtCol"] = 4856] = "NxtCol"; AnalogOff[AnalogOff["OutPin5Low"] = 5144] = "OutPin5Low"; AnalogOff[AnalogOff["Updated"] = 5152] = "Updated"; AnalogOff[AnalogOff["InDcm"] = 5156] = "InDcm"; AnalogOff[AnalogOff["InConn"] = 5160] = "InConn"; AnalogOff[AnalogOff["OutDcm"] = 5164] = "OutDcm"; AnalogOff[AnalogOff["OutConn"] = 5168] = "OutConn"; AnalogOff[AnalogOff["Size"] = 5172] = "Size"; })(AnalogOff = pxsim.AnalogOff || (pxsim.AnalogOff = {})); class EV3AnalogState { constructor() { let data = new Uint8Array(5172); pxsim.MMapMethods.register("/dev/lms_analog", { data, beforeMemRead: () => { //console.log("analog before read"); pxsim.util.map16Bit(data, AnalogOff.BatteryTemp, 21); pxsim.util.map16Bit(data, AnalogOff.BatteryCurrent, 900); const inputNodes = pxsim.ev3board().getInputNodes(); for (let port = 0; port < 4 /* DAL.NUM_INPUTS */; port++) { const node = inputNodes[port]; if (node) { if (node.isAnalog()) data[AnalogOff.InDcm + port] = node.getDeviceType(); data[AnalogOff.InConn + port] = node.isUart() ? 122 /* DAL.CONN_INPUT_UART */ : (!node.isNXT() ? 121 /* DAL.CONN_INPUT_DUMB */ : 119 /* DAL.CONN_NXT_DUMB */); if (node.isAnalog() && node.hasData()) { //data[AnalogOff.InPin6 + 2 * port] = node.getValue(); pxsim.util.map16Bit(data, node.getAnalogReadPin() + 2 * port, Math.floor(node.getValue())); } } } }, read: buf => { let v = "vSIM"; for (let i = 0; i < buf.data.length; ++i) buf.data[i] = v.charCodeAt(i) || 0; return buf.data.length; }, write: buf => { return 2; }, ioctl: (id, buf) => { //console.log("ioctl: " + id); for (let port = 0; port < 4 /* DAL.NUM_INPUTS */; port++) { const connection = buf.data[pxsim.DevConOff.Connection + port]; const type = buf.data[pxsim.DevConOff.Type + port]; const mode = buf.data[pxsim.DevConOff.Mode + port]; //console.log(`${port}, mode: ${mode}`); const node = pxsim.ev3board().getInputNodes()[port]; if (node) node.setMode(mode); } return 2; } }); } } pxsim.EV3AnalogState = EV3AnalogState; })(pxsim || (pxsim = {})); var pxsim; (function (pxsim) { let NodeType; (function (NodeType) { NodeType[NodeType["Port"] = 0] = "Port"; NodeType[NodeType["Brick"] = 1] = "Brick"; NodeType[NodeType["TouchSensor"] = 2] = "TouchSensor"; NodeType[NodeType["MediumMotor"] = 3] = "MediumMotor"; NodeType[NodeType["LargeMotor"] = 4] = "LargeMotor"; NodeType[NodeType["GyroSensor"] = 5] = "GyroSensor"; NodeType[NodeType["ColorSensor"] = 6] = "ColorSensor"; NodeType[NodeType["UltrasonicSensor"] = 7] = "UltrasonicSensor"; NodeType[NodeType["InfraredSensor"] = 8] = "InfraredSensor"; NodeType[NodeType["NXTLightSensor"] = 9] = "NXTLightSensor"; })(NodeType = pxsim.NodeType || (pxsim.NodeType = {})); class BaseNode { constructor(port) { this.isOutput = false; this.used = false; this.changed = true; this.port = port; } didChange() { const res = this.changed; this.changed = false; return res; } setChangedState() { this.changed = true; } /** * Updates any internal state according to the elapsed time since the last call to `updateState` * @param elapsed */ updateState(elapsed) { } } pxsim.BaseNode = BaseNode; })(pxsim || (pxsim = {})); /// <reference path="./nodeTypes.ts"/> var pxsim; (function (pxsim) { class PortNode extends pxsim.BaseNode { constructor(port) { super(port); this.id = pxsim.NodeType.Port; } } pxsim.PortNode = PortNode; class BrickNode extends pxsim.BaseNode { constructor() { super(-1); this.id = pxsim.NodeType.Brick; this.buttonState = new pxsim.EV3ButtonState(); this.lightState = new pxsim.EV3LightState(); } } pxsim.BrickNode = BrickNode; })(pxsim || (pxsim = {})); var pxsim; (function (pxsim) { class EV3ButtonState extends pxsim.CommonButtonState { constructor() { super(); this.buttons = [ new pxsim.CommonButton(1 /* DAL.BUTTON_ID_UP */), new pxsim.CommonButton(2 /* DAL.BUTTON_ID_ENTER */), new pxsim.CommonButton(4 /* DAL.BUTTON_ID_DOWN */), new pxsim.CommonButton(8 /* DAL.BUTTON_ID_RIGHT */), new pxsim.CommonButton(16 /* DAL.BUTTON_ID_LEFT */), new pxsim.CommonButton(32 /* DAL.BUTTON_ID_ESCAPE */) ]; let data = new Uint8Array(this.buttons.length); pxsim.MMapMethods.register("/dev/lms_ui", { data, beforeMemRead: () => { for (let i = 0; i < this.buttons.length; ++i) data[i] = this.buttons[i].isPressed() ? 1 : 0; }, read: buf => { let v = "vSIM"; for (let i = 0; i < buf.data.length; ++i) buf.data[i] = v.charCodeAt(i) || 0; return buf.data.length; }, write: buf => { pxsim.output.setLights(buf.data[0] - 48); return 2; } }); } } pxsim.EV3ButtonState = EV3ButtonState; })(pxsim || (pxsim = {})); var pxsim; (function (pxsim) { class SensorNode extends pxsim.BaseNode { constructor(port) { super(port); } isUart() { return true; } isAnalog() { return false; } isNXT() { return false; } isModeReturnArr() { return this.modeReturnArr; } getValue() { return 0; } getValues() { return [0]; } getAnalogReadPin() { return pxsim.AnalogOff.InPin6; // Defl for ev3 sensor } setMode(mode) { this.mode = mode; this.changed = true; this.modeChanged = true; this.modeReturnArr = false; } getMode() { return this.mode; } getDeviceType() { return 126 /* DAL.DEVICE_TYPE_NONE */; } hasData() { return true; } valueChange() { const res = this.valueChanged; this.valueChanged = false; return res; } modeChange() { const res = this.modeChanged; this.modeChanged = false; return res; } setChangedState() { this.changed = true; this.valueChanged = false; } } pxsim.SensorNode = SensorNode; class AnalogSensorNode extends SensorNode { constructor(port) { super(port); } isUart() { return false; } isAnalog() { return true; } } pxsim.AnalogSensorNode = AnalogSensorNode; class UartSensorNode extends SensorNode { constructor(port) { super(port); } hasChanged() { return this.changed; } } pxsim.UartSensorNode = UartSensorNode; })(pxsim || (pxsim = {})); /// <reference path="./sensor.ts"/> var pxsim; (function (pxsim) { let ColorSensorMode; (function (ColorSensorMode) { ColorSensorMode[ColorSensorMode["None"] = -1] = "None"; ColorSensorMode[ColorSensorMode["Reflected"] = 0] = "Reflected"; ColorSensorMode[ColorSensorMode["Ambient"] = 1] = "Ambient"; ColorSensorMode[ColorSensorMode["Colors"] = 2] = "Colors"; ColorSensorMode[ColorSensorMode["RefRaw"] = 3] = "RefRaw"; ColorSensorMode[ColorSensorMode["RgbRaw"] = 4] = "RgbRaw"; ColorSensorMode[ColorSensorMode["ColorCal"] = 5] = "ColorCal"; })(ColorSensorMode = pxsim.ColorSensorMode || (pxsim.ColorSensorMode = {})); let ThresholdState; (function (ThresholdState) { ThresholdState[ThresholdState["Normal"] = 1] = "Normal"; ThresholdState[ThresholdState["High"] = 2] = "High"; ThresholdState[ThresholdState["Low"] = 3] = "Low"; })(ThresholdState = pxsim.ThresholdState || (pxsim.ThresholdState = {})); class ColorSensorNode extends pxsim.UartSensorNode { constructor(port) { super(port); this.id = pxsim.NodeType.ColorSensor; this.color = 0; this.colors = [0, 0, 0]; this.mode = -1; this.modeReturnArr = false; } getDeviceType() { return 29 /* DAL.DEVICE_TYPE_COLOR */; } isModeReturnArr() { return this.modeReturnArr; } setColors(colors) { this.colors = colors; this.setChangedState(); } setColor(color) { this.color = color; this.setChangedState(); } getValue() { return this.color; } getValues() { return this.colors; } setMode(mode) { this.mode = mode; if (this.mode == ColorSensorMode.RefRaw) { this.color = 512; this.colors = [0, 0, 0]; this.modeReturnArr = false; } else if (this.mode == ColorSensorMode.RgbRaw) { this.color = 0; this.colors = [128, 128, 128]; this.modeReturnArr = true; } else if (this.mode == ColorSensorMode.Colors) { this.color = 0; // None defl color this.colors = [0, 0, 0]; this.modeReturnArr = false; } else { // Reflection or ambiend light this.color = 50; this.colors = [0, 0, 0]; this.modeReturnArr = false; } this.changed = true; this.modeChanged = true; } } pxsim.ColorSensorNode = ColorSensorNode; })(pxsim || (pxsim = {})); /// <reference path="../../libs/core/enums.d.ts"/> var pxsim; (function (pxsim) { var MMapMethods; (function (MMapMethods) { var BM = pxsim.BufferMethods; class MMap extends pxsim.RefObject { constructor(impl, len) { super(); this.impl = impl; this.len = len; if (!impl.data) impl.data = new Uint8Array(this.len); if (!impl.afterMemWrite) impl.afterMemWrite = () => { }; if (!impl.beforeMemRead) impl.beforeMemRead = () => { }; if (!impl.read) impl.read = () => 0; if (!impl.write) impl.write = () => 0; if (!impl.ioctl) impl.ioctl = () => -1; if (!impl.lseek) impl.lseek = (offset, whence) => -1; } destroy() { } buf() { return { data: this.impl.data }; } } MMapMethods.MMap = MMap; MMapMethods.mmapRegistry = {}; function register(filename, impl) { MMapMethods.mmapRegistry[filename] = impl; } MMapMethods.register = register; function setNumber(m, format, offset, value) { BM.setNumber(m.buf(), format, offset, value); m.impl.afterMemWrite(); } MMapMethods.setNumber = setNumber; function getNumber(m, format, offset) { m.impl.beforeMemRead(); return BM.getNumber(m.buf(), format, offset); } MMapMethods.getNumber = getNumber; function slice(m, offset, length) { m.impl.beforeMemRead(); return BM.slice(m.buf(), offset, length); } MMapMethods.slice = slice; function length(m) { m.impl.beforeMemRead(); return m.buf().data.length; } MMapMethods.length = length; function ioctl(m, id, data) { return m.impl.ioctl(id, data); } MMapMethods.ioctl = ioctl; function write(m, data) { return m.impl.write(data); } MMapMethods.write = write; function read(m, data) { return m.impl.read(data); } MMapMethods.read = read; function lseek(m, offset, whence) { return m.impl.lseek(offset, whence); } MMapMethods.lseek = lseek; })(MMapMethods = pxsim.MMapMethods || (pxsim.MMapMethods = {})); })(pxsim || (pxsim = {})); (function (pxsim) { var control; (function (control) { function mmap(filename, size, offset) { let impl = pxsim.MMapMethods.mmapRegistry[filename]; if (!impl) impl = {}; return new pxsim.MMapMethods.MMap(impl, size); } control.mmap = mmap; })(control = pxsim.control || (pxsim.control = {})); })(pxsim || (pxsim = {})); (function (pxsim) { var output; (function (output) { function createBuffer(size) { return pxsim.BufferMethods.createBuffer(size); } output.createBuffer = createBuffer; })(output = pxsim.output || (pxsim.output = {})); })(pxsim || (pxsim = {})); var pxsim; (function (pxsim) { class GyroSensorNode extends pxsim.UartSensorNode { constructor(port) { super(port); this.id = pxsim.NodeType.GyroSensor; this.rate = 0; } getDeviceType() { return 32 /* DAL.DEVICE_TYPE_GYRO */; } setRate(rate) { rate = rate | 0; if (this.rate != rate) { this.rate = rate; this.setChangedState(); } } getRate() { return this.rate; } getValue() { return this.getRate(); } } pxsim.GyroSensorNode = GyroSensorNode; })(pxsim || (pxsim = {})); /// <reference path="./sensor.ts"/> var pxsim; (function (pxsim) { let InfraredRemoteButton; (function (InfraredRemoteButton) { //% block="center beacon" InfraredRemoteButton[InfraredRemoteButton["CenterBeacon"] = 1] = "CenterBeacon"; //% block="top left" InfraredRemoteButton[InfraredRemoteButton["TopLeft"] = 2] = "TopLeft"; //% block="bottom left" InfraredRemoteButton[InfraredRemoteButton["BottomLeft"] = 4] = "BottomLeft"; //% block="top right" InfraredRemoteButton[InfraredRemoteButton["TopRight"] = 8] = "TopRight"; //% block="bottom right" InfraredRemoteButton[InfraredRemoteButton["BottomRight"] = 16] = "BottomRight"; })(InfraredRemoteButton = pxsim.InfraredRemoteButton || (pxsim.InfraredRemoteButton = {})); class RemoteState { constructor() { this.state = 0; } unmapButtons() { switch (this.state) { case InfraredRemoteButton.TopLeft: return 1; case InfraredRemoteButton.BottomLeft: return 2; case InfraredRemoteButton.TopRight: return 3; case InfraredRemoteButton.BottomRight: return 4; case InfraredRemoteButton.TopLeft | InfraredRemoteButton.TopRight: return 5; case InfraredRemoteButton.TopLeft | InfraredRemoteButton.BottomRight: return 6; case InfraredRemoteButton.BottomLeft | InfraredRemoteButton.TopRight: return 7; case InfraredRemoteButton.BottomLeft | InfraredRemoteButton.BottomRight: return 8; case InfraredRemoteButton.CenterBeacon: return 9; case InfraredRemoteButton.BottomLeft | InfraredRemoteButton.TopLeft: return 10; case InfraredRemoteButton.TopRight | InfraredRemoteButton.BottomRight: return 11; default: return 0; } } setPressed(btns, down) { if (down) this.state = this.state | btns; else this.state = ~(~this.state | btns); } } pxsim.RemoteState = RemoteState; let InfraredSensorMode; (function (InfraredSensorMode) { InfraredSensorMode[InfraredSensorMode["None"] = -1] = "None"; InfraredSensorMode[InfraredSensorMode["Proximity"] = 0] = "Proximity"; InfraredSensorMode[InfraredSensorMode["Seek"] = 1] = "Seek"; InfraredSensorMode[InfraredSensorMode["RemoteControl"] = 2] = "RemoteControl"; })(InfraredSensorMode = pxsim.InfraredSensorMode || (pxsim.InfraredSensorMode = {})); class InfraredSensorNode extends pxsim.UartSensorNode { constructor(port) { super(port); this.id = pxsim.NodeType.InfraredSensor; this.proximity = 50; // [0..100] } getDeviceType() { return 33 /* DAL.DEVICE_TYPE_IR */; } setPromixity(proximity) { if (this.proximity != proximity) { this.proximity = proximity; this.setChangedState(); } } getValue() { switch (this.mode) { case InfraredSensorMode.Proximity: return this.proximity; case InfraredSensorMode.RemoteControl: return pxsim.ev3board().remoteState.unmapButtons(); default: return 0; } } } pxsim.InfraredSensorNode = InfraredSensorNode; })(pxsim || (pxsim = {})); var lf = pxsim.localization.lf; var pxsim; (function (pxsim) { var motors; (function (motors) { function portsToString(out) { let r = ""; for (let i = 0; i < 4 /* DAL.NUM_OUTPUTS */; ++i) { if (out & (1 << i)) { if (r.length > 0) r += "+"; r += "ABCD"[i]; } } return r; } function __motorUsed(ports, large) { //console.log("MOTOR INIT " + port); if (pxsim.ev3board().motorUsed(ports, large)) pxsim.runtime.queueDisplayUpdate(); else pxsim.U.userError(`${lf("Multiple motors are connected to Port")} ${portsToString(ports)}`); } motors.__motorUsed = __motorUsed; })(motors = pxsim.motors || (pxsim.motors = {})); })(pxsim || (pxsim = {})); (function (pxsim) { var sensors; (function (sensors) { function __sensorUsed(port, type) { //console.log("SENSOR INIT " + port + ", type: " + type); if (type == 100 /* DAL.DEVICE_TYPE_IIC_UNKNOWN */) return; // Ignore IIC if (!pxsim.ev3board().hasSensor(port)) { const sensor = pxsim.ev3board().getSensor(port, type); pxsim.runtime.queueDisplayUpdate(); } else { pxsim.U.userError(`${lf("Multiple sensors are connected to Port")} ${port + 1}`); } } sensors.__sensorUsed = __sensorUsed; })(sensors = pxsim.sensors || (pxsim.sensors = {})); })(pxsim || (pxsim = {})); var pxsim; (function (pxsim) { class EV3LightState { constructor() { this.lightPattern = 0; } } pxsim.EV3LightState = EV3LightState; })(pxsim || (pxsim = {})); (function (pxsim) { var output; (function (output) { function setLights(pattern) { const brickState = pxsim.ev3board().getBrickNode(); const lightState = brickState.lightState; if (lightState.lightPattern != pattern) { lightState.lightPattern = pattern; brickState.setChangedState(); } } output.setLights = setLights; })(output = pxsim.output || (pxsim.output = {})); })(pxsim || (pxsim = {})); var pxsim; (function (pxsim) { let MotorDataOff; (function (MotorDataOff) { MotorDataOff[MotorDataOff["TachoCounts"] = 0] = "TachoCounts"; MotorDataOff[MotorDataOff["Speed"] = 4] = "Speed"; MotorDataOff[MotorDataOff["Padding"] = 5] = "Padding"; MotorDataOff[MotorDataOff["TachoSensor"] = 8] = "TachoSensor"; MotorDataOff[MotorDataOff["Size"] = 12] = "Size"; })(MotorDataOff || (MotorDataOff = {})); class EV3MotorState { constructor() { let data = new Uint8Array(12 * 4 /* DAL.NUM_OUTPUTS */); pxsim.MMapMethods.register("/dev/lms_motor", { data, beforeMemRead: () => { const outputs = pxsim.ev3board().outputNodes; // console.log("motor before read"); for (let port = 0; port < 4 /* DAL.NUM_OUTPUTS */; ++port) { const output = outputs[port]; const speed = output ? outputs[port].getSpeed() : 0; const angle = output ? outputs[port].getAngle() : 0; const tci = MotorDataOff.TachoCounts + port * MotorDataOff.Size; const tsi = MotorDataOff.TachoSensor + port * MotorDataOff.Size; data[tci] = data[tci + 1] = data[tci + 2] = data[tci + 3] = 0; // Tacho count data[MotorDataOff.Speed + port * MotorDataOff.Size] = speed; // Speed data[tsi] = angle & 0xff; // Count data[tsi + 1] = (angle >> 8) & 0xff; // Count data[tsi + 2] = (angle >> 16) & 0xff; // Count data[tsi + 3] = (angle >> 24); // Count } }, read: buf => { let v = "vSIM"; for (let i = 0; i < buf.data.length; ++i) buf.data[i] = v.charCodeAt(i) || 0; return buf.data.length; }, write: buf => { if (buf.data.length == 0) return 2; const cmd = buf.data[0]; return 2; }, ioctl: (id, buf) => { return 2; } }); } } pxsim.EV3MotorState = EV3MotorState; })(pxsim || (pxsim = {})); var pxsim; (function (pxsim) { const MIN_RAMP_SPEED = 3; class MotorNode extends pxsim.BaseNode { constructor(port, large) { super(port); this.isOutput = true; // current state this.angle = 0; this.tacho = 0; this.speed = 0; this.manualReferenceAngle = undefined; this.manualAngle = undefined; this.setLarge(large); } isReady() { return !this.speedCmd; } getSpeed() { return Math.round(this.speed); } getAngle() { return Math.round(this.angle); } // returns the secondary motor if any getSynchedMotor() { return this._synchedMotor; } setSpeedCmd(cmd, values) { if (this.speedCmd != cmd || JSON.stringify(this.speedCmdValues) != JSON.stringify(values)) this.setChangedState(); // new command TODO: values this.speedCmd = cmd; this.speedCmdValues = values; this.speedCmdTacho = this.tacho; this.speedCmdTime = pxsim.U.now(); delete this._synchedMotor; } setSyncCmd(motor, cmd, values) { this.setSpeedCmd(cmd, values); this._synchedMotor = motor; } clearSpeedCmd() { delete this.speedCmd; delete this.speedCmdValues; delete this._synchedMotor; this.setChangedState(); } clearSyncCmd() { if (this._synchedMotor) this.clearSpeedCmd(); } setLarge(large) { this.id = large ? pxsim.NodeType.LargeMotor : pxsim.NodeType.MediumMotor; // large 170 rpm (https://education.lego.com/en-us/products/ev3-large-servo-motor/45502) this.rotationsPerMilliSecond = (large ? 170 : 250) / 60000; } isLarge() { return this.id == pxsim.NodeType.LargeMotor; } reset() { // not sure what reset does... } clearCount() { this.tacho = 0; this.angle = 0; } stop() { this.started = false; this.clearSpeedCmd(); } start() { this.started = true; } manualMotorDown() { this.manualReferenceAngle = this.angle; this.manualAngle = 0; } // position: 0, 360 manualMotorAngle(angle) { this.manualAngle = angle; } manualMotorUp() { delete this.manualReferenceAngle; delete this.manualAngle; } updateState(elapsed) { //console.log(`motor: ${elapsed}ms - ${this.speed}% - ${this.angle}> - ${this.tacho}|`) const interval = Math.min(20, elapsed); let t = 0; while (t < elapsed) { let dt = interval; if (t + dt > elapsed) dt = elapsed - t; this.updateStateStep(dt); t += dt; } } updateStateStep(elapsed) { if (this.manualAngle === undefined) { // compute new speed switch (this.speedCmd) { case 165 /* DAL.opOutputSpeed */: case 164 /* DAL.opOutputPower */: // assume power == speed // TODO: PID this.speed = this.speedCmdValues[0]; break; case 175 /* DAL.opOutputTimeSpeed */: case 173 /* DAL.opOutputTimePower */: case 172 /* DAL.opOutputStepPower */: case 174 /* DAL.opOutputStepSpeed */: { // ramp up, run, ramp down, <brake> using time const speed = this.speedCmdValues[0]; const step1 = this.speedCmdValues[1]; const step2 = this.speedCmdValues[2]; const step3 = this.speedCmdValues[3]; const brake = this.speedCmdValues[4]; const isTimeCommand = this.speedCmd == 173 /* DAL.opOutputTimePower */ || this.speedCmd == 175 /* DAL.opOutputTimeSpeed */; const dstep = isTimeCommand ? pxsim.U.now() - this.speedCmdTime : this.tacho - this.speedCmdTacho; if (step1 && dstep < step1) { // rampup this.speed = speed * dstep / step1; // ensure non-zero speed this.speed = Math.max(MIN_RAMP_SPEED, Math.ceil(Math.abs(this.speed))) * Math.sign(speed); } else if (dstep < step1 + step2) // run this.speed = speed; else if (step2 && dstep < step1 + step2 + step3) { this.speed = speed * (step1 + step2 + step3 - dstep) / (step1 + step2 + step3) + 5; // ensure non-zero speed this.speed = Math.max(MIN_RAMP_SPEED, Math.ceil(Math.abs(this.speed))) * Math.sign(speed); } else { if (brake) this.speed = 0; if (!isTimeCommand) { // we need to patch the actual position of the motor when // finishing the move as our integration step introduce errors const deltaAngle = -Math.sign(speed) * (dstep - (step1 + step2 + step3)); if (deltaAngle) { this.angle += deltaAngle; this.tacho -= Math.abs(deltaAngle); this.setChangedState(); } } this.clearSpeedCmd(); } break; } case 176 /* DAL.opOutputStepSync */: case 177 /* DAL.opOutputTimeSync */: { const otherMotor = this._synchedMotor; const speed = this.speedCmdValues[0]; const turnRatio = this.speedCmdValues[1]; // if turnratio is negative, right motor at power level // right motor -> this.port > otherMotor.port if (Math.sign(this.port - otherMotor.port) == Math.sign(turnRatio)) break; // handled in other motor code const stepsOrTime = this.speedCmdValues[2]; const brake = this.speedCmdValues[3]; const dstep = this.speedCmd == 177 /* DAL.opOutputTimeSync */ ? pxsim.U.now() - this.speedCmdTime : this.tacho - this.speedCmdTacho; // 0 is special case, run infinite if (!stepsOrTime || dstep < stepsOrTime) this.speed = speed; else { if (brake) this.speed = 0; this.clearSpeedCmd(); } // turn ratio is a bit weird to interpret // see https://communities.theiet.org/blogs/698/1706 otherMotor.speed = this.speed * (100 - Math.abs(turnRatio)) / 100; // clamp this.speed = Math.max(-100, Math.min(100, this.speed >> 0)); otherMotor.speed = Math.max(-100, Math.min(100, otherMotor.speed >> 0)); ; // stop other motor if needed if (!this._synchedMotor) otherMotor.clearSpeedCmd(); break; } } } else { // the user is holding the handle - so position is the angle this.speed = 0; // rotate by the desired angle change this.angle = this.manualReferenceAngle + this.manualAngle; this.setChangedState(); } // don't round speed // compute delta angle const rotations = this.speed / 100 * this.rotationsPerMilliSecond * elapsed; const deltaAngle = rotations * 360; if (deltaAngle) { this.angle += deltaAngle; this.tacho += Math.abs(deltaAngle); this.setChangedState(); } // if the motor was stopped or there are no speed commands, // let it coast to speed 0 if ((this.manualReferenceAngle === undefined) && this.speed && !(this.started || this.speedCmd)) { // decay speed 5% per tick this.speed = Math.round(Math.max(0, Math.abs(this.speed) - 10) * pxsim.sign(this.speed)); } } } pxsim.MotorNode = MotorNode; })(pxsim || (pxsim = {})); (function (pxsim) { // A re-implementation of Math.sign (since IE11 doesn't support it) function sign(num) { return num ? num < 0 ? -1 : 1 : 0; } pxsim.sign = sign; })(pxsim || (pxsim = {})); /// <reference path="./sensor.ts"/> var pxsim; (function (pxsim) { let NXTLightSensorMode; (function (NXTLightSensorMode) { NXTLightSensorMode[NXTLightSensorMode["None"] = -1] = "None"; NXTLightSensorMode[NXTLightSensorMode["ReflectedLightRaw"] = 0] = "ReflectedLightRaw"; NXTLightSensorMode[NXTLightSensorMode["ReflectedLight"] = 1] = "ReflectedLight"; NXTLightSensorMode[NXTLightSensorMode["AmbientLightRaw"] = 2] = "AmbientLightRaw"; NXTLightSensorMode[NXTLightSensorMode["AmbientLight"] = 3] = "AmbientLight"; })(NXTLightSensorMode = pxsim.NXTLightSensorMode || (pxsim.NXTLightSensorMode = {})); class NXTLightSensorNode extends pxsim.AnalogSensorNode { constructor(port) { super(port); this.id = pxsim.NodeType.NXTLightSensor; this.value = 0; this.darkReflectedLight = 3372; this.brightReflectedLight = 445; this.darkAmbientLight = 3411; this.brightAmbientLight = 633; this.mode = -1; } getDeviceType() { return 2 /* DAL.DEVICE_TYPE_NXT_LIGHT */; } setValue(value) { this.value = value; this.setChangedState(); } getValue() { return this.value; } setMode(mode) { this.mode = mode; if (this.mode == NXTLightSensorMode.ReflectedLight) this.value = 1908; else if (this.mode == NXTLightSensorMode.AmbientLight) this.value = 2022; else this.value = 2048; this.changed = true; this.modeChanged = true; } getAnalogReadPin() { return pxsim.AnalogOff.InPin1; } isNXT() { return true; } } pxsim.NXTLightSensorNode = NXTLightSensorNode; })(pxsim || (pxsim = {})); var pxsim; (function (pxsim) { class EV3OutputState { constructor() { let data = new Uint8Array(10); pxsim.MMapMethods.register("/dev/lms_pwm", { data, beforeMemRead: () => { //console.log("pwm before read"); for (let i = 0; i < 10; ++i) data[i] = 0; }, read: buf => { // console.log("pwm read"); if (buf.data.length == 0) return 2; const cmd = buf.data[0]; switch (cmd) { case 169 /* DAL.opOutputTest */: const port = buf.data[1]; let r = 0; pxsim.ev3board().getMotor(port) .filter(motor => !motor.isReady()) .forEach(motor => r |= (1 << motor.port)); pxsim.BufferMethods.setNumber(buf, pxsim.BufferMethods.NumberFormat.UInt8LE, 2, r); break; default: let v = "vSIM"; for (let i = 0; i < buf.data.length; ++i) buf.data[i] = v.charCodeAt(i) || 0; break; } return buf.data.length; }, write: buf => { if (buf.data.length == 0) return 2; const cmd = buf.data[0]; switch (cmd) { case 3 /* DAL.opProgramStart */: { // init return 2; } case 162 /* DAL.opOutputReset */: { // reset const port = buf.data[1]; const motors = pxsim.ev3board().getMotor(port); motors.forEach(motor => motor.reset()); return 2; } case 178 /* DAL.opOutputClearCount */: const port = buf.data[1]; const motors = pxsim.ev3board().getMotor(port); motors.forEach(motor => motor.clearCount()); break; case 172 /* DAL.opOutputStepPower */: case 174 /* DAL.opOutputStepSpeed */: case 173 /* DAL.opOutputTimePower */: case 175 /* DAL.opOutputTimeSpeed */: { // step speed const port = buf.data[1]; const speed = pxsim.BufferMethods.getNumber(buf, pxsim.BufferMethods.NumberFormat.Int8LE, 2); // signed byte // note that b[3] is padding const step1 = pxsim.BufferMethods.getNumber(buf, pxsim.BufferMethods.NumberFormat.Int32LE, 4); const step2 = pxsim.BufferMethods.getNumber(buf, pxsim.BufferMethods.NumberFormat.Int32LE, 8); const step3 = pxsim.BufferMethods.getNumber(buf, pxsim.BufferMethods.NumberFormat.Int32LE, 12); const brake = pxsim.BufferMethods.getNumber(buf, pxsim.BufferMethods.NumberFormat.Int8LE, 16); //console.log(buf); const motors = pxsim.ev3board().getMotor(port); motors.forEach(motor => motor.setSpeedCmd(cmd, [speed, step1, step2, step3, brake])); return 2; } case 176 /* DAL.opOutputStepSync */: case 177 /* DAL.opOutputTimeSync */: { const port = buf.data[1]; const speed = pxsim.BufferMethods.getNumber(buf, pxsim.BufferMethods.NumberFormat.Int8LE, 2); // signed byte // note that b[3] is padding const turnRatio = pxsim.BufferMethods.getNumber(buf, pxsim.BufferMethods.NumberFormat.Int16LE, 4); // b[6], b[7] is padding const stepsOrTime = pxsim.BufferMethods.getNumber(buf, pxsim.BufferMethods.NumberFormat.Int32LE, 8); const brake = pxsim.BufferMethods.getNumber(buf, pxsim.BufferMethods.NumberFormat.Int8LE, 12); const motors = pxsim.ev3board().getMotor(port); // cancel any other sync command for (const motor of pxsim.ev3board().getMotors().filter(motor => motors.indexOf(motor) < 0)) { motor.clearSyncCmd(); } // apply commands to all motors for (const motor of motors) { const otherMotor = motors.filter(m => m.port != motor.port)[0]; motor.setSyncCmd(otherMotor, cmd, [speed, turnRatio, stepsOrTime, brake]); } return 2; } case 163 /* DAL.opOutputStop */: { // stop const port = buf.data[1]; const brake = pxsim.BufferMethods.getNumber(buf, pxsim.BufferMethods.NumberFormat.Int8LE, 2); const motors = pxsim.ev3board().getMotor(port); motors.forEach(motor => motor.stop()); return 2; } case 164 /* DAL.opOutputPower */: case 165 /* DAL.opOutputSpeed */: { // setSpeed const port = buf.data[1]; const speed = pxsim.BufferMethods.getNumber(buf, pxsim.BufferMethods.NumberFormat.Int8LE, 2); const motors = pxsim.ev3board().getMotor(port); motors.forEach(motor => motor.setSpeedCmd(cmd, [speed])); return 2; } case 166 /* DAL.opOutputStart */: { // start const port = buf.data[1]; const motors = pxsim.ev3board().getMotor(port); motors.forEach(motor => motor.start()); return 2; } case 167 /* DAL.opOutputPolarity */: { console.error("opOutputPolarity not supported"); return 2; } case 161 /* DAL.opOutputSetType */: { const portIndex = buf.data[1]; // not a port but a port index 0..3 const large = buf.data[2] == 0x07; const motor = pxsim.ev3board().getMotors()[portIndex]; if (motor) motor.setLarge(large);