pxt-calliope/sim/libmbit.ts
2016-03-19 19:15:20 -07:00

494 lines
15 KiB
TypeScript

/// <reference path="../node_modules/kindscript/typings/bluebird/bluebird.d.ts"/>
/// <reference path="../node_modules/kindscript/built/kindsim.d.ts"/>
namespace ks.rt.micro_bit {
export function initCurrentRuntime() {
U.assert(!runtime.board)
runtime.board = new Board()
}
ks.rt.initCurrentRuntime = initCurrentRuntime;
export function board() {
return runtime.board as Board
}
export function enums() {
return runtime.enums as any as Enums
}
export interface AnimationOptions {
interval: number;
// false means last frame
frame: () => boolean;
whenDone?: (cancelled: boolean) => void;
}
export class AnimationQueue {
private queue: AnimationOptions[] = [];
private process: () => void;
constructor(private runtime: Runtime) {
this.process = () => {
let top = this.queue[0]
if (!top) return
if (this.runtime.dead) return
runtime = this.runtime
let res = top.frame()
runtime.queueDisplayUpdate()
runtime.maybeUpdateDisplay()
if (res === false) {
this.queue.shift();
// if there is already something in the queue, start processing
if (this.queue[0])
setTimeout(this.process, this.queue[0].interval)
// this may push additional stuff
top.whenDone(false);
} else {
setTimeout(this.process, top.interval)
}
}
}
public cancelAll() {
let q = this.queue
this.queue = []
for (let a of q) {
a.whenDone(true)
}
}
public cancelCurrent() {
let top = this.queue[0]
if (top) {
this.queue.shift();
top.whenDone(true);
}
}
public enqueue(anim: AnimationOptions) {
if (!anim.whenDone) anim.whenDone = () => { };
this.queue.push(anim)
// we start processing when the queue goes from 0 to 1
if (this.queue.length == 1)
this.process()
}
public executeAsync(anim: AnimationOptions) {
U.assert(!anim.whenDone)
return new Promise<boolean>((resolve, reject) => {
anim.whenDone = resolve
this.enqueue(anim)
})
}
}
export function panic(code: number) {
console.log("PANIC:", code)
throw new Error("PANIC " + code)
}
/* basic */
export function showDigit(v: number) {
if (!quiet)
console.log("DIGIT:", v)
plotLeds(createImageFromString(v.toString()[0]));
}
export function clearScreen() {
board().image.clear();
runtime.queueDisplayUpdate()
}
export function showLeds(leds: micro_bit.Image, delay: number): void {
showAnimation(leds, delay);
}
function scrollImage(leds: micro_bit.Image, interval: number, stride: number): void {
let cb = getResume()
let off = stride > 0 ? 0 : leds.width - 1;
let display = board().image;
board().animationQ.enqueue({
interval: interval,
frame: () => {
if (off >= leds.width || off < 0) return false;
stride > 0 ? display.shiftLeft(stride) : display.shiftRight(-stride);
let c = Math.min(stride, leds.width - off);
leds.copyTo(off, c, display, 5 - stride)
off += stride;
return true;
},
whenDone: cb
})
}
export function showAnimation(leds: micro_bit.Image, interval: number = 400): void {
scrollImage(leds, interval, 5);
}
export function scrollNumber(x: number, interval: number) {
if (interval < 0) return;
let leds = createImageFromString(x.toString());
if (x < 0 || x >= 10) scrollImage(leds, interval, 1);
else showLeds(leds, interval * 5);
}
export function scrollString(s: string, interval: number) {
if (interval < 0) return;
if (s.length == 0) {
clearScreen();
pause(interval * 5);
} else {
let leds = createImageFromString(s);
if (s.length == 1) showLeds(leds, interval * 5)
else scrollImage(leds, interval, 1);
}
}
export function forever(a: RefAction) {
function loop() {
runtime.runFiberAsync(a)
.then(() => Promise.delay(20))
.then(loop)
.done()
}
incr(a)
loop()
}
export var pause = thread.pause;
/* leds */
export function plot(x: number, y: number) {
board().image.set(x, y, 255);
runtime.queueDisplayUpdate()
}
export function unPlot(x: number, y: number) {
board().image.set(x, y, 0);
runtime.queueDisplayUpdate()
}
export function point(x: number, y: number): boolean {
return !!board().image.get(x, y);
}
export function brightness(): number {
return board().brigthness;
}
export function setBrightness(value: number): void {
board().brigthness = value;
runtime.queueDisplayUpdate()
}
export function stopAnimation(): void {
board().animationQ.cancelAll();
}
export function plotLeds(leds: micro_bit.Image): void {
leds.copyTo(0, 5, board().image, 0)
runtime.queueDisplayUpdate()
}
export function setDisplayMode(mode: DisplayMode): void {
board().displayMode = mode;
runtime.queueDisplayUpdate()
}
/* control */
export var runInBackground = thread.runInBackground;
/* serial */
export function serialSendString(s: string) {
board().writeSerial(s);
}
export function serialReadString() : string {
return board().readSerial();
}
/* input */
export function onButtonPressed(button : number, handler: RefAction) : void {
let ens = enums();
let b = board();
if (button == ens.MICROBIT_ID_BUTTON_AB && !board().usesButtonAB) {
b.usesButtonAB = true;
runtime.queueDisplayUpdate();
}
b.bus.listen(button, ens.MICROBIT_BUTTON_EVT_CLICK, handler);
}
export function isButtonPressed(button: number): boolean {
let ens = enums();
let b = board();
if (button == ens.MICROBIT_ID_BUTTON_AB && !board().usesButtonAB) {
b.usesButtonAB = true;
runtime.queueDisplayUpdate();
}
let bts = b.buttons;
if (button == ens.MICROBIT_ID_BUTTON_A) return bts[0].pressed;
if (button == ens.MICROBIT_ID_BUTTON_B) return bts[1].pressed;
return bts[2].pressed || (bts[0].pressed && bts[1].pressed);
}
export function onGesture(gesture: number, handler: RefAction) {
let ens = enums();
let b = board();
b.accelerometer.activate();
if (gesture == 11 && !b.useShake) { // SAKE
b.useShake = true;
runtime.queueDisplayUpdate();
}
b.bus.listen(ens.MICROBIT_ID_GESTURE, gesture, handler);
}
export function onPinPressed(pin: Pin, handler: RefAction) {
pin.isTouched();
onButtonPressed(pin.id, handler);
}
export function ioP0() { return board().pins[0]; }
export function ioP1() { return board().pins[1]; }
export function ioP2() { return board().pins[2]; }
export function ioP3() { return board().pins[3]; }
export function ioP4() { return board().pins[4]; }
export function ioP5() { return board().pins[5]; }
export function ioP6() { return board().pins[6]; }
export function ioP7() { return board().pins[7]; }
export function ioP8() { return board().pins[8]; }
export function ioP9() { return board().pins[9]; }
export function ioP10() { return board().pins[10]; }
export function ioP11() { return board().pins[11]; }
export function ioP12() { return board().pins[12]; }
export function ioP13() { return board().pins[13]; }
export function ioP14() { return board().pins[14]; }
export function ioP15() { return board().pins[15]; }
export function ioP16() { return board().pins[16]; }
export function ioP19() { return board().pins[19]; }
export function ioP20() { return board().pins[20]; }
export function isPinTouched(pin: Pin): boolean {
return pin.isTouched();
}
export function compassHeading(): number {
var b = board();
if (!b.usesHeading) {
b.usesHeading = true;
runtime.queueDisplayUpdate();
}
return b.heading;
}
export function temperature(): number {
var b = board();
if (!b.usesTemperature) {
b.usesTemperature = true;
runtime.queueDisplayUpdate();
}
return b.temperature;
}
export function getAcceleration(dimension: number): number {
let b = board();
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()));
}
}
export function setAccelerometerRange(range : number) {
let b = board();
b.accelerometer.setSampleRange(range);
}
export function lightLevel(): number {
let b = board();
if (!b.usesLightLevel) {
b.usesLightLevel = true;
runtime.queueDisplayUpdate();
}
return b.lightLevel;
}
export function getMagneticForce(): number {
// TODO
return 0;
}
export function getCurrentTime(): number {
return runtime.runningTime();
}
/* pins */
export function digitalReadPin(pin : Pin) : number {
pin.mode = PinMode.Digital | PinMode.Input;
return pin.value > 100 ? 1 : 0;
}
export function digitalWritePin(pin : Pin, value: number) {
pin.mode = PinMode.Digital | PinMode.Output;
pin.value = value > 0 ? 1023 : 0;
runtime.queueDisplayUpdate();
}
export function analogReadPin(pin : Pin) : number {
pin.mode = PinMode.Analog | PinMode.Input;
return pin.value || 0;
}
export function analogWritePin(pin : Pin, value: number) {
pin.mode = PinMode.Analog | PinMode.Output;
pin.value = value ? 1 : 0;
runtime.queueDisplayUpdate();
}
export function setAnalogPeriodUs(pin: Pin, micros:number) {
pin.mode = PinMode.Analog | PinMode.Output;
pin.period = micros;
runtime.queueDisplayUpdate();
}
export function servoWritePin(pin: Pin, value: number) {
setAnalogPeriodUs(pin, 20000);
// TODO
}
export function servoSetPulse(pin: Pin, micros:number) {
}
module AudioContextManager {
var _context : any; // AudioContext
var _vco : any; //OscillatorNode;
var _vca: any; // GainNode;
function context() : any {
if (!_context) _context = freshContext();
return _context;
}
function freshContext() : any {
(<any>window).AudioContext = (<any>window).AudioContext || (<any>window).webkitAudioContext;
if ((<any>window).AudioContext) {
try {
// this call my crash.
// SyntaxError: audio resources unavailable for AudioContext construction
return new (<any>window).AudioContext();
} catch(e) {}
}
return undefined;
}
export function stop() {
if (_vca) _vca.gain.value = 0;
}
export function tone(frequency: number, gain: number) {
if (frequency <= 0) return;
var ctx = context();
if (!ctx) return;
gain = Math.max(0, Math.min(1, gain));
if (!_vco) {
try {
_vco = ctx.createOscillator();
_vca = ctx.createGain();
_vco.connect(_vca);
_vca.connect(ctx.destination);
_vca.gain.value = gain;
_vco.start(0);
} catch(e) {
_vco = undefined;
_vca = undefined;
return;
}
}
_vco.frequency.value = frequency;
_vca.gain.value = gain;
}
}
export function enablePitch(pin: Pin) {
board().pins.filter(p => !!p).forEach(p => p.pitch = false);
pin.pitch = true;
}
export function pitch(frequency: number, ms: number) {
// update analog output
let pin = board().pins.filter(pin => !!pin && pin.pitch)[0] || board().pins[0];
pin.mode = PinMode.Analog | PinMode.Output;
if (frequency <= 0) {
pin.value = 0;
pin.period = 0;
} else {
pin.value = 512;
pin.period = 1000000/frequency;
}
runtime.queueDisplayUpdate();
let cb = getResume();
AudioContextManager.tone(frequency, 1);
if (ms <= 0) cb();
else {
setTimeout(() => {
AudioContextManager.stop();
pin.value = 0;
pin.period = 0;
pin.mode = PinMode.Unused;
runtime.queueDisplayUpdate();
cb()
}, ms);
}
}
/* radio */
export function broadcastMessage(msg: number) : void {
board().radio.broadcast(msg);
}
export function onBroadcastMessageReceived(msg: number, handler: RefAction) : void {
let ens = enums()
board().bus.listen(ens.MES_BROADCAST_GENERAL_ID, msg, handler);
}
export function setGroup(id : number) : void {
board().radio.setGroup(id);
}
export function setTransmitPower(power: number) : void {
board().radio.setTransmitPower(power);
}
export function datagramSendNumbers(value0 : number, value1: number, value2: number, value3: number) : void {
board().radio.datagram.send([value0, value1, value2, value3]);
}
export function datagramReceiveNumber() : number {
return board().radio.datagram.recv().data[0];
}
export function datagramGetNumber(index : number) : number {
return board().radio.datagram.lastReceived.data[index] || 0;
}
export function datagramGetRSSI() : number {
return board().radio.datagram.lastReceived.rssi;
}
export function onDatagramReceived(handler: RefAction) : void {
let ens = enums();
board().bus.listen(ens.MICROBIT_ID_RADIO, ens.MICROBIT_RADIO_EVT_DATAGRAM, handler);
}
}