UNPKG

pxt-maker

Version:
1,257 lines (1,256 loc) 219 kB
var pxsim; (function (pxsim) { var input; (function (input) { function onGesture(gesture, handler) { let b = pxsim.accelerometer(); b.accelerometer.activate(); if (gesture == 11 /* DAL.ACCELEROMETER_EVT_SHAKE */ && !b.useShake) { b.useShake = true; pxsim.runtime.queueDisplayUpdate(); } pxsim.pxtcore.registerWithDal(13 /* DAL.DEVICE_ID_GESTURE */, gesture, handler); } input.onGesture = onGesture; function rotation(kind) { let b = pxsim.accelerometer(); let acc = b.accelerometer; acc.activate(); let x = acc.getX(pxsim.MicroBitCoordinateSystem.NORTH_EAST_DOWN); let y = acc.getY(pxsim.MicroBitCoordinateSystem.NORTH_EAST_DOWN); let z = acc.getZ(pxsim.MicroBitCoordinateSystem.NORTH_EAST_DOWN); let roll = Math.atan2(y, z); let pitch = Math.atan(-x / (y * Math.sin(roll) + z * Math.cos(roll))); let r = 0; switch (kind) { case 0: r = pitch; break; case 1: r = roll; break; } return Math.floor(r / Math.PI * 180); } input.rotation = rotation; function setAccelerometerRange(range) { let b = pxsim.accelerometer(); b.accelerometer.setSampleRange(range); } input.setAccelerometerRange = setAccelerometerRange; function acceleration(dimension) { let b = pxsim.accelerometer(); let acc = b.accelerometer; acc.activate(); switch (dimension) { case 0: return acc.getX(); case 1: return acc.getY(); case 2: return acc.getZ(); default: return Math.floor(Math.sqrt(acc.instantaneousAccelerationSquared())); } } input.acceleration = acceleration; })(input = pxsim.input || (pxsim.input = {})); })(pxsim || (pxsim = {})); (function (pxsim) { /** * Co-ordinate systems that can be used. * RAW: Unaltered data. Data will be returned directly from the accelerometer. * * SIMPLE_CARTESIAN: Data will be returned based on an easy to understand alignment, consistent with the cartesian system taught in schools. * When held upright, facing the user: * * / * +--------------------+ z * | | * | ..... | * | * ..... * | * ^ | ..... | * | | | * y +--------------------+ x--> * * * NORTH_EAST_DOWN: Data will be returned based on the industry convention of the North East Down (NED) system. * When held upright, facing the user: * * z * +--------------------+ / * | | * | ..... | * | * ..... * | * ^ | ..... | * | | | * x +--------------------+ y--> * */ let MicroBitCoordinateSystem; (function (MicroBitCoordinateSystem) { MicroBitCoordinateSystem[MicroBitCoordinateSystem["RAW"] = 0] = "RAW"; MicroBitCoordinateSystem[MicroBitCoordinateSystem["SIMPLE_CARTESIAN"] = 1] = "SIMPLE_CARTESIAN"; MicroBitCoordinateSystem[MicroBitCoordinateSystem["NORTH_EAST_DOWN"] = 2] = "NORTH_EAST_DOWN"; })(MicroBitCoordinateSystem = pxsim.MicroBitCoordinateSystem || (pxsim.MicroBitCoordinateSystem = {})); class Accelerometer { constructor(runtime) { this.runtime = runtime; this.sigma = 0; // the number of ticks that the instantaneous gesture has been stable. this.lastGesture = 0; // the last, stable gesture recorded. this.currentGesture = 0; // the instantaneous, unfiltered gesture detected. this.sample = { x: 0, y: 0, z: -1023 }; this.shake = { x: false, y: false, z: false, count: 0, shaken: 0, timer: 0 }; // State information needed to detect shake events. this.isActive = false; this.sampleRange = 2; this.id = 5 /* DAL.DEVICE_ID_ACCELEROMETER */; } setSampleRange(range) { this.activate(); this.sampleRange = Math.max(1, Math.min(8, range)); } activate() { if (!this.isActive) { this.isActive = true; this.runtime.queueDisplayUpdate(); } } /** * Reads the acceleration data from the accelerometer, and stores it in our buffer. * This is called by the tick() member function, if the interrupt is set! */ update(x, y, z) { // read MSB values... this.sample.x = Math.floor(x); this.sample.y = Math.floor(y); this.sample.z = Math.floor(z); // Update gesture tracking this.updateGesture(); // Indicate that a new sample is available pxsim.board().bus.queue(this.id, 1 /* DAL.ACCELEROMETER_EVT_DATA_UPDATE */); } instantaneousAccelerationSquared() { // Use pythagoras theorem to determine the combined force acting on the device. return this.sample.x * this.sample.x + this.sample.y * this.sample.y + this.sample.z * this.sample.z; } /** * Service function. Determines the best guess posture of the device based on instantaneous data. * This makes no use of historic data (except for shake), and forms this input to the filter implemented in updateGesture(). * * @return A best guess of the current posture of the device, based on instantaneous data. */ instantaneousPosture() { let force = this.instantaneousAccelerationSquared(); let shakeDetected = false; // Test for shake events. // We detect a shake by measuring zero crossings in each axis. In other words, if we see a strong acceleration to the left followed by // a string acceleration to the right, then we can infer a shake. Similarly, we can do this for each acxis (left/right, up/down, in/out). // // If we see enough zero crossings in succession (MICROBIT_ACCELEROMETER_SHAKE_COUNT_THRESHOLD), then we decide that the device // has been shaken. if ((this.getX() < -400 /* DAL.ACCELEROMETER_SHAKE_TOLERANCE */ && this.shake.x) || (this.getX() > 400 /* DAL.ACCELEROMETER_SHAKE_TOLERANCE */ && !this.shake.x)) { shakeDetected = true; this.shake.x = !this.shake.x; } if ((this.getY() < -400 /* DAL.ACCELEROMETER_SHAKE_TOLERANCE */ && this.shake.y) || (this.getY() > 400 /* DAL.ACCELEROMETER_SHAKE_TOLERANCE */ && !this.shake.y)) { shakeDetected = true; this.shake.y = !this.shake.y; } if ((this.getZ() < -400 /* DAL.ACCELEROMETER_SHAKE_TOLERANCE */ && this.shake.z) || (this.getZ() > 400 /* DAL.ACCELEROMETER_SHAKE_TOLERANCE */ && !this.shake.z)) { shakeDetected = true; this.shake.z = !this.shake.z; } if (shakeDetected && this.shake.count < 4 /* DAL.ACCELEROMETER_SHAKE_COUNT_THRESHOLD */ && ++this.shake.count == 4 /* DAL.ACCELEROMETER_SHAKE_COUNT_THRESHOLD */) this.shake.shaken = 1; if (++this.shake.timer >= 10 /* DAL.ACCELEROMETER_SHAKE_DAMPING */) { this.shake.timer = 0; if (this.shake.count > 0) { if (--this.shake.count == 0) this.shake.shaken = 0; } } if (this.shake.shaken) return 11 /* DAL.ACCELEROMETER_EVT_SHAKE */; let sq = (n) => n * n; if (force < sq(400 /* DAL.ACCELEROMETER_FREEFALL_TOLERANCE */)) return 7 /* DAL.ACCELEROMETER_EVT_FREEFALL */; if (force > sq(3072 /* DAL.ACCELEROMETER_3G_TOLERANCE */)) return 8 /* DAL.ACCELEROMETER_EVT_3G */; if (force > sq(6144 /* DAL.ACCELEROMETER_6G_TOLERANCE */)) return 9 /* DAL.ACCELEROMETER_EVT_6G */; if (force > sq(8192 /* DAL.ACCELEROMETER_8G_TOLERANCE */)) return 10 /* DAL.ACCELEROMETER_EVT_8G */; // Determine our posture. if (this.getX() < (-1000 + 200 /* DAL.ACCELEROMETER_TILT_TOLERANCE */)) return 3 /* DAL.ACCELEROMETER_EVT_TILT_LEFT */; if (this.getX() > (1000 - 200 /* DAL.ACCELEROMETER_TILT_TOLERANCE */)) return 4 /* DAL.ACCELEROMETER_EVT_TILT_RIGHT */; if (this.getY() < (-1000 + 200 /* DAL.ACCELEROMETER_TILT_TOLERANCE */)) return 1 /* DAL.ACCELEROMETER_EVT_TILT_UP */; if (this.getY() > (1000 - 200 /* DAL.ACCELEROMETER_TILT_TOLERANCE */)) return 2 /* DAL.ACCELEROMETER_EVT_TILT_DOWN */; if (this.getZ() < (-1000 + 200 /* DAL.ACCELEROMETER_TILT_TOLERANCE */)) return 5 /* DAL.ACCELEROMETER_EVT_FACE_UP */; if (this.getZ() > (1000 - 200 /* DAL.ACCELEROMETER_TILT_TOLERANCE */)) return 6 /* DAL.ACCELEROMETER_EVT_FACE_DOWN */; return 0; } updateGesture() { // Determine what it looks like we're doing based on the latest sample... let g = this.instantaneousPosture(); // Perform some low pass filtering to reduce jitter from any detected effects if (g == this.currentGesture) { if (this.sigma < 5 /* DAL.ACCELEROMETER_GESTURE_DAMPING */) this.sigma++; } else { this.currentGesture = g; this.sigma = 0; } // If we've reached threshold, update our record and raise the relevant event... if (this.currentGesture != this.lastGesture && this.sigma >= 5 /* DAL.ACCELEROMETER_GESTURE_DAMPING */) { this.lastGesture = this.currentGesture; pxsim.board().bus.queue(13 /* DAL.DEVICE_ID_GESTURE */, this.lastGesture); } } /** * Reads the X axis value of the latest update from the accelerometer. * @param system The coordinate system to use. By default, a simple cartesian system is provided. * @return The force measured in the X axis, in milli-g. * * Example: * @code * uBit.accelerometer.getX(); * uBit.accelerometer.getX(RAW); * @endcode */ getX(system = MicroBitCoordinateSystem.SIMPLE_CARTESIAN) { this.activate(); let val; switch (system) { case MicroBitCoordinateSystem.SIMPLE_CARTESIAN: val = -this.sample.x; case MicroBitCoordinateSystem.NORTH_EAST_DOWN: val = this.sample.y; //case MicroBitCoordinateSystem.SIMPLE_CARTESIAN.RAW: default: val = this.sample.x; } return pxsim.board().invertAccelerometerXAxis ? val * -1 : val; } /** * Reads the Y axis value of the latest update from the accelerometer. * @param system The coordinate system to use. By default, a simple cartesian system is provided. * @return The force measured in the Y axis, in milli-g. * * Example: * @code * uBit.accelerometer.getY(); * uBit.accelerometer.getY(RAW); * @endcode */ getY(system = MicroBitCoordinateSystem.SIMPLE_CARTESIAN) { this.activate(); let val; switch (system) { case MicroBitCoordinateSystem.SIMPLE_CARTESIAN: val = -this.sample.y; case MicroBitCoordinateSystem.NORTH_EAST_DOWN: val = -this.sample.x; //case RAW: default: val = this.sample.y; } return pxsim.board().invertAccelerometerYAxis ? val * -1 : val; } /** * Reads the Z axis value of the latest update from the accelerometer. * @param system The coordinate system to use. By default, a simple cartesian system is provided. * @return The force measured in the Z axis, in milli-g. * * Example: * @code * uBit.accelerometer.getZ(); * uBit.accelerometer.getZ(RAW); * @endcode */ getZ(system = MicroBitCoordinateSystem.SIMPLE_CARTESIAN) { this.activate(); let val; switch (system) { case MicroBitCoordinateSystem.NORTH_EAST_DOWN: val = -this.sample.z; //case MicroBitCoordinateSystem.SIMPLE_CARTESIAN: //case MicroBitCoordinateSystem.RAW: default: val = this.sample.z; } return pxsim.board().invertAccelerometerZAxis ? val * -1 : val; } /** * Provides a rotation compensated pitch of the device, based on the latest update from the accelerometer. * @return The pitch of the device, in degrees. * * Example: * @code * uBit.accelerometer.getPitch(); * @endcode */ getPitch() { this.activate(); return Math.floor((360 * this.getPitchRadians()) / (2 * Math.PI)); } getPitchRadians() { this.recalculatePitchRoll(); return this.pitch; } /** * Provides a rotation compensated roll of the device, based on the latest update from the accelerometer. * @return The roll of the device, in degrees. * * Example: * @code * uBit.accelerometer.getRoll(); * @endcode */ getRoll() { this.activate(); return Math.floor((360 * this.getRollRadians()) / (2 * Math.PI)); } getRollRadians() { this.recalculatePitchRoll(); return this.roll; } /** * Recalculate roll and pitch values for the current sample. * We only do this at most once per sample, as the necessary trigonemteric functions are rather * heavyweight for a CPU without a floating point unit... */ recalculatePitchRoll() { let x = this.getX(MicroBitCoordinateSystem.NORTH_EAST_DOWN); let y = this.getY(MicroBitCoordinateSystem.NORTH_EAST_DOWN); let z = this.getZ(MicroBitCoordinateSystem.NORTH_EAST_DOWN); this.roll = Math.atan2(y, z); this.pitch = Math.atan(-x / (y * Math.sin(this.roll) + z * Math.cos(this.roll))); } } pxsim.Accelerometer = Accelerometer; class AccelerometerState { constructor(runtime) { this.useShake = false; this.tiltDecayer = 0; this.accelerometer = new Accelerometer(runtime); } attachEvents(element) { this.element = element; this.tiltDecayer = 0; this.element.addEventListener(pxsim.pointerEvents.move, (ev) => { if (!this.accelerometer.isActive) return; if (this.tiltDecayer) { clearInterval(this.tiltDecayer); this.tiltDecayer = 0; } let bbox = element.getBoundingClientRect(); let ax = (ev.clientX - bbox.width / 2) / (bbox.width / 3); let ay = (ev.clientY - bbox.height / 2) / (bbox.height / 3); let x = -Math.max(-1023, Math.min(1023, Math.floor(ax * 1023))); let y = Math.max(-1023, Math.min(1023, Math.floor(ay * 1023))); let z2 = 1023 * 1023 - x * x - y * y; let z = Math.floor((z2 > 0 ? -1 : 1) * Math.sqrt(Math.abs(z2))); this.accelerometer.update(-x, y, z); this.updateTilt(); }, false); this.element.addEventListener(pxsim.pointerEvents.leave, (ev) => { if (!this.accelerometer.isActive) return; if (!this.tiltDecayer) { this.tiltDecayer = setInterval(() => { let accx = this.accelerometer.getX(); accx = Math.floor(Math.abs(accx) * 0.85) * (accx > 0 ? 1 : -1); let accy = this.accelerometer.getY(); accy = Math.floor(Math.abs(accy) * 0.85) * (accy > 0 ? 1 : -1); let accz = -Math.sqrt(Math.max(0, 1023 * 1023 - accx * accx - accy * accy)); if (Math.abs(accx) <= 24 && Math.abs(accy) <= 24) { clearInterval(this.tiltDecayer); this.tiltDecayer = 0; accx = 0; accy = 0; accz = -1023; } this.accelerometer.update(accx, accy, accz); this.updateTilt(); }, 50); } }, false); } updateTilt() { if (!this.accelerometer.isActive || !this.element) return; const x = this.accelerometer.getX(); const y = this.accelerometer.getY(); const af = 8 / 1023; const s = 1 - Math.min(0.1, Math.pow(Math.max(Math.abs(x), Math.abs(y)) / 1023, 2) / 35); this.element.style.transform = `perspective(30em) rotateX(${y * af}deg) rotateY(${x * af}deg) scale(${s}, ${s})`; this.element.style.perspectiveOrigin = "50% 50% 50%"; this.element.style.perspective = "30em"; } } pxsim.AccelerometerState = AccelerometerState; })(pxsim || (pxsim = {})); var pxsim; (function (pxsim) { function accelerometer() { return pxsim.board().accelerometerState; } pxsim.accelerometer = accelerometer; })(pxsim || (pxsim = {})); var pxsim; (function (pxsim) { var pxtcore; (function (pxtcore) { function getPin(id) { const b = pxsim.board(); if (b && b.edgeConnectorState) return b.edgeConnectorState.getPin(id); return undefined; } pxtcore.getPin = getPin; function lookupPinCfg(key) { return getPinCfg(key); } pxtcore.lookupPinCfg = lookupPinCfg; function getPinCfg(key) { return getPin(pxtcore.getConfig(key, -1)); } pxtcore.getPinCfg = getPinCfg; })(pxtcore = pxsim.pxtcore || (pxsim.pxtcore = {})); })(pxsim || (pxsim = {})); var pxsim; (function (pxsim) { var pxtcore; (function (pxtcore) { // TODO: add in support for mode, as in CODAL function registerWithDal(id, evid, handler, mode = 0) { pxsim.board().bus.listen(id, evid, handler); } pxtcore.registerWithDal = registerWithDal; function deepSleep() { // TODO? console.log("deep sleep requested"); } pxtcore.deepSleep = deepSleep; })(pxtcore = pxsim.pxtcore || (pxsim.pxtcore = {})); })(pxsim || (pxsim = {})); (function (pxsim) { var BufferMethods; (function (BufferMethods) { function fnv1(data) { let h = 0x811c9dc5; for (let i = 0; i < data.length; ++i) { h = Math.imul(h, 0x1000193) ^ data[i]; } return h; } function hash(buf, bits) { bits |= 0; if (bits < 1) return 0; const h = fnv1(buf.data); if (bits >= 32) return h >>> 0; else return ((h ^ (h >>> bits)) & ((1 << bits) - 1)) >>> 0; } BufferMethods.hash = hash; })(BufferMethods = pxsim.BufferMethods || (pxsim.BufferMethods = {})); })(pxsim || (pxsim = {})); (function (pxsim) { var control; (function (control) { control.runInParallel = pxsim.thread.runInBackground; control.delay = pxsim.thread.pause; function reset() { pxsim.Runtime.postMessage({ type: "simulator", command: "restart", controlReset: true }); const cb = pxsim.getResume(); } control.reset = reset; function waitMicros(micros) { pxsim.thread.pause(micros / 1000); // it prempts not much we can do here. } control.waitMicros = waitMicros; function deviceName() { let b = pxsim.board(); return b && b.id ? b.id.slice(0, 4) : "abcd"; } control.deviceName = deviceName; function _ramSize() { return 32 * 1024 * 1024; } control._ramSize = _ramSize; function deviceSerialNumber() { let b = pxsim.board(); if (!b) return 42; let n = 0; if (b.id) { n = parseInt(b.id.slice(1)); if (isNaN(n)) { n = 0; for (let i = 0; i < b.id.length; ++i) { n = ((n << 5) - n) + b.id.charCodeAt(i); n |= 0; } n = Math.abs(n); } } if (!n) n = 42; return n; } control.deviceSerialNumber = deviceSerialNumber; function deviceLongSerialNumber() { let b = control.createBuffer(8); pxsim.BufferMethods.setNumber(b, pxsim.BufferMethods.NumberFormat.UInt32LE, 0, deviceSerialNumber()); return b; } control.deviceLongSerialNumber = deviceLongSerialNumber; function deviceDalVersion() { return "sim"; } control.deviceDalVersion = deviceDalVersion; function internalOnEvent(id, evid, handler) { pxsim.pxtcore.registerWithDal(id, evid, handler); } control.internalOnEvent = internalOnEvent; function waitForEvent(id, evid) { const cb = pxsim.getResume(); pxsim.board().bus.wait(id, evid, cb); } control.waitForEvent = waitForEvent; function allocateNotifyEvent() { let b = pxsim.board(); return b.bus.nextNotifyEvent++; } control.allocateNotifyEvent = allocateNotifyEvent; function raiseEvent(id, evid, mode) { // TODO mode? pxsim.board().bus.queue(id, evid); } control.raiseEvent = raiseEvent; function millis() { return pxsim.runtime.runningTime(); } control.millis = millis; function micros() { return pxsim.runtime.runningTimeUs() & 0x3fffffff; } control.micros = micros; function delayMicroseconds(us) { control.delay(us / 0.001); } control.delayMicroseconds = delayMicroseconds; function createBuffer(size) { return pxsim.BufferMethods.createBuffer(size); } control.createBuffer = createBuffer; function dmesg(msg) { console.log(`DMESG: ${msg}`); } control.dmesg = dmesg; function setDebugFlags(flags) { console.log(`debug flags: ${flags}`); } control.setDebugFlags = setDebugFlags; function heapSnapshot() { console.log(pxsim.runtime.traceObjects()); } control.heapSnapshot = heapSnapshot; function toStr(v) { if (v instanceof pxsim.RefRecord) { return `${v.vtable.name}@${v.id}`; } if (v instanceof pxsim.RefCollection) { let r = "["; for (let e of v.toArray()) { if (r.length > 200) { r += "..."; break; } r += toStr(e) + ", "; } r += "]"; return r; } if (typeof v == "function") { return (v + "").slice(0, 60) + "..."; } return v + ""; } function dmesgPtr(msg, ptr) { console.log(`DMESG: ${msg} ${toStr(ptr)}`); } control.dmesgPtr = dmesgPtr; function dmesgValue(ptr) { console.log(`DMESG: ${toStr(ptr)}`); } control.dmesgValue = dmesgValue; function gc() { } control.gc = gc; function profilingEnabled() { return !!pxsim.runtime.perfCounters; } control.profilingEnabled = profilingEnabled; function __log(priority, str) { switch (priority) { case 0: console.debug("d>" + str); break; case 1: console.log("l>" + str); break; case 2: console.warn("w>" + str); break; case 3: console.error("e>" + str); break; } pxsim.runtime.board.writeSerial(str); } control.__log = __log; function heapDump() { // TODO something better } control.heapDump = heapDump; function isUSBInitialized() { return false; } control.isUSBInitialized = isUSBInitialized; })(control = pxsim.control || (pxsim.control = {})); })(pxsim || (pxsim = {})); var pxsim; (function (pxsim) { var pxtcore; (function (pxtcore) { // general purpose message sending mechanism function sendMessage(channel, message, parentOnly) { if (!channel) return; pxsim.Runtime.postMessage({ type: "messagepacket", broadcast: !parentOnly, channel: channel, data: message && message.data }); } pxtcore.sendMessage = sendMessage; function peekMessageChannel() { const state = pxsim.getControlMessageState(); const msg = state && state.peek(); return msg && msg.channel; } pxtcore.peekMessageChannel = peekMessageChannel; function readMessageData() { const state = pxsim.getControlMessageState(); const msg = state && state.read(); return msg && new pxsim.RefBuffer(msg.data); } pxtcore.readMessageData = readMessageData; })(pxtcore = pxsim.pxtcore || (pxsim.pxtcore = {})); })(pxsim || (pxsim = {})); (function (pxsim) { // keep in sync with ts pxsim.CONTROL_MESSAGE_EVT_ID = 2999; pxsim.CONTROL_MESSAGE_RECEIVED = 1; class ControlMessageState { constructor(board) { this.board = board; this.messages = []; this.enabled = false; this.board.addMessageListener(msg => this.messageHandler(msg)); } messageHandler(msg) { if (msg.type == "messagepacket") { let packet = msg; this.enqueue(packet); } } enqueue(message) { this.messages.push(message); this.board.bus.queue(pxsim.CONTROL_MESSAGE_EVT_ID, pxsim.CONTROL_MESSAGE_RECEIVED); } peek() { return this.messages[0]; } read() { return this.messages.shift(); } } pxsim.ControlMessageState = ControlMessageState; function getControlMessageState() { return pxsim.board().controlMessageState; } pxsim.getControlMessageState = getControlMessageState; })(pxsim || (pxsim = {})); /// <reference path="../../../node_modules/pxt-core/built/pxtsim.d.ts" /> var pxsim; (function (pxsim) { function board() { return pxsim.runtime && pxsim.runtime.board; } pxsim.board = board; })(pxsim || (pxsim = {})); var pxsim; (function (pxsim) { var loops; (function (loops) { loops.pause = pxsim.thread.pause; loops.forever = pxsim.thread.forever; })(loops = pxsim.loops || (pxsim.loops = {})); })(pxsim || (pxsim = {})); /// <reference path="../../core/dal.d.ts"/> var pxsim; (function (pxsim) { const DOUBLE_CLICK_TIME = 500; class CommonButton extends pxsim.Button { constructor() { super(...arguments); this._pressedTime = -1; this._clickedTime = -1; } setPressed(p) { if (this.pressed === p) { return; } this.pressed = p; if (p) { this._wasPressed = true; pxsim.board().bus.queue(this.id, 1 /* DAL.DEVICE_BUTTON_EVT_DOWN */); this._pressedTime = pxsim.runtime.runningTime(); } else if (this._pressedTime !== -1) { pxsim.board().bus.queue(this.id, 2 /* DAL.DEVICE_BUTTON_EVT_UP */); const current = pxsim.runtime.runningTime(); if (current - this._pressedTime >= 1000 /* DAL.DEVICE_BUTTON_LONG_CLICK_TIME */) { pxsim.board().bus.queue(this.id, 4 /* DAL.DEVICE_BUTTON_EVT_LONG_CLICK */); } else { pxsim.board().bus.queue(this.id, 3 /* DAL.DEVICE_BUTTON_EVT_CLICK */); } if (this._clickedTime !== -1) { if (current - this._clickedTime <= DOUBLE_CLICK_TIME) { pxsim.board().bus.queue(this.id, 6 /* DAL.DEVICE_BUTTON_EVT_DOUBLE_CLICK */); } } this._clickedTime = current; } } wasPressed() { const temp = this._wasPressed; this._wasPressed = false; return temp; } pressureLevel() { // digital for now return this.isPressed() ? 512 : 0; } isPressed() { return this.pressed; } } pxsim.CommonButton = CommonButton; class CommonButtonState { constructor(buttons) { this.usesButtonAB = false; this.buttonsByPin = {}; this.buttons = buttons || [ new CommonButton(1 /* DAL.DEVICE_ID_BUTTON_A */), new CommonButton(2 /* DAL.DEVICE_ID_BUTTON_B */), new CommonButton(3 /* DAL.DEVICE_ID_BUTTON_AB */) ]; this.buttons.forEach(btn => this.buttonsByPin[btn.id] = btn); } } pxsim.CommonButtonState = CommonButtonState; })(pxsim || (pxsim = {})); (function (pxsim) { var pxtcore; (function (pxtcore) { function getButtonByPin(pinId) { let m = pxsim.board().buttonState.buttonsByPin; let b = m[pinId + ""]; if (!b) { b = m[pinId + ""] = new pxsim.CommonButton(pinId); } return b; } pxtcore.getButtonByPin = getButtonByPin; function getButtonByPinCfg(key) { return getButtonByPin(pxtcore.getConfig(key, -1)); } pxtcore.getButtonByPinCfg = getButtonByPinCfg; function getButton(buttonId) { const buttons = pxsim.board().buttonState.buttons; if (buttonId === 2) { pxsim.board().buttonState.usesButtonAB = true; pxsim.runtime.queueDisplayUpdate(); } if (buttonId < buttons.length && buttonId >= 0) { return buttons[buttonId]; } // panic return undefined; } pxtcore.getButton = getButton; })(pxtcore = pxsim.pxtcore || (pxsim.pxtcore = {})); })(pxsim || (pxsim = {})); (function (pxsim) { var ButtonMethods; (function (ButtonMethods) { function onEvent(button, ev, body) { pxsim.pxtcore.registerWithDal(button.id, ev, body); } ButtonMethods.onEvent = onEvent; function isPressed(button) { return button.pressed; } ButtonMethods.isPressed = isPressed; function pressureLevel(button) { return button.pressureLevel(); } ButtonMethods.pressureLevel = pressureLevel; function wasPressed(button) { return button.wasPressed(); } ButtonMethods.wasPressed = wasPressed; function id(button) { return button.id; } ButtonMethods.id = id; })(ButtonMethods = pxsim.ButtonMethods || (pxsim.ButtonMethods = {})); })(pxsim || (pxsim = {})); (function (pxsim) { var DigitalInOutPinMethods; (function (DigitalInOutPinMethods) { function pushButton(pin) { return pxsim.pxtcore.getButtonByPin(pin.id); } DigitalInOutPinMethods.pushButton = pushButton; })(DigitalInOutPinMethods = pxsim.DigitalInOutPinMethods || (pxsim.DigitalInOutPinMethods = {})); })(pxsim || (pxsim = {})); var pxsim; (function (pxsim) { var network; (function (network) { function cableSendPacket(buf) { const state = pxsim.getCableState(); state.send(buf); } network.cableSendPacket = cableSendPacket; function cablePacket() { const state = pxsim.getCableState(); return (state.packet); } network.cablePacket = cablePacket; function onCablePacket(body) { const state = pxsim.getCableState(); state.listen(body); } network.onCablePacket = onCablePacket; function onCableError(body) { const state = pxsim.getCableState(); state.listenError(body); } network.onCableError = onCableError; })(network = pxsim.network || (pxsim.network = {})); })(pxsim || (pxsim = {})); var pxsim; (function (pxsim) { class CableState { constructor() { // notify view that a packet was received this.packetReceived = false; // PULSE_IR_COMPONENT_ID = 0x2042; this.PULSE_CABLE_COMPONENT_ID = 0x2043; this.PULSE_PACKET_EVENT = 0x2; this.PULSE_PACKET_ERROR_EVENT = 0x3; } send(buf) { pxsim.Runtime.postMessage({ type: "irpacket", packet: buf.data }); } listen(body) { pxsim.pxtcore.registerWithDal(this.PULSE_CABLE_COMPONENT_ID, this.PULSE_PACKET_EVENT, body); } listenError(body) { pxsim.pxtcore.registerWithDal(this.PULSE_CABLE_COMPONENT_ID, this.PULSE_PACKET_ERROR_EVENT, body); } receive(buf) { this.packet = buf; this.packetReceived = true; pxsim.board().bus.queue(this.PULSE_CABLE_COMPONENT_ID, this.PULSE_PACKET_EVENT); } } pxsim.CableState = CableState; function getCableState() { return pxsim.board().cableState; } pxsim.getCableState = getCableState; })(pxsim || (pxsim = {})); var pxsim; (function (pxsim) { let ThresholdState; (function (ThresholdState) { ThresholdState[ThresholdState["High"] = 0] = "High"; ThresholdState[ThresholdState["Low"] = 1] = "Low"; ThresholdState[ThresholdState["Normal"] = 2] = "Normal"; })(ThresholdState || (ThresholdState = {})); class AnalogSensorState { constructor(id, min = 0, max = 255, lowThreshold = 64, highThreshold = 192) { this.id = id; this.min = min; this.max = max; this.lowThreshold = lowThreshold; this.highThreshold = highThreshold; this.sensorUsed = false; this.state = ThresholdState.Normal; this.level = Math.ceil((max - min) / 2); } setUsed() { if (!this.sensorUsed) { this.sensorUsed = true; pxsim.runtime.queueDisplayUpdate(); } } setLevel(level) { this.level = this.clampValue(level); if (this.level >= this.highThreshold) { this.setState(ThresholdState.High); } else if (this.level <= this.lowThreshold) { this.setState(ThresholdState.Low); } else { this.setState(ThresholdState.Normal); } } getLevel() { return this.level; } setLowThreshold(value) { this.lowThreshold = this.clampValue(value); this.highThreshold = Math.max(this.lowThreshold + 1, this.highThreshold); } setHighThreshold(value) { this.highThreshold = this.clampValue(value); this.lowThreshold = Math.min(this.highThreshold - 1, this.lowThreshold); } clampValue(value) { if (value < this.min) { return this.min; } else if (value > this.max) { return this.max; } return value; } setState(state) { if (this.state === state) { return; } this.state = state; switch (state) { case ThresholdState.High: pxsim.board().bus.queue(this.id, 2 /* DAL.SENSOR_THRESHOLD_HIGH */); break; case ThresholdState.Low: pxsim.board().bus.queue(this.id, 1 /* DAL.SENSOR_THRESHOLD_LOW */); break; case ThresholdState.Normal: break; } } } pxsim.AnalogSensorState = AnalogSensorState; })(pxsim || (pxsim = {})); var pxsim; (function (pxsim) { var visuals; (function (visuals) { function mkBtnSvg(xy) { let [innerCls, outerCls] = ["sim-button", "sim-button-outer"]; const tabSize = visuals.PIN_DIST / 2.5; const pegR = visuals.PIN_DIST / 5; const btnR = visuals.PIN_DIST * .8; const pegMargin = visuals.PIN_DIST / 8; const plateR = visuals.PIN_DIST / 12; const pegOffset = pegMargin + pegR; let [x, y] = xy; const left = x - tabSize / 2; const top = y - tabSize / 2; const plateH = 3 * visuals.PIN_DIST - tabSize; const plateW = 2 * visuals.PIN_DIST + tabSize; const plateL = left; const plateT = top + tabSize; const btnCX = plateL + plateW / 2; const btnCY = plateT + plateH / 2; let btng = pxsim.svg.elt("g"); //tabs const mkTab = (x, y) => { pxsim.svg.child(btng, "rect", { class: "sim-button-tab", x: x, y: y, width: tabSize, height: tabSize }); }; mkTab(left, top); mkTab(left + 2 * visuals.PIN_DIST, top); mkTab(left, top + 3 * visuals.PIN_DIST); mkTab(left + 2 * visuals.PIN_DIST, top + 3 * visuals.PIN_DIST); //plate pxsim.svg.child(btng, "rect", { class: outerCls, x: plateL, y: plateT, rx: plateR, ry: plateR, width: plateW, height: plateH }); //pegs const mkPeg = (x, y) => { pxsim.svg.child(btng, "circle", { class: "sim-button-nut", cx: x, cy: y, r: pegR }); }; mkPeg(plateL + pegOffset, plateT + pegOffset); mkPeg(plateL + plateW - pegOffset, plateT + pegOffset); mkPeg(plateL + pegOffset, plateT + plateH - pegOffset); mkPeg(plateL + plateW - pegOffset, plateT + plateH - pegOffset); //inner btn let innerBtn = pxsim.svg.child(btng, "circle", { class: innerCls, cx: btnCX, cy: btnCY, r: btnR }); //return return { el: btng, y: top, x: left, w: plateW, h: plateH + 2 * tabSize }; } visuals.mkBtnSvg = mkBtnSvg; visuals.BUTTON_PAIR_STYLE = ` .sim-button { pointer-events: none; fill: #000; } .sim-button-outer:active ~ .sim-button, .sim-button-virtual:active { fill: #FFA500; } .sim-button-outer { cursor: pointer; fill: #979797; } .sim-button-outer:hover { stroke:gray; stroke-width: ${visuals.PIN_DIST / 5}px; } .sim-button-nut { fill:#000; pointer-events:none; } .sim-button-nut:hover { stroke:${visuals.PIN_DIST / 15}px solid #704A4A; } .sim-button-tab { fill:#FFF; pointer-events:none; } .sim-button-virtual { cursor: pointer; fill: rgba(255, 255, 255, 0.6); stroke: rgba(255, 255, 255, 1); stroke-width: ${visuals.PIN_DIST / 5}px; } .sim-button-virtual:hover { stroke: rgba(128, 128, 128, 1); } .sim-text-virtual { fill: #000; pointer-events:none; } `; class ButtonPairView { constructor() { this.style = visuals.BUTTON_PAIR_STYLE; } init(bus, state) { this.state = state; this.bus = bus; this.defs = []; this.element = this.mkBtns(); this.updateState(); this.attachEvents(); } moveToCoord(xy) { let btnWidth = visuals.PIN_DIST * 3; let [x, y] = xy; visuals.translateEl(this.aBtn, [x, y]); visuals.translateEl(this.bBtn, [x + btnWidth, y]); visuals.translateEl(this.abBtn, [x + visuals.PIN_DIST * 1.5, y + visuals.PIN_DIST * 4]); } updateState() { let stateBtns = [this.state.aBtn, this.state.bBtn, this.state.abBtn]; let svgBtns = [this.aBtn, this.bBtn, this.abBtn]; if (this.state.usesButtonAB && this.abBtn.style.visibility != "visible") { this.abBtn.style.visibility = "visible"; } } updateTheme() { } mkBtns() { this.aBtn = mkBtnSvg([0, 0]).el; this.bBtn = mkBtnSvg([0, 0]).el; const mkVirtualBtn = () => { const numPins = 2; const w = visuals.PIN_DIST * 2.8; const offset = (w - (numPins * visuals.PIN_DIST)) / 2; const corner = visuals.PIN_DIST / 2; const cx = 0 - offset + w / 2; const cy = cx; const txtSize = visuals.PIN_DIST * 1.3; const x = -offset; const y = -offset; const txtXOff = visuals.PIN_DIST / 7; const txtYOff = visuals.PIN_DIST / 10; let btng = pxsim.svg.elt("g"); let btn = pxsim.svg.child(btng, "rect", { class: "sim-button-virtual", x: x, y: y, rx: corner, ry: corner, width: w, height: w }); let btnTxt = visuals.mkTxt(cx + txtXOff, cy + txtYOff, txtSize, 0, "A+B"); pxsim.U.addClass(btnTxt, "sim-text"); pxsim.U.addClass(btnTxt, "sim-text-virtual"); btng.appendChild(btnTxt); return btng; }; this.abBtn = mkVirtualBtn(); this.abBtn.style.visibility = "hidden"; let el = pxsim.svg.elt("g"); pxsim.U.addClass(el, "sim-buttonpair"); el.appendChild(this.aBtn); el.appendChild(this.bBtn); el.appendChild(this.abBtn); return el; } attachEvents() { let btnStates = [this.state.aBtn, this.state.bBtn]; let btnSvgs = [this.aBtn, this.bBtn]; btnSvgs.forEach((btn, index) => { pxsim.pointerEvents.down.forEach(evid => btn.addEventListener(evid, ev => { btnStates[index].pressed = true; })); btn.addEventListener(pxsim.pointerEvents.leave, ev => { btnStates[index].pressed = false; }); btn.addEventListener(pxsim.pointerEvents.up, ev => { btnStates[index].pressed = false; this.bus.queue(btnStates[index].id, this.state.props.BUTTON_EVT_UP); this.bus.queue(btnStates[index].id, this.state.props.BUTTON_EVT_CLICK); }); }); let updateBtns = (s) => { btnStates.forEach(b => b.pressed = s); }; pxsim.pointerEvents.down.forEach(evid => this.abBtn.addEventListener(evid, ev => { updateBtns(true); })); this.abBtn.addEventListener(pxsim.pointerEvents.leave, ev => { updateBtns(false); }); this.abBtn.addEventListener(pxsim.pointerEvents.up, ev => { updateBtns(false); this.bus.queue(this.state.abBtn.id, this.state.props.BUTTON_EVT_UP); this.bus.queue(this.state.abBtn.id, this.state.props.BUTTON_EVT_CLICK); }); } } visuals.ButtonPairView = ButtonPairView; })(visuals = pxsim.visuals || (pxsim.visuals = {})); })(pxsim || (pxsim = {})); var pxsim; (function (pxsim) { let PinFlags; (function (PinFlags) { PinFlags[PinFlags["Unused"] = 0] = "Unused"; PinFlags[PinFlags["Digital"] = 1] = "Digital"; PinFlags[PinFlags["Analog"] = 2] = "Analog"; PinFlags[PinFlags["Input"] = 4] = "Input"; PinFlags[PinFlags["Output"] = 8] = "Output"; PinFlags[PinFlags["Touch"] = 16] = "Touch"; })(PinFlags = pxsim.PinFlags || (pxsim.PinFlags = {})); class Pin { constructor(id) { this.id = id; this.touched = false; this.value = 0; this.period = 0; this.servoAngle = 0; this.mode = PinFlags.Unused; this.pitch = false; this.pull = 0; // PullDown this.eventMode = 0; this.used = false; } setValue(value) { // value set from the simulator const old = this.value; this.value = value; const b = pxsim.board(); if (b && this.eventMode == 2 /* DAL.DEVICE_PIN_EVENT_ON_EDGE */ && old != this.value) b.bus.queue(this.id, this.value > 0 ? 2 /* DAL.DEVICE_PIN_EVT_RISE */ : 3 /* DAL.DEVICE_PIN_EVT_FALL */); } digitalReadPin() { this.mode = PinFlags.Digital | PinFlags.Input; return this.value > 100 ? 1 : 0; } digitalWritePin(value) { const b = pxsim.board(); this.mode = PinFlags.Digital | PinFlags.Output; const v = this.value; this.value = value > 0 ? 1023 : 0; pxsim.runtime.queueDisplayUpdate(); } setPull(pull) { this.pull = pull; switch (pull) { case 2 /*PinPullMode.PullDown*/: this.value = 0; break; case 1 /*PinPullMode.PullUp*/: this.value = 1023; break; default: this.value = pxsim.Math_.randomRange(0, 1023); break; } } analogReadPin() { this.mode = PinFlags.Analog | PinFlags.Input; return this.value || 0; } analogWritePin(value) { const b = pxsim.board(); this.mode = PinFlags.Analog | PinFlags.Output; const v = this.value; this.value = Math.max(0, Math.min(1023, value)); pxsim.runtime.queueDisplayUpdate(); } analogSetPeriod(micros) { this.mode = PinFlags.Analog | PinFlags.Output; this.period = micros; pxsim.runtime.queueDisplayUpdate(); } servoWritePin(value) { this.analogSetPeriod(20000); this.servoAngle = Math.max(0, Math.min(180, value)); pxsim.runtime.queueDisplayUpdate(); } servoSetContinuous(continuous) { this.servoContinuous = continuous; } servoSetPulse(pinId, micros) { // TODO } isTouched() { this.mode = PinFlags.Touch | PinFlags.Analog | PinFlags.Input; return this.touched; } onEvent(ev, handler) { const b = pxsim.board(); switch (ev) { case 4 /* DAL.DEVICE_PIN_EVT_PULSE_HI */: case 5 /* DAL.DEVICE_PIN_EVT_PULSE_LO */: this.eventMode = 3 /* DAL.DEVICE_PIN_EVENT_ON_PULSE */;