pxt-calliope/sim/state.ts

374 lines
14 KiB
TypeScript
Raw Normal View History

2016-03-11 01:24:11 +01:00
namespace ks.rt.micro_bit {
export interface RuntimeOptions {
theme: string;
}
2016-03-14 05:24:11 +01:00
2016-03-11 01:24:11 +01:00
export enum DisplayMode {
bw,
greyscale
}
2016-03-14 05:24:11 +01:00
2016-03-11 01:24:11 +01:00
export enum PinMode {
2016-03-14 05:24:11 +01:00
Unused = 0,
2016-03-11 01:24:11 +01:00
Digital = 0x0001,
2016-03-14 05:24:11 +01:00
Analog = 0x0002,
Input = 0x0004,
Output = 0x0008,
Touch = 0x0010
2016-03-11 01:24:11 +01:00
}
2016-03-14 05:24:11 +01:00
2016-03-11 01:24:11 +01:00
export class Pin {
2016-03-14 05:24:11 +01:00
constructor(public id: number) { }
2016-03-11 01:24:11 +01:00
touched = false;
value = 0;
2016-03-14 16:32:02 +01:00
period = 0;
2016-03-12 01:51:35 +01:00
mode = PinMode.Unused;
pitch = false;
2016-03-14 05:24:11 +01:00
isTouched(): boolean {
2016-03-11 01:24:11 +01:00
this.mode = PinMode.Touch;
return this.touched;
}
}
2016-03-14 05:24:11 +01:00
2016-03-11 01:24:11 +01:00
export class Button {
2016-03-14 05:24:11 +01:00
constructor(public id: number) { }
2016-03-11 01:24:11 +01:00
pressed: boolean;
}
2016-03-14 05:24:11 +01:00
2016-03-11 01:24:11 +01:00
export class EventBus {
2016-03-14 05:24:11 +01:00
private queues: Map<EventQueue<number>> = {};
constructor(private runtime: Runtime) { }
listen(id: number, evid: number, handler: RefAction) {
let k = id + ':' + evid;
2016-03-11 01:24:11 +01:00
let queue = this.queues[k];
if (!queue) queue = this.queues[k] = new EventQueue<number>(this.runtime);
queue.handler = handler;
}
2016-03-14 05:24:11 +01:00
2016-03-11 01:24:11 +01:00
queue(id: number, evid: number, value: number = 0) {
2016-03-14 05:24:11 +01:00
let k = id + ':' + evid;
2016-03-11 01:24:11 +01:00
let queue = this.queues[k];
if (queue) queue.push(value);
}
}
2016-03-14 05:24:11 +01:00
2016-03-11 01:24:11 +01:00
export interface PacketBuffer {
2016-03-14 05:24:11 +01:00
data: number[];
rssi?: number;
2016-03-11 01:24:11 +01:00
}
2016-03-14 05:24:11 +01:00
2016-03-11 01:24:11 +01:00
export class RadioDatagram {
datagram: PacketBuffer[] = [];
2016-03-14 05:24:11 +01:00
lastReceived: PacketBuffer = {
data: [0, 0, 0, 0],
rssi: -1
2016-03-11 01:24:11 +01:00
};
2016-03-14 05:24:11 +01:00
constructor(private runtime: Runtime) {
2016-03-11 01:24:11 +01:00
}
2016-03-14 05:24:11 +01:00
queue(packet: PacketBuffer) {
2016-03-11 01:24:11 +01:00
if (this.datagram.length < 5) {
this.datagram.push(packet);
let ens = enums();
(<Board>runtime.board).bus.queue(ens.MICROBIT_ID_RADIO, ens.MICROBIT_RADIO_EVT_DATAGRAM);
}
}
2016-03-14 05:24:11 +01:00
send(buffer: number[]) {
2016-03-11 01:24:11 +01:00
Runtime.postMessage(<SimulatorRadioPacketMessage>{
2016-03-14 05:24:11 +01:00
type: 'radiopacket',
2016-03-11 01:24:11 +01:00
data: buffer.slice(0, 8)
})
}
2016-03-14 05:24:11 +01:00
recv(): PacketBuffer {
2016-03-11 01:24:11 +01:00
var r = this.datagram.shift();
2016-03-14 05:24:11 +01:00
if (!r) r = {
data: [0, 0, 0, 0],
rssi: -1
2016-03-11 01:24:11 +01:00
};
return this.lastReceived = r;
}
}
2016-03-14 05:24:11 +01:00
2016-03-11 01:24:11 +01:00
export class RadioBus {
// uint8_t radioDefaultGroup = MICROBIT_RADIO_DEFAULT_GROUP;
groupId = 0; // todo
2016-03-15 21:05:11 +01:00
power = 0;
2016-03-14 05:24:11 +01:00
datagram: RadioDatagram;
constructor(private runtime: Runtime) {
2016-03-11 01:24:11 +01:00
this.datagram = new RadioDatagram(runtime);
}
2016-03-14 05:24:11 +01:00
2016-03-11 01:24:11 +01:00
setGroup(id: number) {
this.groupId = id & 0xff; // byte only
}
2016-03-15 21:05:11 +01:00
setTransmitPower(power: number) {
this.power = Math.max(0, Math.min(7, power));
}
2016-03-14 05:24:11 +01:00
2016-03-11 01:24:11 +01:00
broadcast(msg: number) {
let ens = enums();
Runtime.postMessage(<SimulatorEventBusMessage>{
2016-03-14 05:24:11 +01:00
type: 'eventbus',
2016-03-11 01:24:11 +01:00
id: ens.MES_BROADCAST_GENERAL_ID,
2016-03-15 21:05:11 +01:00
eventid: msg,
power: this.power,
group: this.groupId
2016-03-11 01:24:11 +01:00
})
}
}
2016-03-14 05:24:11 +01:00
2016-03-11 01:24:11 +01:00
export interface SimulatorEventBusMessage extends SimulatorMessage {
id: number;
eventid: number;
value?: number;
}
2016-03-14 05:24:11 +01:00
2016-03-11 01:24:11 +01:00
export interface SimulatorSerialMessage extends SimulatorMessage {
2016-03-14 05:24:11 +01:00
id: string;
2016-03-11 01:24:11 +01:00
data: string;
}
2016-03-14 05:24:11 +01:00
2016-03-11 01:24:11 +01:00
export interface SimulatorRadioPacketMessage extends SimulatorMessage {
data: number[];
rssi?: number;
}
2016-03-14 05:24:11 +01:00
2016-03-11 01:24:11 +01:00
export class Board extends BaseBoard {
id: string;
2016-03-14 05:24:11 +01:00
2016-03-11 01:24:11 +01:00
// the bus
2016-03-14 05:24:11 +01:00
bus: EventBus;
2016-03-11 01:24:11 +01:00
radio: RadioBus;
// display
image = createImage(5);
brigthness = 255;
displayMode = DisplayMode.bw;
font: Image = createFont();
// buttons
usesButtonAB: boolean = false;
2016-03-14 05:24:11 +01:00
buttons: Button[];
2016-03-11 01:24:11 +01:00
// pins
2016-03-14 05:24:11 +01:00
pins: Pin[];
2016-03-11 01:24:11 +01:00
// serial
serialIn: string[] = [];
// sensors
usesAcceleration = false;
acceleration = [0, 0, -1023];
2016-03-16 22:56:50 +01:00
accelerometerRange = 2;
2016-03-14 05:24:11 +01:00
usesHeading = false;
2016-03-11 01:24:11 +01:00
heading = 90;
2016-03-14 05:24:11 +01:00
2016-03-14 22:03:31 +01:00
usesTemperature = false;
2016-03-11 01:24:11 +01:00
temperature = 21;
2016-03-14 05:24:11 +01:00
2016-03-11 22:17:49 +01:00
usesLightLevel = false;
2016-03-11 01:24:11 +01:00
lightLevel = 128;
2016-03-14 05:24:11 +01:00
2016-03-11 01:24:11 +01:00
animationQ: AnimationQueue;
2016-03-14 05:24:11 +01:00
2016-03-11 01:24:11 +01:00
constructor() {
super()
this.id = "b" + Math.random();
this.animationQ = new AnimationQueue(runtime);
this.bus = new EventBus(runtime);
this.radio = new RadioBus(runtime);
let ens = enums();
this.buttons = [
new Button(ens.MICROBIT_ID_BUTTON_A),
new Button(ens.MICROBIT_ID_BUTTON_B),
new Button(ens.MICROBIT_ID_BUTTON_AB)
];
this.pins = [
new Pin(ens.MICROBIT_ID_IO_P0),
new Pin(ens.MICROBIT_ID_IO_P1),
new Pin(ens.MICROBIT_ID_IO_P2),
new Pin(ens.MICROBIT_ID_IO_P3),
new Pin(ens.MICROBIT_ID_IO_P4),
new Pin(ens.MICROBIT_ID_IO_P5),
new Pin(ens.MICROBIT_ID_IO_P6),
new Pin(ens.MICROBIT_ID_IO_P7),
new Pin(ens.MICROBIT_ID_IO_P8),
new Pin(ens.MICROBIT_ID_IO_P9),
new Pin(ens.MICROBIT_ID_IO_P10),
new Pin(ens.MICROBIT_ID_IO_P11),
new Pin(ens.MICROBIT_ID_IO_P12),
new Pin(ens.MICROBIT_ID_IO_P13),
new Pin(ens.MICROBIT_ID_IO_P14),
new Pin(ens.MICROBIT_ID_IO_P15),
new Pin(ens.MICROBIT_ID_IO_P16),
2016-03-14 05:24:11 +01:00
null,
null,
2016-03-11 01:24:11 +01:00
new Pin(ens.MICROBIT_ID_IO_P19),
new Pin(ens.MICROBIT_ID_IO_P20)
];
}
2016-03-14 05:24:11 +01:00
initAsync(msg: SimulatorRunMessage): Promise<void> {
2016-03-11 01:24:11 +01:00
let options = (msg.options || {}) as RuntimeOptions;
2016-03-14 05:24:11 +01:00
let theme: micro_bit.IBoardTheme;
switch (options.theme) {
2016-03-11 01:24:11 +01:00
case 'blue': theme = micro_bit.themes[0]; break;
case 'yellow': theme = micro_bit.themes[1]; break;
case 'green': theme = micro_bit.themes[2]; break;
case 'red': theme = micro_bit.themes[3]; break;
2016-03-14 05:24:11 +01:00
default: theme = ks.rt.micro_bit.randomTheme();
2016-03-11 01:24:11 +01:00
}
2016-03-14 05:24:11 +01:00
2016-03-11 01:24:11 +01:00
console.log('setting up microbit simulator')
let view = new ks.rt.micro_bit.MicrobitBoardSvg({
theme: theme,
runtime: runtime
})
document.body.innerHTML = ''; // clear children
document.body.appendChild(view.element);
2016-03-14 05:24:11 +01:00
return Promise.resolve();
2016-03-11 01:24:11 +01:00
}
2016-03-14 05:24:11 +01:00
2016-03-11 01:24:11 +01:00
receiveMessage(msg: SimulatorMessage) {
if (!runtime || runtime.dead) return;
2016-03-14 05:24:11 +01:00
switch (msg.type || "") {
case 'eventbus':
2016-03-11 01:24:11 +01:00
let ev = <SimulatorEventBusMessage>msg;
this.bus.queue(ev.id, ev.eventid, ev.value);
break;
2016-03-14 05:24:11 +01:00
case 'serial':
2016-03-11 01:24:11 +01:00
this.serialIn.push((<SimulatorSerialMessage>msg).data || '');
break;
case 'radiopacket':
let packet = <SimulatorRadioPacketMessage>msg;
2016-03-14 05:24:11 +01:00
this.radio.datagram.queue({ data: packet.data || [], rssi: packet.rssi || 0 })
2016-03-11 01:24:11 +01:00
break;
}
}
2016-03-14 05:24:11 +01:00
2016-03-11 01:24:11 +01:00
readSerial() {
let v = this.serialIn.shift() || '';
return v;
}
2016-03-14 05:24:11 +01:00
2016-03-11 01:24:11 +01:00
serialOutBuffer: string = '';
2016-03-14 05:24:11 +01:00
writeSerial(s: string) {
for (let i = 0; i < s.length; ++i) {
2016-03-11 01:24:11 +01:00
let c = s[i];
2016-03-14 05:24:11 +01:00
switch (c) {
case '\n':
2016-03-11 01:24:11 +01:00
Runtime.postMessage(<SimulatorSerialMessage>{
type: 'serial',
data: this.serialOutBuffer,
id: runtime.id
2016-03-14 05:24:11 +01:00
})
2016-03-11 01:24:11 +01:00
this.serialOutBuffer = ''
break;
case '\r': continue;
default: this.serialOutBuffer += c;
2016-03-14 05:24:11 +01:00
}
2016-03-11 01:24:11 +01:00
}
}
}
export class Image {
public width: number;
2016-03-14 05:24:11 +01:00
public data: number[];
2016-03-11 01:24:11 +01:00
constructor(width: number, data: number[]) {
this.width = width;
this.data = data;
}
public get(x: number, y: number): number {
// TODO range checking
return this.data[y * this.width + x];
}
public set(x: number, y: number, v: number) {
// TODO range checking
this.data[y * this.width + x] = v;
}
public copyTo(xSrcIndex: number, length: number, target: Image, xTargetIndex: number): void {
for (let x = 0; x < length; x++) {
for (let y = 0; y < 5; y++) {
let value = this.get(xSrcIndex + x, y);
target.set(xTargetIndex + x, y, value);
}
}
}
2016-03-15 05:37:03 +01:00
public shiftLeft(cols: number) {
for(let x = 0; x < this.width;++x)
for(let y = 0; y < 5;++y)
this.set(x, y, x < this.width - cols ? this.get(x + cols,y) : 0);
}
public shiftRight(cols: number) {
for(let x = this.width -1; x <=0;--x)
for(let y = 0; y < 5;++y)
this.set(x, y, x > cols ? this.get(x - cols,y) : 0);
}
2016-03-14 05:24:11 +01:00
public clear(): void {
for (var i = 0; i < this.data.length; ++i)
2016-03-11 01:24:11 +01:00
this.data[i] = 0;
}
}
export function createImage(width: number): Image {
return new Image(width, new Array(width * 5));
}
export function createImageFromBuffer(data: number[]): Image {
return new Image(data.length / 5, data);
}
export function createImageFromString(text: string): Image {
let font = board().font;
let w = font.width;
let sprite = createImage(6 * text.length - 1);
let k = 0;
for (let i = 0; i < text.length; i++) {
let charCode = text.charCodeAt(i);
let charStart = (charCode - 32) * 5;
if (charStart < 0 || charStart + 5 > w) {
charCode = " ".charCodeAt(0);
charStart = (charCode - 32) * 5;
}
font.copyTo(charStart, 5, sprite, k);
k = k + 5;
if (i < text.length - 1) {
k = k + 1;
}
}
return sprite;
}
export function createFont(): Image {
var data = [0x0, 0x0, 0x0, 0x0, 0x0, 0x8, 0x8, 0x8, 0x0, 0x8, 0xa, 0x4a, 0x40, 0x0, 0x0, 0xa, 0x5f, 0xea, 0x5f, 0xea, 0xe, 0xd9, 0x2e, 0xd3, 0x6e, 0x19, 0x32, 0x44, 0x89, 0x33, 0xc, 0x92, 0x4c, 0x92, 0x4d, 0x8, 0x8, 0x0, 0x0, 0x0, 0x4, 0x88, 0x8, 0x8, 0x4, 0x8, 0x4, 0x84, 0x84, 0x88, 0x0, 0xa, 0x44, 0x8a, 0x40, 0x0, 0x4, 0x8e, 0xc4, 0x80, 0x0, 0x0, 0x0, 0x4, 0x88, 0x0, 0x0, 0xe, 0xc0, 0x0, 0x0, 0x0, 0x0, 0x8, 0x0, 0x1, 0x22, 0x44, 0x88, 0x10, 0xc, 0x92, 0x52, 0x52, 0x4c, 0x4, 0x8c, 0x84, 0x84, 0x8e, 0x1c, 0x82, 0x4c, 0x90, 0x1e, 0x1e, 0xc2, 0x44, 0x92, 0x4c, 0x6, 0xca, 0x52, 0x5f, 0xe2, 0x1f, 0xf0, 0x1e, 0xc1, 0x3e, 0x2, 0x44, 0x8e, 0xd1, 0x2e, 0x1f, 0xe2, 0x44, 0x88, 0x10, 0xe, 0xd1, 0x2e, 0xd1, 0x2e, 0xe, 0xd1, 0x2e, 0xc4, 0x88, 0x0, 0x8, 0x0, 0x8, 0x0, 0x0, 0x4, 0x80, 0x4, 0x88, 0x2, 0x44, 0x88, 0x4, 0x82, 0x0, 0xe, 0xc0, 0xe, 0xc0, 0x8, 0x4, 0x82, 0x44, 0x88, 0xe, 0xd1, 0x26, 0xc0, 0x4, 0xe, 0xd1, 0x35, 0xb3, 0x6c, 0xc, 0x92, 0x5e, 0xd2, 0x52, 0x1c, 0x92, 0x5c, 0x92, 0x5c, 0xe, 0xd0, 0x10, 0x10, 0xe, 0x1c, 0x92, 0x52, 0x52, 0x5c, 0x1e, 0xd0, 0x1c, 0x90, 0x1e, 0x1e, 0xd0, 0x1c, 0x90, 0x10, 0xe, 0xd0, 0x13, 0x71, 0x2e, 0x12, 0x52, 0x5e, 0xd2, 0x52, 0x1c, 0x88, 0x8, 0x8, 0x1c, 0x1f, 0xe2, 0x42, 0x52, 0x4c, 0x12, 0x54, 0x98, 0x14, 0x92, 0x10, 0x10, 0x10, 0x10, 0x1e, 0x11, 0x3b, 0x75, 0xb1, 0x31, 0x11, 0x39, 0x35, 0xb3, 0x71, 0xc, 0x92, 0x52, 0x52, 0x4c, 0x1c, 0x92, 0x5c, 0x90, 0x10, 0xc, 0x92, 0x52, 0x4c, 0x86, 0x1c, 0x92, 0x5c, 0x92, 0x51, 0xe, 0xd0, 0xc, 0x82, 0x5c, 0x1f, 0xe4, 0x84, 0x84, 0x84, 0x12, 0x52, 0x52, 0x52, 0x4c, 0x11, 0x31, 0x31, 0x2a, 0x44, 0x11, 0x31, 0x35, 0xbb, 0x71, 0x12, 0x52, 0x4c, 0x92, 0x52, 0x11, 0x2a, 0x44, 0x84, 0x84, 0x1e, 0xc4, 0x88, 0x10, 0x1e, 0xe, 0xc8, 0x8, 0x8, 0xe, 0x10, 0x8, 0x4, 0x82, 0x41, 0xe, 0xc2, 0x42, 0x42, 0x4e, 0x4, 0x8a, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1f, 0x8, 0x4, 0x80, 0x0, 0x0, 0x0, 0xe, 0xd2, 0x52, 0x4f, 0x10, 0x10, 0x1c, 0x92, 0x5c, 0x0, 0xe, 0xd0, 0x10, 0xe, 0x2, 0x42, 0x4e, 0xd2, 0x4e, 0xc, 0x92, 0x5c, 0x90, 0xe, 0x6, 0xc8, 0x1c, 0x88, 0x8, 0xe, 0xd2, 0x4e, 0xc2, 0x4c, 0x10, 0x10, 0x1c, 0x92, 0x52, 0x8, 0x0, 0x8, 0x8, 0x8, 0x2, 0x40, 0x2, 0x42, 0x4c, 0x10, 0x14, 0x98, 0x14, 0x92, 0x8, 0x8, 0x8, 0x8, 0x6, 0x0, 0x1b, 0x75, 0xb1, 0x31, 0x0, 0x1c, 0x92, 0x52, 0x52, 0x0, 0xc, 0x92, 0x52, 0x4c, 0x0, 0x1c, 0x92, 0x5c, 0x90, 0x0, 0xe, 0xd2, 0x4e, 0xc2, 0x0, 0xe, 0xd0, 0x10, 0x10, 0x0, 0x6, 0xc8, 0x4, 0x98, 0x8, 0x8, 0xe, 0xc8, 0x7, 0x0, 0x12, 0x52, 0x52, 0x4f, 0x0, 0x11, 0x31, 0x2a, 0x44, 0x0, 0x11, 0x31, 0x35, 0xbb, 0x0, 0x12, 0x4c, 0x8c, 0x92, 0x0, 0x11, 0x2a, 0x44, 0x98, 0x0, 0x1e, 0xc4, 0x88, 0x1e, 0x6, 0xc4, 0x8c, 0x84, 0x86, 0x8, 0x8, 0x8, 0x8, 0x8, 0x18, 0x8, 0xc, 0x88, 0x18, 0x0, 0x0, 0xc, 0x83, 0x60];
let nb = data.length;
let n = nb / 5;
var font = createImage(nb);
for (let c = 0; c < n; c++) {
for (let row = 0; row < 5; row++) {
let char = data[c * 5 + row];
for (let col = 0; col < 5; col++) {
if ((char & (1 << col)) != 0)
font.set((c * 5 + 4) - col, row, 255);
}
}
}
return font;
}
}