Bump V3.0.22 (#110)
* change simulator svg * change radio image * Remove google fonts cdn * change color of 'advanced' button * font fix * font fix 2 * display fix * change fullsceen simulator bg * Continuous servo * handle continuous state * adding shims * update rendering for continuous servos * fixing sim * fix sig * typo * fix sim * bump pxt * bump pxt * rerun travis * Input blocks revision - add Button and Pin event types - merge onPinPressed & onPinReleased in new onPinEvent function - create new onButtonEvent function * update input blocks in docs and tests * remove device_pin_release block * Hide DAL.x behind Enum * bring back deprecated blocks, but hide them * shims and locales files * fix input.input. typing * remove buildpr * bump V3 * update simulator aspect ratio * add Loudness Block * revoke loudness block * Adds soundLevel To be replaced by pxt-common-packages when DAL is updated. * Remove P0 & P3 from AnalogPin Co-authored-by: Juri <gitkraken@juriwolf.de>
This commit is contained in:
@ -1,8 +1,10 @@
|
||||
/// <reference path="../node_modules/pxt-core/built/pxtsim.d.ts"/>
|
||||
/// <reference path="../libs/core/dal.d.ts"/>
|
||||
/// <reference path="../libs/core/enums.d.ts"/>
|
||||
|
||||
namespace pxsim {
|
||||
export class DalBoard extends CoreBoard
|
||||
implements RadioBoard {
|
||||
implements RadioBoard, LightBoard {
|
||||
// state & update logic for component services
|
||||
ledMatrixState: LedMatrixState;
|
||||
edgeConnectorState: EdgeConnectorState;
|
||||
@ -13,8 +15,8 @@ namespace pxsim {
|
||||
lightSensorState: LightSensorState;
|
||||
buttonPairState: ButtonPairState;
|
||||
radioState: RadioState;
|
||||
// TODO: not singletons
|
||||
neopixelState: NeoPixelState;
|
||||
microphoneState: AnalogSensorState;
|
||||
lightState: pxt.Map<CommonNeoPixelState>;
|
||||
rgbLedState: number;
|
||||
speakerState: SpeakerState;
|
||||
fileSystem: FileSystemState;
|
||||
@ -27,6 +29,7 @@ namespace pxsim {
|
||||
super()
|
||||
|
||||
// components
|
||||
this.lightState = {};
|
||||
this.fileSystem = new FileSystemState();
|
||||
this.builtinParts["ledmatrix"] = this.ledMatrixState = new LedMatrixState(runtime);
|
||||
this.builtinParts["buttonpair"] = this.buttonPairState = new ButtonPairState({
|
||||
@ -72,23 +75,25 @@ namespace pxsim {
|
||||
ID_RADIO: DAL.MICROBIT_ID_RADIO,
|
||||
RADIO_EVT_DATAGRAM: DAL.MICROBIT_RADIO_EVT_DATAGRAM
|
||||
});
|
||||
this.builtinParts["microphone"] = this.microphoneState = new AnalogSensorState(3001 /* DEVICE_ID_MICROPHONE */, 52, 120, 75, 96);
|
||||
this.builtinParts["accelerometer"] = this.accelerometerState = new AccelerometerState(runtime);
|
||||
this.builtinParts["serial"] = this.serialState = new SerialState();
|
||||
this.builtinParts["thermometer"] = this.thermometerState = new ThermometerState();
|
||||
this.builtinParts["lightsensor"] = this.lightSensorState = new LightSensorState();
|
||||
this.builtinParts["compass"] = this.compassState = new CompassState();
|
||||
this.builtinParts["neopixel"] = this.neopixelState = new NeoPixelState();
|
||||
this.builtinParts["speaker"] = this.speakerState = new SpeakerState();
|
||||
this.builtinParts["microservo"] = this.edgeConnectorState;
|
||||
|
||||
this.builtinVisuals["buttonpair"] = () => new visuals.ButtonPairView();
|
||||
this.builtinVisuals["ledmatrix"] = () => new visuals.LedMatrixView();
|
||||
this.builtinVisuals["neopixel"] = () => new visuals.NeoPixelView();
|
||||
this.builtinVisuals["microservo"] = () => new visuals.MicroServoView();
|
||||
|
||||
this.builtinParts["neopixel"] = (pin: Pin) => { return this.neopixelState(pin.id); };
|
||||
this.builtinVisuals["neopixel"] = () => new visuals.NeoPixelView(pxsim.parsePinString);
|
||||
this.builtinPartVisuals["neopixel"] = (xy: visuals.Coord) => visuals.mkNeoPixelPart(xy);
|
||||
|
||||
this.builtinPartVisuals["buttonpair"] = (xy: visuals.Coord) => visuals.mkBtnSvg(xy);
|
||||
this.builtinPartVisuals["ledmatrix"] = (xy: visuals.Coord) => visuals.mkLedMatrixSvg(xy, 8, 8);
|
||||
this.builtinPartVisuals["neopixel"] = (xy: visuals.Coord) => visuals.mkNeoPixelPart(xy);
|
||||
this.builtinPartVisuals["microservo"] = (xy: visuals.Coord) => visuals.mkMicroServoPart(xy);
|
||||
}
|
||||
|
||||
@ -114,8 +119,6 @@ namespace pxsim {
|
||||
initAsync(msg: SimulatorRunMessage): Promise<void> {
|
||||
super.initAsync(msg);
|
||||
|
||||
const options = (msg.options || {}) as RuntimeOptions;
|
||||
|
||||
const boardDef = msg.boardDefinition;
|
||||
const cmpsList = msg.parts;
|
||||
const cmpDefs = msg.partDefinitions || {};
|
||||
@ -143,6 +146,19 @@ namespace pxsim {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
tryGetNeopixelState(pinId: number): CommonNeoPixelState {
|
||||
return this.lightState[pinId];
|
||||
}
|
||||
|
||||
neopixelState(pinId: number): CommonNeoPixelState {
|
||||
if (pinId === undefined) {
|
||||
pinId = DAL.MICROBIT_ID_IO_P0;
|
||||
}
|
||||
let state = this.lightState[pinId];
|
||||
if (!state) state = this.lightState[pinId] = new CommonNeoPixelState();
|
||||
return state;
|
||||
}
|
||||
|
||||
screenshotAsync(width?: number): Promise<ImageData> {
|
||||
return this.viewHost.screenshotAsync(width);
|
||||
}
|
||||
@ -180,4 +196,15 @@ namespace pxsim {
|
||||
export function board(): DalBoard {
|
||||
return runtime.board as DalBoard;
|
||||
}
|
||||
}
|
||||
|
||||
export function parsePinString(gpioPin: string): Pin {
|
||||
if (gpioPin == "*")
|
||||
return board().edgeConnectorState.getPin(DAL.MICROBIT_ID_IO_P0);
|
||||
|
||||
const m = /^(Analog|Digital)Pin\.P(\d)+/.exec(gpioPin);
|
||||
if (!m)
|
||||
return undefined;
|
||||
const pinNum = parseInt(m[2]);
|
||||
return board().edgeConnectorState.pins[pinNum]
|
||||
}
|
||||
}
|
@ -16,6 +16,7 @@ namespace pxsim.input {
|
||||
|
||||
export function isGesture(gesture: number): boolean {
|
||||
const b = accForGesture(gesture);
|
||||
b.accelerometer.activate();
|
||||
return b.accelerometer.getGesture() == gesture;
|
||||
}
|
||||
|
||||
@ -33,28 +34,25 @@ namespace pxsim.input {
|
||||
acc.activate(AccelerometerFlag.Z);
|
||||
return acc.getZ();
|
||||
default:
|
||||
acc.activate();
|
||||
return Math.floor(Math.sqrt(acc.instantaneousAccelerationSquared()));
|
||||
acc.activate(AccelerometerFlag.Strength);
|
||||
return acc.getStrength();
|
||||
}
|
||||
}
|
||||
|
||||
export function rotation(kind: number): number {
|
||||
const b = board().accelerometerState;
|
||||
const acc = b.accelerometer;
|
||||
acc.activate();
|
||||
const x = acc.getX(MicroBitCoordinateSystem.NORTH_EAST_DOWN);
|
||||
const y = acc.getY(MicroBitCoordinateSystem.NORTH_EAST_DOWN);
|
||||
const z = acc.getZ(MicroBitCoordinateSystem.NORTH_EAST_DOWN);
|
||||
|
||||
const roll = Math.atan2(y, z);
|
||||
const 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;
|
||||
case 0: {
|
||||
acc.activate(AccelerometerFlag.Pitch);
|
||||
return acc.getPitch();
|
||||
}
|
||||
case 1: {
|
||||
acc.activate(AccelerometerFlag.Roll);
|
||||
return acc.getRoll();
|
||||
}
|
||||
default: return 0;
|
||||
}
|
||||
return Math.floor(r / Math.PI * 180);
|
||||
}
|
||||
|
||||
export function setAccelerometerRange(range: number) {
|
||||
@ -117,8 +115,11 @@ namespace pxsim {
|
||||
|
||||
export enum AccelerometerFlag {
|
||||
X = 1,
|
||||
Y = 2,
|
||||
Z = 4
|
||||
Y = 1 << 1,
|
||||
Z = 1 << 2,
|
||||
Strength = 1 << 3,
|
||||
Pitch = 1 << 4,
|
||||
Roll = 1 << 5
|
||||
}
|
||||
|
||||
export class Accelerometer {
|
||||
@ -148,7 +149,7 @@ namespace pxsim {
|
||||
this.isActive = true;
|
||||
this.runtime.queueDisplayUpdate();
|
||||
}
|
||||
if (flags)
|
||||
if (!!flags)
|
||||
this.flags |= flags;
|
||||
}
|
||||
|
||||
@ -169,7 +170,29 @@ namespace pxsim {
|
||||
board().bus.queue(this.id, DAL.MICROBIT_ACCELEROMETER_EVT_DATA_UPDATE)
|
||||
}
|
||||
|
||||
public instantaneousAccelerationSquared() {
|
||||
public getStrength() {
|
||||
return Math.floor(Math.sqrt(this.instantaneousAccelerationSquared()));
|
||||
}
|
||||
|
||||
updateEnvironmentGlobals() {
|
||||
// update debugger
|
||||
if (this.isActive) {
|
||||
if (this.flags & AccelerometerFlag.X)
|
||||
this.runtime.environmentGlobals[pxsim.localization.lf("acceleration.x")] = this.sample.x;
|
||||
if (this.flags & AccelerometerFlag.Y)
|
||||
this.runtime.environmentGlobals[pxsim.localization.lf("acceleration.y")] = this.sample.y;
|
||||
if (this.flags & AccelerometerFlag.Z)
|
||||
this.runtime.environmentGlobals[pxsim.localization.lf("acceleration.z")] = this.sample.z;
|
||||
if (this.flags & AccelerometerFlag.Strength)
|
||||
this.runtime.environmentGlobals[pxsim.localization.lf("acceleration.strength")] = Math.sqrt(this.instantaneousAccelerationSquared());
|
||||
if (this.flags & AccelerometerFlag.Pitch)
|
||||
this.runtime.environmentGlobals[pxsim.localization.lf("acceleration.pitch")] = this.getPitch();
|
||||
if (this.flags & AccelerometerFlag.Roll)
|
||||
this.runtime.environmentGlobals[pxsim.localization.lf("acceleration.roll")] = this.getRoll();
|
||||
}
|
||||
}
|
||||
|
||||
private 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;
|
||||
}
|
||||
@ -294,7 +317,6 @@ namespace pxsim {
|
||||
* @endcode
|
||||
*/
|
||||
public getX(system: MicroBitCoordinateSystem = MicroBitCoordinateSystem.SIMPLE_CARTESIAN): number {
|
||||
this.activate();
|
||||
switch (system) {
|
||||
case MicroBitCoordinateSystem.SIMPLE_CARTESIAN:
|
||||
return -this.sample.x;
|
||||
@ -319,7 +341,6 @@ namespace pxsim {
|
||||
* @endcode
|
||||
*/
|
||||
public getY(system: MicroBitCoordinateSystem = MicroBitCoordinateSystem.SIMPLE_CARTESIAN): number {
|
||||
this.activate();
|
||||
switch (system) {
|
||||
case MicroBitCoordinateSystem.SIMPLE_CARTESIAN:
|
||||
return -this.sample.y;
|
||||
@ -344,7 +365,6 @@ namespace pxsim {
|
||||
* @endcode
|
||||
*/
|
||||
public getZ(system: MicroBitCoordinateSystem = MicroBitCoordinateSystem.SIMPLE_CARTESIAN): number {
|
||||
this.activate();
|
||||
switch (system) {
|
||||
case MicroBitCoordinateSystem.NORTH_EAST_DOWN:
|
||||
return -this.sample.z;
|
||||
@ -365,7 +385,6 @@ namespace pxsim {
|
||||
* @endcode
|
||||
*/
|
||||
public getPitch(): number {
|
||||
this.activate();
|
||||
return Math.floor((360 * this.getPitchRadians()) / (2 * Math.PI));
|
||||
}
|
||||
|
||||
@ -384,7 +403,6 @@ namespace pxsim {
|
||||
* @endcode
|
||||
*/
|
||||
public getRoll(): number {
|
||||
this.activate();
|
||||
return Math.floor((360 * this.getRollRadians()) / (2 * Math.PI));
|
||||
}
|
||||
|
||||
|
@ -47,7 +47,7 @@ namespace pxsim.pins {
|
||||
export function setPull(pinId: number, pull: number) {
|
||||
let pin = getPin(pinId);
|
||||
if (!pin) return;
|
||||
pin.pull = pull;
|
||||
pin.setPull(pull);
|
||||
}
|
||||
|
||||
export function analogReadPin(pinId: number): number {
|
||||
@ -61,7 +61,7 @@ namespace pxsim.pins {
|
||||
let pin = getPin(pinId);
|
||||
if (!pin) return;
|
||||
pin.mode = PinFlags.Analog | PinFlags.Output;
|
||||
pin.value = value ? value : 0;
|
||||
pin.value = value | 0;
|
||||
runtime.queueDisplayUpdate();
|
||||
}
|
||||
|
||||
@ -101,22 +101,38 @@ namespace pxsim.pins {
|
||||
pin.pitch = true;
|
||||
}
|
||||
|
||||
export function analogSetPitchVolume(volume: number) {
|
||||
const ec = board().edgeConnectorState;
|
||||
ec.pitchVolume = Math.max(0, Math.min(0xff, volume | 0));
|
||||
}
|
||||
|
||||
export function analogPitchVolume() {
|
||||
const ec = board().edgeConnectorState;
|
||||
return ec.pitchVolume;
|
||||
}
|
||||
|
||||
export function analogPitch(frequency: number, ms: number) {
|
||||
// update analog output
|
||||
let pins = board().edgeConnectorState.pins;
|
||||
let pin = pins.filter(pin => !!pin && pin.pitch)[0] || pins[0];
|
||||
const b = board();
|
||||
if (!b) return;
|
||||
const ec = b.edgeConnectorState;
|
||||
const pins = ec.pins;
|
||||
const pin = pins.filter(pin => !!pin && pin.pitch)[0] || pins[0];
|
||||
const pitchVolume = ec.pitchVolume | 0;
|
||||
pin.mode = PinFlags.Analog | PinFlags.Output;
|
||||
if (frequency <= 0) {
|
||||
if (frequency <= 0 || pitchVolume <= 0) {
|
||||
pin.value = 0;
|
||||
pin.period = 0;
|
||||
} else {
|
||||
pin.value = 512;
|
||||
const v = 1 << (pitchVolume >> 5);
|
||||
pin.value = v;
|
||||
pin.period = 1000000 / frequency;
|
||||
}
|
||||
runtime.queueDisplayUpdate();
|
||||
|
||||
let cb = getResume();
|
||||
AudioContextManager.tone(frequency, 1);
|
||||
const v = pitchVolume / 0xff;
|
||||
AudioContextManager.tone(frequency, v);
|
||||
if (ms <= 0) cb();
|
||||
else {
|
||||
setTimeout(() => {
|
||||
@ -129,4 +145,11 @@ namespace pxsim.pins {
|
||||
}, ms);
|
||||
}
|
||||
}
|
||||
|
||||
export function pushButton(pinId: number) {
|
||||
const b = board();
|
||||
if (!b) return;
|
||||
const ec = b.edgeConnectorState;
|
||||
// TODO support buttons here
|
||||
}
|
||||
}
|
@ -86,9 +86,11 @@ namespace pxsim {
|
||||
|
||||
export class EdgeConnectorState {
|
||||
pins: Pin[];
|
||||
pitchVolume: number;
|
||||
|
||||
constructor(public props: EdgeConnectorProps) {
|
||||
this.pins = props.pins.map(id => id != undefined ? new Pin(id) : null);
|
||||
this.pitchVolume = 0xff
|
||||
}
|
||||
|
||||
public getPin(id: number) {
|
||||
|
@ -19,51 +19,52 @@ namespace pxsim {
|
||||
}
|
||||
|
||||
export class Image extends RefObject {
|
||||
public static height: number = 5;
|
||||
public height: number;
|
||||
public width: number;
|
||||
public data: number[];
|
||||
constructor(width: number, data: number[]) {
|
||||
super();
|
||||
this.width = width;
|
||||
this.data = data;
|
||||
this.height = (this.data.length / this.width) | 0;
|
||||
}
|
||||
public print() {
|
||||
console.debug(`Image id:${this.id} size:${this.width}x${Image.height}`)
|
||||
console.debug(`Image id:${this.id} size:${this.width}x${this.height}`)
|
||||
}
|
||||
public get(x: number, y: number): number {
|
||||
x = x >> 0;
|
||||
y = y >> 0;
|
||||
if (x < 0 || x >= this.width || y < 0 || y >= 5) return 0;
|
||||
x = x | 0;
|
||||
y = y | 0;
|
||||
if (x < 0 || x >= this.width || y < 0 || y >= this.height) return 0;
|
||||
return this.data[y * this.width + x];
|
||||
}
|
||||
public set(x: number, y: number, v: number) {
|
||||
x = x >> 0;
|
||||
y = y >> 0;
|
||||
if (x < 0 || x >= this.width || y < 0 || y >= 5) return;
|
||||
x = x | 0;
|
||||
y = y | 0;
|
||||
if (x < 0 || x >= this.width || y < 0 || y >= this.height) return;
|
||||
this.data[y * this.width + x] = Math.max(0, Math.min(255, v));
|
||||
}
|
||||
public copyTo(xSrcIndex: number, length: number, target: Image, xTargetIndex: number): void {
|
||||
xSrcIndex = xSrcIndex >> 0;
|
||||
length = length >> 0;
|
||||
xTargetIndex = xTargetIndex >> 0;
|
||||
xSrcIndex = xSrcIndex | 0;
|
||||
length = length | 0;
|
||||
xTargetIndex = xTargetIndex | 0;
|
||||
for (let x = 0; x < length; x++) {
|
||||
for (let y = 0; y < 5; y++) {
|
||||
for (let y = 0; y < this.height; y++) {
|
||||
let value = this.get(xSrcIndex + x, y);
|
||||
target.set(xTargetIndex + x, y, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
public shiftLeft(cols: number) {
|
||||
cols = cols >> 0;
|
||||
cols = cols | 0;
|
||||
for (let x = 0; x < this.width; ++x)
|
||||
for (let y = 0; y < 5; ++y)
|
||||
for (let y = 0; y < this.height; ++y)
|
||||
this.set(x, y, x < this.width - cols ? this.get(x + cols, y) : 0);
|
||||
}
|
||||
|
||||
public shiftRight(cols: number) {
|
||||
cols = cols >> 0;
|
||||
for (let x = this.width - 1; x >= 0; --x)
|
||||
for (let y = 0; y < 5; ++y)
|
||||
for (let y = 0; y < this.height; ++y)
|
||||
this.set(x, y, x >= cols ? this.get(x - cols, y) : 0);
|
||||
}
|
||||
|
||||
@ -85,7 +86,7 @@ namespace pxsim {
|
||||
}
|
||||
|
||||
export function createImageFromBuffer(data: number[]): Image {
|
||||
return new Image(data.length / 5, data);
|
||||
return new Image((data.length / 5) | 0, data);
|
||||
}
|
||||
|
||||
export function createImageFromString(text: string): Image {
|
||||
@ -110,15 +111,14 @@ namespace pxsim {
|
||||
return sprite;
|
||||
}
|
||||
|
||||
export const FONT_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];
|
||||
export function createFont(): Image {
|
||||
const 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 nb = FONT_DATA.length;
|
||||
let n = nb / 5;
|
||||
let font = createInternalImage(nb);
|
||||
for (let c = 0; c < n; c++) {
|
||||
for (let row = 0; row < 5; row++) {
|
||||
let char = data[c * 5 + row];
|
||||
let char = FONT_DATA[c * 5 + row];
|
||||
for (let col = 0; col < 5; col++) {
|
||||
if ((char & (1 << col)) != 0)
|
||||
font.set((c * 5 + 4) - col, row, 255);
|
||||
@ -129,6 +129,24 @@ namespace pxsim {
|
||||
}
|
||||
}
|
||||
|
||||
namespace pxsim.fonts {
|
||||
export function charCodeBuffer(charCode: number): RefBuffer {
|
||||
if (charCode < DAL.MICROBIT_FONT_ASCII_START || charCode > DAL.MICROBIT_FONT_ASCII_END)
|
||||
return undefined;
|
||||
|
||||
const b = board();
|
||||
const led = b.ledMatrixState;
|
||||
const font = led.font;
|
||||
const h = font.height;
|
||||
const w = font.width;
|
||||
const buf = control.createBuffer(h);
|
||||
const offset = (charCode - DAL.MICROBIT_FONT_ASCII_START) * h;
|
||||
for (let row = 0; row < h; ++row)
|
||||
buf.data[row] = FONT_DATA[offset + row];
|
||||
return buf;
|
||||
}
|
||||
}
|
||||
|
||||
namespace pxsim.images {
|
||||
export function createImage(img: Image) {
|
||||
return img
|
||||
@ -177,7 +195,7 @@ namespace pxsim.ImageMethods {
|
||||
|
||||
export function height(leds: Image): number {
|
||||
pxtrt.nullCheck(leds)
|
||||
return Image.height;
|
||||
return leds.height;
|
||||
}
|
||||
|
||||
export function width(leds: Image): number {
|
||||
@ -186,11 +204,11 @@ namespace pxsim.ImageMethods {
|
||||
}
|
||||
|
||||
export function plotFrame(leds: Image, frame: number) {
|
||||
ImageMethods.plotImage(leds, frame * Image.height);
|
||||
ImageMethods.plotImage(leds, frame * leds.height);
|
||||
}
|
||||
|
||||
export function showFrame(leds: Image, frame: number, interval: number) {
|
||||
ImageMethods.showImage(leds, frame * Image.height, interval);
|
||||
ImageMethods.showImage(leds, frame * leds.height, interval);
|
||||
}
|
||||
|
||||
export function pixel(leds: Image, x: number, y: number): number {
|
||||
@ -268,7 +286,7 @@ namespace pxsim.ImageMethods {
|
||||
|
||||
namespace pxsim.basic {
|
||||
export function showNumber(x: number, interval: number) {
|
||||
interval = interval >> 0;
|
||||
interval |= 0;
|
||||
if (interval <= 0)
|
||||
interval = 1;
|
||||
let leds = createImageFromString(x.toString());
|
||||
@ -277,7 +295,7 @@ namespace pxsim.basic {
|
||||
}
|
||||
|
||||
export function showString(s: string, interval: number) {
|
||||
interval = interval >> 0;
|
||||
interval |= 0;
|
||||
if (interval <= 0)
|
||||
interval = 1;
|
||||
if (s.length == 0) {
|
||||
@ -291,6 +309,7 @@ namespace pxsim.basic {
|
||||
}
|
||||
|
||||
export function showLeds(leds: Image, interval: number): void {
|
||||
interval |= 0;
|
||||
showAnimation(leds, interval);
|
||||
}
|
||||
|
||||
@ -300,6 +319,7 @@ namespace pxsim.basic {
|
||||
}
|
||||
|
||||
export function showAnimation(leds: Image, interval: number): void {
|
||||
interval |= 0;
|
||||
ImageMethods.scrollImage(leds, 5, interval);
|
||||
}
|
||||
|
||||
@ -310,13 +330,17 @@ namespace pxsim.basic {
|
||||
|
||||
namespace pxsim.led {
|
||||
export function plot(x: number, y: number) {
|
||||
x |= 0;
|
||||
y |= 0;
|
||||
board().ledMatrixState.image.set(x, y, 0xff);
|
||||
runtime.queueDisplayUpdate()
|
||||
}
|
||||
|
||||
export function plotBrightness(x: number, y: number, brightness: number) {
|
||||
x |= 0;
|
||||
y |= 0;
|
||||
const state = board().ledMatrixState;
|
||||
brightness = brightness >> 0;
|
||||
brightness |= 0;
|
||||
brightness = Math.max(0, Math.min(led.brightness(), brightness));
|
||||
if (brightness != 0 && brightness != 0xff && state.displayMode != DisplayMode.greyscale)
|
||||
state.displayMode = DisplayMode.greyscale;
|
||||
@ -325,12 +349,16 @@ namespace pxsim.led {
|
||||
}
|
||||
|
||||
export function unplot(x: number, y: number) {
|
||||
x |= 0;
|
||||
y |= 0;
|
||||
board().ledMatrixState.image.set(x, y, 0);
|
||||
runtime.queueDisplayUpdate()
|
||||
}
|
||||
|
||||
export function point(x: number, y: number): boolean {
|
||||
return !!board().ledMatrixState.image.get(x, y);
|
||||
export function pointBrightness(x: number, y: number): number {
|
||||
x |= 0;
|
||||
y |= 0;
|
||||
return board().ledMatrixState.image.get(x, y);
|
||||
}
|
||||
|
||||
export function brightness(): number {
|
||||
@ -338,7 +366,7 @@ namespace pxsim.led {
|
||||
}
|
||||
|
||||
export function setBrightness(value: number): void {
|
||||
value = value >> 0;
|
||||
value |= 0;
|
||||
board().ledMatrixState.brigthness = Math.max(0, Math.min(255, value));
|
||||
runtime.queueDisplayUpdate()
|
||||
}
|
||||
|
@ -136,6 +136,10 @@ namespace pxsim.pins {
|
||||
return 0;
|
||||
}
|
||||
|
||||
export function spiTransfer(cmd: RefBuffer, resp: RefBuffer): void {
|
||||
// TODO
|
||||
}
|
||||
|
||||
export function spiFrequency(f: number): void {
|
||||
// TODO
|
||||
}
|
||||
@ -219,7 +223,7 @@ namespace pxsim.bluetooth {
|
||||
}
|
||||
|
||||
export function uartReadBuffer(): RefBuffer {
|
||||
return pins.createBuffer(0);
|
||||
return pins.createBuffer(0);
|
||||
}
|
||||
|
||||
export function uartReadUntil(del: string): string {
|
||||
@ -241,3 +245,14 @@ namespace pxsim.bluetooth {
|
||||
export function setTransmitPower(power: number) { }
|
||||
}
|
||||
|
||||
namespace pxsim.light {
|
||||
export function sendWS2812Buffer(buffer: RefBuffer, pin: number) {
|
||||
pxsim.sendBufferAsm(buffer, pin)
|
||||
}
|
||||
|
||||
export function setMode(pin: number, mode: number) {
|
||||
const lp = neopixelState(pin);
|
||||
if (!lp) return;
|
||||
lp.mode = mode & 0xff;
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +0,0 @@
|
||||
namespace pxsim {
|
||||
export function sendBufferAsm(buffer: RefBuffer, pin: DigitalPin) {
|
||||
let b = board();
|
||||
if (b) {
|
||||
let np = b.neopixelState;
|
||||
if (np) {
|
||||
let buf = buffer.data;
|
||||
np.updateBuffer(buf as any, pin); // TODO this is wrong
|
||||
runtime.queueDisplayUpdate();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -73,8 +73,13 @@ namespace pxsim.serial {
|
||||
}
|
||||
|
||||
export function readBuffer(length: number) {
|
||||
length |= 0;
|
||||
if (length <= 0)
|
||||
length = 64;
|
||||
return pins.createBuffer(length);
|
||||
}
|
||||
|
||||
export function setBaudRate(rate: number) {
|
||||
// TODO
|
||||
}
|
||||
}
|
@ -16,6 +16,8 @@
|
||||
"*.ts",
|
||||
"state/*.ts",
|
||||
"visuals/*.ts",
|
||||
"../node_modules/pxt-common-packages/libs/core/sim/analogSensor.ts",
|
||||
"../node_modules/pxt-common-packages/libs/core/sim/neopixel.ts",
|
||||
"../node_modules/pxt-common-packages/libs/radio/sim/*.ts"
|
||||
]
|
||||
}
|
||||
|
@ -3,8 +3,7 @@ namespace pxsim.visuals {
|
||||
return new visuals.MicrobitBoardSvg({
|
||||
runtime: runtime,
|
||||
theme: visuals.randomTheme(),
|
||||
disableTilt: false,
|
||||
wireframe: opts.wireframe,
|
||||
wireframe: opts.wireframe
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -140,9 +140,31 @@ namespace pxsim.visuals {
|
||||
stroke: none;
|
||||
fill: #777;
|
||||
}
|
||||
.sim-label, .sim-button-label {
|
||||
fill: #000;
|
||||
}
|
||||
.sim-wireframe .sim-board {
|
||||
stroke-width: 2px;
|
||||
}
|
||||
*:focus {
|
||||
outline: none;
|
||||
}
|
||||
*:focus .sim-button-outer,
|
||||
.sim-pin:focus,
|
||||
.sim-thermometer:focus,
|
||||
.sim-shake:focus,
|
||||
.sim-light-level-button:focus {
|
||||
stroke: #4D90FE;
|
||||
stroke-width: 5px !important;
|
||||
}
|
||||
.no-drag, .sim-text, .sim-text-pin {
|
||||
user-drag: none;
|
||||
user-select: none;
|
||||
-moz-user-select: none;
|
||||
-webkit-user-drag: none;
|
||||
-webkit-user-select: none;
|
||||
-ms-user-select: none;
|
||||
}
|
||||
`;
|
||||
const BOARD_SVG = `<?xml version="1.0" encoding="utf-8"?>
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px"
|
||||
@ -692,8 +714,8 @@ namespace pxsim.visuals {
|
||||
export interface IBoardProps {
|
||||
runtime?: pxsim.Runtime;
|
||||
theme?: IBoardTheme;
|
||||
disableTilt?: boolean;
|
||||
wireframe?: boolean;
|
||||
disableTilt?: boolean;
|
||||
}
|
||||
|
||||
export class MicrobitBoardSvg implements BoardView {
|
||||
@ -711,6 +733,7 @@ namespace pxsim.visuals {
|
||||
private leds: SVGElement[];
|
||||
private systemLed: SVGCircleElement;
|
||||
private antenna: SVGPolylineElement;
|
||||
private rssi: SVGTextElement;
|
||||
private lightLevelButton: SVGCircleElement;
|
||||
private lightLevelGradient: SVGLinearGradientElement;
|
||||
private lightLevelText: SVGTextElement;
|
||||
@ -925,6 +948,7 @@ namespace pxsim.visuals {
|
||||
};
|
||||
|
||||
constructor(public props: IBoardProps) {
|
||||
|
||||
this.buildDom();
|
||||
if (props && props.wireframe)
|
||||
U.addClass(this.element, "sim-wireframe");
|
||||
@ -968,7 +992,6 @@ namespace pxsim.visuals {
|
||||
const r = p.getBoundingClientRect();
|
||||
this.pinNmToCoord[nm] = [r.left + r.width / 2, r.top + r.height / 2];
|
||||
});
|
||||
console.log(JSON.stringify(this.pinNmToCoord, null, 2))
|
||||
}
|
||||
|
||||
private updateTheme() {
|
||||
@ -981,6 +1004,7 @@ namespace pxsim.visuals {
|
||||
svg.fill(this.buttons[1], theme.buttonUps[1]);
|
||||
svg.fill(this.buttonsOuter[2], theme.virtualButtonOuter);
|
||||
svg.fill(this.buttons[2], theme.virtualButtonUp);
|
||||
if (this.shakeButton) svg.fill(this.shakeButton, theme.virtualButtonUp);
|
||||
|
||||
this.pinGradients.forEach(lg => svg.setGradientColors(lg, theme.pin, theme.pinActive));
|
||||
svg.setGradientColors(this.lightLevelGradient, theme.lightLevelOn, theme.lightLevelOff);
|
||||
@ -1031,6 +1055,7 @@ namespace pxsim.visuals {
|
||||
this.updateGestures();
|
||||
this.updateRgbLed();
|
||||
this.updateSpeaker();
|
||||
this.updateRSSI();
|
||||
|
||||
if (!runtime || runtime.dead) U.addClass(this.element, "grayscale");
|
||||
else U.removeClass(this.element, "grayscale");
|
||||
@ -1092,6 +1117,33 @@ namespace pxsim.visuals {
|
||||
}
|
||||
}
|
||||
|
||||
private updateRSSI() {
|
||||
let state = this.board;
|
||||
if (!state) return;
|
||||
const v = state.radioState.datagram.rssi;
|
||||
if (v === undefined) return;
|
||||
|
||||
if (!this.rssi) {
|
||||
let ax = 380;
|
||||
let dax = 18;
|
||||
let ayt = 10;
|
||||
let ayb = 40;
|
||||
const wh = dax * 5;
|
||||
for (let i = 0; i < 4; ++i)
|
||||
svg.child(this.g, "rect", { x: ax - 90 + i * 6, y: ayt + 28 - i * 4, width: 4, height: 2 + i * 4, fill: "#fff" })
|
||||
this.rssi = svg.child(this.g, "text", { x: ax - 64, y: ayb, class: "sim-text" }) as SVGTextElement;
|
||||
this.rssi.textContent = "";
|
||||
}
|
||||
|
||||
const vt = v.toString();
|
||||
if (vt !== this.rssi.textContent) {
|
||||
this.rssi.textContent = v.toString();
|
||||
this.antenna.setAttribute("aria-valuenow", this.rssi.textContent);
|
||||
accessibility.setLiveContent(this.rssi.textContent);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private updatePin(pin: Pin, index: number) {
|
||||
if (!pin) return;
|
||||
let text = this.pinTexts[pin.id];
|
||||
@ -1106,12 +1158,22 @@ namespace pxsim.visuals {
|
||||
}
|
||||
else if (pin.mode & PinFlags.Touch) {
|
||||
v = pin.touched ? "0%" : "100%";
|
||||
if (text) text.textContent = "TOUCHED";
|
||||
if (text) text.textContent = "";
|
||||
} else {
|
||||
v = "100%";
|
||||
if (text) text.textContent = "unused";
|
||||
if (text) text.textContent = "";
|
||||
}
|
||||
if (v) svg.setGradientValue(this.pinGradients[index], v);
|
||||
|
||||
if (pin.mode !== PinFlags.Unused) {
|
||||
accessibility.makeFocusable(this.pins[index]);
|
||||
accessibility.setAria(this.pins[index], "slider", this.pins[index].firstChild.textContent);
|
||||
this.pins[index].setAttribute("aria-valuemin", "0");
|
||||
this.pins[index].setAttribute("aria-valuemax", pin.mode & PinFlags.Analog ? "1023" : "100");
|
||||
this.pins[index].setAttribute("aria-orientation", "vertical");
|
||||
this.pins[index].setAttribute("aria-valuenow", text ? text.textContent : v);
|
||||
accessibility.setLiveContent(text ? text.textContent : v);
|
||||
}
|
||||
}
|
||||
|
||||
private updateTemperature() {
|
||||
|
@ -1,242 +0,0 @@
|
||||
/// <reference path="../../node_modules/pxt-core/built/pxtsim.d.ts"/>
|
||||
/// <reference path="../../libs/core/dal.d.ts"/>
|
||||
/// <reference path="../../libs/core/enums.d.ts"/>
|
||||
|
||||
namespace pxsim.visuals {
|
||||
const PIXEL_SPACING = PIN_DIST * 3;
|
||||
const PIXEL_RADIUS = PIN_DIST;
|
||||
const CANVAS_WIDTH = 1.2 * PIN_DIST;
|
||||
const CANVAS_HEIGHT = 12 * PIN_DIST;
|
||||
const CANVAS_VIEW_WIDTH = CANVAS_WIDTH;
|
||||
const CANVAS_VIEW_HEIGHT = CANVAS_HEIGHT;
|
||||
const CANVAS_VIEW_PADDING = PIN_DIST * 4;
|
||||
const CANVAS_LEFT = 1.4 * PIN_DIST;
|
||||
const CANVAS_TOP = PIN_DIST;
|
||||
|
||||
// For the instructions parts list
|
||||
export function mkNeoPixelPart(xy: Coord = [0, 0]): SVGElAndSize {
|
||||
const NP_PART_XOFF = -13.5;
|
||||
const NP_PART_YOFF = -11;
|
||||
const NP_PART_WIDTH = 87.5;
|
||||
const NP_PART_HEIGHT = 190;
|
||||
const NEOPIXEL_PART_IMG = `<svg viewBox="-5 -1 53 112" xmlns="http://www.w3.org/2000/svg" xmlns:bx="https://boxy-svg.com">
|
||||
<rect x="2.5" width="38" height="100" style="fill: rgb(68, 68, 68);"/>
|
||||
<rect x="11.748" y="3.2" width="1.391" height="2.553" style="fill: none; stroke-linejoin: round; stroke-width: 3; stroke: rgb(165, 103, 52);"/>
|
||||
<rect x="20.75" y="3.2" width="1.391" height="2.553" style="fill: none; stroke-linejoin: round; stroke-width: 3; stroke: rgb(165, 103, 52);"/>
|
||||
<rect x="29.75" y="3.2" width="1.391" height="2.553" style="fill: none; stroke-linejoin: round; stroke-width: 3; stroke: rgb(165, 103, 52);"/>
|
||||
<g>
|
||||
<rect x="9" y="16.562" width="25" height="3.238" style="fill: rgb(216, 216, 216);"/>
|
||||
<rect x="9" y="22.562" width="25" height="3.238" style="fill: rgb(216, 216, 216);"/>
|
||||
<rect x="9" y="28.563" width="25" height="3.238" style="fill: rgb(216, 216, 216);"/>
|
||||
<rect x="11.607" y="14.833" width="19.787" height="18.697" style="fill: rgb(0, 0, 0);"/>
|
||||
<ellipse style="fill: rgb(216, 216, 216);" cx="21.5" cy="24.181" rx="7" ry="7"/>
|
||||
</g>
|
||||
<path d="M -7.25 -103.2 L -2.5 -100.003 L -12 -100.003 L -7.25 -103.2 Z" style="fill: rgb(68, 68, 68);" transform="matrix(-1, 0, 0, -1, 0, 0)" bx:shape="triangle -12 -103.2 9.5 3.197 0.5 0 1@ad6f5cac"/>
|
||||
<path d="M -16.75 -103.197 L -12 -100 L -21.5 -100 L -16.75 -103.197 Z" style="fill: rgb(68, 68, 68);" transform="matrix(-1, 0, 0, -1, 0, 0)" bx:shape="triangle -21.5 -103.197 9.5 3.197 0.5 0 1@07d73149"/>
|
||||
<path d="M -26.25 -103.2 L -21.5 -100.003 L -31 -100.003 L -26.25 -103.2 Z" style="fill: rgb(68, 68, 68);" transform="matrix(-1, 0, 0, -1, 0, 0)" bx:shape="triangle -31 -103.2 9.5 3.197 0.5 0 1@54403e2d"/>
|
||||
<path d="M -35.75 -103.197 L -31 -100 L -40.5 -100 L -35.75 -103.197 Z" style="fill: rgb(68, 68, 68);" transform="matrix(-1, 0, 0, -1, 0, 0)" bx:shape="triangle -40.5 -103.197 9.5 3.197 0.5 0 1@21c9b772"/>
|
||||
<g transform="matrix(1, 0, 0, 1, 0.000002, 29.999994)">
|
||||
<rect x="9" y="16.562" width="25" height="3.238" style="fill: rgb(216, 216, 216);"/>
|
||||
<rect x="9" y="22.562" width="25" height="3.238" style="fill: rgb(216, 216, 216);"/>
|
||||
<rect x="9" y="28.563" width="25" height="3.238" style="fill: rgb(216, 216, 216);"/>
|
||||
<rect x="11.607" y="14.833" width="19.787" height="18.697" style="fill: rgb(0, 0, 0);"/>
|
||||
<ellipse style="fill: rgb(216, 216, 216);" cx="21.5" cy="24.181" rx="7" ry="7"/>
|
||||
</g>
|
||||
<g transform="matrix(1, 0, 0, 1, 0.000005, 59.999992)">
|
||||
<rect x="9" y="16.562" width="25" height="3.238" style="fill: rgb(216, 216, 216);"/>
|
||||
<rect x="9" y="22.562" width="25" height="3.238" style="fill: rgb(216, 216, 216);"/>
|
||||
<rect x="9" y="28.563" width="25" height="3.238" style="fill: rgb(216, 216, 216);"/>
|
||||
<rect x="11.607" y="14.833" width="19.787" height="18.697" style="fill: rgb(0, 0, 0);"/>
|
||||
<ellipse style="fill: rgb(216, 216, 216);" cx="21.5" cy="24.181" rx="7" ry="7"/>
|
||||
</g>
|
||||
</svg>`;
|
||||
let [x, y] = xy;
|
||||
let l = x + NP_PART_XOFF;
|
||||
let t = y + NP_PART_YOFF;
|
||||
let w = NP_PART_WIDTH;
|
||||
let h = NP_PART_HEIGHT;
|
||||
let img = <SVGImageElement>svg.elt("image");
|
||||
svg.hydrate(img, {
|
||||
class: "sim-neopixel-strip", x: l, y: t, width: w, height: h,
|
||||
href: svg.toDataUri(NEOPIXEL_PART_IMG)
|
||||
});
|
||||
return { el: img, x: l, y: t, w: w, h: h };
|
||||
}
|
||||
export class NeoPixel {
|
||||
public el: SVGElement;
|
||||
public cy: number;
|
||||
|
||||
constructor(xy: Coord = [0, 0]) {
|
||||
let el = <SVGElement>svg.elt("rect");
|
||||
let r = PIXEL_RADIUS;
|
||||
let [cx, cy] = xy;
|
||||
let y = cy - r;
|
||||
svg.hydrate(el, { x: "-50%", y: y, width: "100%", height: r * 2, class: "sim-neopixel" });
|
||||
this.el = el;
|
||||
this.cy = cy;
|
||||
}
|
||||
|
||||
public setRgb(rgb: [number, number, number]) {
|
||||
let hsl = visuals.rgbToHsl(rgb);
|
||||
let [h, s, l] = hsl;
|
||||
// at least 70% luminosity
|
||||
l = Math.max(l, 60);
|
||||
let fill = `hsl(${h}, ${s}%, ${l}%)`;
|
||||
this.el.setAttribute("fill", fill);
|
||||
}
|
||||
}
|
||||
|
||||
export class NeoPixelCanvas {
|
||||
public canvas: SVGSVGElement;
|
||||
public pin: number;
|
||||
public pixels: NeoPixel[];
|
||||
private viewBox: [number, number, number, number];
|
||||
private background: SVGRectElement;
|
||||
|
||||
constructor(pin: number) {
|
||||
this.pixels = [];
|
||||
this.pin = pin;
|
||||
let el = <SVGSVGElement>svg.elt("svg");
|
||||
svg.hydrate(el, {
|
||||
"class": `sim-neopixel-canvas`,
|
||||
"x": "0px",
|
||||
"y": "0px",
|
||||
"width": `${CANVAS_WIDTH}px`,
|
||||
"height": `${CANVAS_HEIGHT}px`,
|
||||
});
|
||||
this.canvas = el;
|
||||
this.background = <SVGRectElement>svg.child(el, "rect", { class: "sim-neopixel-background hidden" });
|
||||
this.updateViewBox(-CANVAS_VIEW_WIDTH / 2, 0, CANVAS_VIEW_WIDTH, CANVAS_VIEW_HEIGHT);
|
||||
}
|
||||
|
||||
private updateViewBox(x: number, y: number, w: number, h: number) {
|
||||
this.viewBox = [x, y, w, h];
|
||||
svg.hydrate(this.canvas, { "viewBox": `${x} ${y} ${w} ${h}` });
|
||||
svg.hydrate(this.background, { "x": x, "y": y, "width": w, "height": h });
|
||||
}
|
||||
|
||||
public update(colors: RGBW[]) {
|
||||
if (!colors || colors.length <= 0)
|
||||
return;
|
||||
|
||||
for (let i = 0; i < colors.length; i++) {
|
||||
let pixel = this.pixels[i];
|
||||
if (!pixel) {
|
||||
let cxy: Coord = [0, CANVAS_VIEW_PADDING + i * PIXEL_SPACING];
|
||||
pixel = this.pixels[i] = new NeoPixel(cxy);
|
||||
svg.hydrate(pixel.el, { title: `offset: ${i}` });
|
||||
this.canvas.appendChild(pixel.el);
|
||||
}
|
||||
let color = colors[i];
|
||||
pixel.setRgb([color[0], color[1], color[2]]);
|
||||
}
|
||||
|
||||
//show the canvas if it's hidden
|
||||
U.removeClass(this.background, "hidden");
|
||||
|
||||
//resize if necessary
|
||||
let [first, last] = [this.pixels[0], this.pixels[this.pixels.length - 1]]
|
||||
let yDiff = last.cy - first.cy;
|
||||
let newH = yDiff + CANVAS_VIEW_PADDING * 2;
|
||||
let [oldX, oldY, oldW, oldH] = this.viewBox;
|
||||
if (oldH < newH) {
|
||||
let scalar = newH / oldH;
|
||||
let newW = oldW * scalar;
|
||||
this.updateViewBox(-newW / 2, oldY, newW, newH);
|
||||
}
|
||||
}
|
||||
|
||||
public setLoc(xy: Coord) {
|
||||
let [x, y] = xy;
|
||||
svg.hydrate(this.canvas, { x: x, y: y });
|
||||
}
|
||||
};
|
||||
|
||||
function digitalPinToPinNumber(gpioPin: string): number {
|
||||
const MICROBIT_ID_IO_P0 = 7; //TODO: don't hardcode this, import enums.d.ts
|
||||
if (gpioPin == "*") {
|
||||
return MICROBIT_ID_IO_P0;
|
||||
}
|
||||
let pinSplit = gpioPin.split("DigitalPin.P");
|
||||
U.assert(pinSplit.length === 2, "Unknown format for pin (for NeoPixel): " + gpioPin);
|
||||
let pinNumStr = pinSplit[1];
|
||||
let pinNum = Number(pinNumStr) + MICROBIT_ID_IO_P0;
|
||||
return pinNum
|
||||
}
|
||||
function parseNeoPixelMode(modeStr: string): NeoPixelMode {
|
||||
const modeMap: Map<NeoPixelMode> = {
|
||||
"NeoPixelMode.RGB": NeoPixelMode.RGB,
|
||||
"NeoPixelMode.RGBW": NeoPixelMode.RGBW
|
||||
};
|
||||
return modeMap[modeStr] || NeoPixelMode.RGB;
|
||||
}
|
||||
|
||||
export class NeoPixelView implements IBoardPart<NeoPixelState> {
|
||||
public style: string = `
|
||||
.sim-neopixel-canvas {
|
||||
}
|
||||
.sim-neopixel-canvas-parent:hover {
|
||||
transform-origin: center;
|
||||
transform: scale(4) translateY(-60px);
|
||||
-moz-transform: scale(4) translateY(-220px);
|
||||
}
|
||||
.sim-neopixel-canvas .hidden {
|
||||
visibility:hidden;
|
||||
}
|
||||
.sim-neopixel-background {
|
||||
fill: rgba(255,255,255,0.9);
|
||||
}
|
||||
.sim-neopixel-strip {
|
||||
}
|
||||
`;
|
||||
public element: SVGElement;
|
||||
public overElement: SVGElement;
|
||||
public defs: SVGElement[];
|
||||
private state: NeoPixelState;
|
||||
private canvas: NeoPixelCanvas;
|
||||
private part: SVGElAndSize;
|
||||
private stripGroup: SVGGElement;
|
||||
private lastLocation: Coord;
|
||||
private pin: number;
|
||||
private mode: NeoPixelMode;
|
||||
|
||||
public init(bus: EventBus, state: NeoPixelState, svgEl: SVGSVGElement, otherParams: Map<string>): void {
|
||||
U.assert(!!otherParams["mode"], "NeoPixels assumes a RGB vs RGBW mode is passed to it");
|
||||
U.assert(!!otherParams["pin"], "NeoPixels assumes a pin is passed to it");
|
||||
let modeStr = otherParams["mode"];
|
||||
this.mode = parseNeoPixelMode(modeStr);
|
||||
this.state = state;
|
||||
this.stripGroup = <SVGGElement>svg.elt("g");
|
||||
this.element = this.stripGroup;
|
||||
let pinStr = otherParams["pin"];
|
||||
this.pin = digitalPinToPinNumber(pinStr);
|
||||
this.lastLocation = [0, 0];
|
||||
let part = mkNeoPixelPart();
|
||||
this.part = part;
|
||||
this.stripGroup.appendChild(part.el);
|
||||
let canvas = new NeoPixelCanvas(this.pin);
|
||||
this.canvas = canvas;
|
||||
let canvasG = svg.elt("g", { class: "sim-neopixel-canvas-parent" });
|
||||
this.overElement = canvasG;
|
||||
canvasG.appendChild(canvas.canvas);
|
||||
this.updateStripLoc();
|
||||
}
|
||||
public moveToCoord(xy: Coord): void {
|
||||
let [x, y] = xy;
|
||||
let loc: Coord = [x, y];
|
||||
this.lastLocation = loc;
|
||||
this.updateStripLoc();
|
||||
}
|
||||
private updateStripLoc() {
|
||||
let [x, y] = this.lastLocation;
|
||||
U.assert(typeof x === "number" && typeof y === "number", "invalid x,y for NeoPixel strip");
|
||||
this.canvas.setLoc([x + CANVAS_LEFT, y + CANVAS_TOP]);
|
||||
svg.hydrate(this.part.el, { transform: `translate(${x} ${y})` }); //TODO: update part's l,h, etc.
|
||||
}
|
||||
public updateState(): void {
|
||||
let colors = this.state.getColors(this.pin, this.mode);
|
||||
this.canvas.update(colors);
|
||||
}
|
||||
public updateTheme(): void { }
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user