new part definitions (#247)

* working on new part definitions

* draft of new part definitions

* updates comments

* starting new allocator

* starting from the old allocator

* alloc internals renaming

* alloc minor renaming

* alloc internal renaming

* progress on new parts definition

* progress on new part defs allocator

* refactors BBLoc; progress on new allocator

* more progress on new allocator

* finishing new allocator

* deleting old allocator

* moves new allocator and part definitions

* porting to new part definitions

* refactors instructions for new definitions

* debugging new allocator

* fixes ground and power wire colros

* fixing new part definition bugs

* fixes wire end offsets; fixes NeoPixel placement

* fixes colorGroup issue

* fixes led matrix wiring

* naming tweaks

* fixes instructions regressions

* typo
This commit is contained in:
Daryl Zuniga 2016-09-09 01:23:39 -07:00 committed by Peli de Halleux
parent d8fc11a688
commit c63e2c85f1
13 changed files with 753 additions and 557 deletions

View File

@ -1,78 +1,126 @@
{
"ledmatrix": {
"visual": "ledmatrix",
"breadboardColumnsNeeded": 8,
"breadboardStartRow": "h",
"pinAllocation": {
"type": "auto",
"gpioPinsNeeded": [5, 5]
"buttonpair": {
"simulationBehavior": "buttonpair",
"visual": {
"builtIn": "buttonpair",
"width": 75,
"height": 45,
"pinDistance": 15,
"pinLocations": [
{"x": 0, "y": 0},
{"x": 30, "y": 45},
{"x": 45, "y": 0},
{"x": 75, "y": 45}
]
},
"assemblyStep": 0,
"wires": [
{"start": ["breadboard", "j", 0], "end": ["GPIO", 5], "color": "purple", "assemblyStep": 1},
{"start": ["breadboard", "j", 1], "end": ["GPIO", 6], "color": "purple", "assemblyStep": 1},
{"start": ["breadboard", "j", 2], "end": ["GPIO", 7], "color": "purple", "assemblyStep": 1},
{"start": ["breadboard", "j", 3], "end": ["GPIO", 8], "color": "purple", "assemblyStep": 1},
{"start": ["breadboard", "a", 7], "end": ["GPIO", 9], "color": "purple", "assemblyStep": 1},
{"start": ["breadboard", "a", 0], "end": ["GPIO", 0], "color": "green", "assemblyStep": 2},
{"start": ["breadboard", "a", 1], "end": ["GPIO", 1], "color": "green", "assemblyStep": 2},
{"start": ["breadboard", "a", 2], "end": ["GPIO", 2], "color": "green", "assemblyStep": 2},
{"start": ["breadboard", "a", 3], "end": ["GPIO", 3], "color": "green", "assemblyStep": 2},
{"start": ["breadboard", "j", 4], "end": ["GPIO", 4], "color": "green", "assemblyStep": 2}
]
},
"buttonpair": {
"visual": "buttonpair",
"breadboardColumnsNeeded": 6,
"breadboardStartRow": "f",
"pinAllocation": {
"type": "predefined",
"pins": ["P13", "P12"]
"numberOfPins": 4,
"pinDefinitions": [
{"target": "P14", "style": "male", "orientation": "-Z"},
{"target": "ground", "style": "male", "orientation": "-Z"},
{"target": "P15", "style": "male", "orientation": "-Z"},
{"target": "ground", "style": "male", "orientation": "-Z"}
],
"instantiation": {
"kind": "singleton"
},
"assemblyStep": 0,
"wires": [
{"start": ["breadboard", "j", 0], "end": ["GPIO", 0], "color": "yellow", "assemblyStep": 1},
{"start": ["breadboard", "a", 2], "end": "ground", "color": "blue", "assemblyStep": 1},
{"start": ["breadboard", "j", 3], "end": ["GPIO", 1], "color": "orange", "assemblyStep": 2},
{"start": ["breadboard", "a", 5], "end": "ground", "color": "blue", "assemblyStep": 2}
"assembly": [
{"part": true},
{"pinIndices": [0, 1]},
{"pinIndices": [2, 3]}
]
},
"neopixel": {
"visual": "neopixel",
"breadboardColumnsNeeded": 5,
"breadboardStartRow": "h",
"pinAllocation": {
"type": "factoryfunction",
"functionName": "neopixel.create",
"pinArgPositions": [0],
"otherArgPositions": [1]
"simulationBehavior": "neopixel",
"visual": {
"builtIn": "neopixel",
"width": 58,
"height": 113,
"pinDistance": 9,
"pinLocations": [
{"x": 10, "y": 0},
{"x": 19, "y": 0},
{"x": 28, "y": 0}
]
},
"assemblyStep": 0,
"wires": [
{"start": ["breadboard", "j", 1], "end": "ground", "color": "blue", "assemblyStep": 1},
{"start": ["breadboard", "j", 2], "end": "threeVolt", "color": "red", "assemblyStep": 2},
{"start": ["breadboard", "j", 3], "end": ["GPIO", 0], "color": "green", "assemblyStep": 2}
"numberOfPins": 3,
"pinDefinitions": [
{"target": {"pinInstantiationIdx": 0}, "style": "solder", "orientation": "+Z"},
{"target": "threeVolt", "style": "solder", "orientation": "+Z"},
{"target": "ground", "style": "solder", "orientation": "+Z"}
],
"instantiation": {
"kind": "function",
"fullyQualifiedName": "neopixel.create",
"argumentRoles": [
{"pinInstantiationIdx": 0, "partParameter": "pin"},
{"partParameter": "mode"}
]
},
"assembly": [
{"part": true, "pinIndices": [2]},
{"pinIndices": [0, 1]}
]
},
"ledmatrix": {
"visual": {
"builtIn": "ledmatrix",
"width": 105,
"height": 105,
"pinDistance": 15,
"pinLocations": [
{"x": 0, "y": 0},
{"x": 15, "y": 0},
{"x": 30, "y": 0},
{"x": 45, "y": 0},
{"x": 105, "y": 105},
{"x": 0, "y": 105},
{"x": 15, "y": 105},
{"x": 30, "y": 105},
{"x": 45, "y": 105},
{"x": 60, "y": 0}
]
},
"simulationBehavior": "ledmatrix",
"numberOfPins": 10,
"instantiation": {"kind": "singleton"},
"pinDefinitions": [
{"target": "P6", "style": "male", "orientation": "-Z", "colorGroup": 0},
{"target": "P7", "style": "male", "orientation": "-Z", "colorGroup": 0},
{"target": "P8", "style": "male", "orientation": "-Z", "colorGroup": 0},
{"target": "P9", "style": "male", "orientation": "-Z", "colorGroup": 0},
{"target": "P10", "style": "male", "orientation": "-Z", "colorGroup": 0},
{"target": "P12", "style": "male", "orientation": "-Z", "colorGroup": 1},
{"target": "P13", "style": "male", "orientation": "-Z", "colorGroup": 1},
{"target": "P16", "style": "male", "orientation": "-Z", "colorGroup": 1},
{"target": "P19", "style": "male", "orientation": "-Z", "colorGroup": 1},
{"target": "P20", "style": "male", "orientation": "-Z", "colorGroup": 1}
],
"assembly": [
{"part": true},
{"pinIndices": [0, 1, 2, 3, 4]},
{"pinIndices": [5, 6, 7, 8, 9]}
]
},
"speaker": {
"numberOfPins": 2,
"visual": {
"image": "/parts/speaker.svg",
"width": 500,
"height": 500,
"firstPin": [180, 135],
"pinDist": 70,
"extraColumnOffset": 1
"pinDistance": 70,
"pinLocations": [
{"x": 180, "y": 135},
{"x": 320, "y": 135}
]
},
"breadboardColumnsNeeded": 5,
"breadboardStartRow": "f",
"pinAllocation": {
"type": "auto",
"gpioPinsNeeded": 1
},
"assemblyStep": 0,
"wires": [
{"start": ["breadboard", "j", 1], "end": ["GPIO", 0], "color": "#ff80fa", "assemblyStep": 1},
{"start": ["breadboard", "j", 3], "end": "ground", "color": "blue", "assemblyStep": 1}
"pinDefinitions": [
{"target": "P0", "style": "male", "orientation": "-Z"},
{"target": "ground", "style": "male", "orientation": "-Z"}
],
"instantiation": {"kind": "singleton"},
"assembly": [
{"part": true, "pinIndices": [0]},
{"pinIndices": [1]}
]
}
}

View File

@ -1,60 +1,81 @@
namespace pxsim {
export interface AllocatorOpts {
const GROUND_COLOR = "blue";
const POWER_COLOR = "red";
export interface AllocatorOpts {
boardDef: BoardDefinition,
cmpDefs: Map<PartDefinition>,
partDefs: Map<PartDefinition>,
partsList: string[]
fnArgs: any,
getBBCoord: (loc: BBRowCol) => visuals.Coord,
cmpList: string[]
// Used for finding the nearest available power pins
getBBCoord: (loc: BBLoc) => visuals.Coord,
};
export interface AllocatorResult {
powerWires: WireInst[],
components: CmpAndWireInst[]
partsAndWires: PartAndWiresInst[],
}
export interface CmpAndWireInst {
component: CmpInst,
wires: WireInst[]
}
export interface CmpInst {
export interface PartInst {
name: string,
breadboardStartColumn: number,
breadboardStartRow: string,
assemblyStep: number,
visual: string | PartVisualDefinition,
microbitPins: string[],
otherArgs?: string[],
simulationBehavior?: string,
visual: PartVisualDefinition,
bbFit: PartBBFit,
startColumnIdx: number,
startRowIdx: number,
breadboardConnections: BBLoc[],
params: Map<string>,
}
export interface WireInst {
start: Loc,
end: Loc,
color: string,
assemblyStep: number
};
interface PartialCmpAlloc {
export interface AssemblyStep {
part?: boolean,
wireIndices?: number[],
}
export interface PartAndWiresInst {
part?: PartInst,
wires?: WireInst[],
assembly: AssemblyStep[],
}
export interface PartBBFit {
xOffset: number,
yOffset: number,
rowCount: number,
colCount: number,
}
interface PinBBFit {
partRelativeColIdx: number,
partRelativeRowIdx: number,
xOffset: number,
yOffset: number,
}
interface PinIR {
loc: XY,
def: PartPinDefinition,
target: PinTarget,
bbFit: PinBBFit,
}
interface PartIR {
name: string,
def: PartDefinition,
pinsAssigned: string[],
pinsNeeded: number | number[],
breadboardColumnsNeeded: number,
otherArgs?: string[],
}
interface AllocLocOpts {
nearestBBPin?: BBRowCol,
startColumn?: number,
cmpGPIOPins?: string[],
partParams: Map<string>,
pins: PinIR[],
bbFit: PartBBFit,
};
interface AllocWireOpts {
startColumn: number,
cmpGPIOPins: string[],
}
interface AllocBlock {
cmpIdx: number,
cmpBlkIdx: number,
gpioNeeded: number,
gpioAssigned: string[]
interface PartPlacement extends PartIR {
startColumnIdx: number,
startRowIdx: number,
};
type WireIRLoc = PinTarget | BBLoc;
interface WireIR {
pinIdx: number,
start: WireIRLoc,
end: WireIRLoc,
color: string,
}
interface PartIRAndWireIRs extends PartPlacement {
wires: WireIR[],
};
interface PowerUsage {
topGround: boolean,
topThreeVolt: boolean,
@ -63,18 +84,27 @@ namespace pxsim {
singleGround: boolean,
singleThreeVolt: boolean,
}
function isOnBreadboardBottom(location: WireLocationDefinition) {
interface AllocLocOpts {
referenceBBPin?: BBLoc,
};
interface AllocWireOpts {
//TODO: port
startColumn: number,
partGPIOPins: string[],
}
function isOnBreadboardBottom(location: WireIRLoc) {
let isBot = false;
if (location[0] === "breadboard") {
let row = <string>location[1];
if (typeof location !== "string" && (<BBLoc>location).type === "breadboard") {
let bbLoc = <BBLoc>location;
let row = bbLoc.row;
isBot = 0 <= ["a", "b", "c", "d", "e"].indexOf(row);
}
return isBot;
}
const arrCount = (a: boolean[]) => a.reduce((p, n) => p + (n ? 1 : 0), 0);
const arrAny = (a: boolean[]) => arrCount(a) > 0;
function computePowerUsage(wireDef: WireDefinition): PowerUsage {
let ends = [wireDef.start, wireDef.end];
function computePowerUsage(wire: WireIR): PowerUsage {
let ends = [wire.start, wire.end];
let endIsGround = ends.map(e => e === "ground");
let endIsThreeVolt = ends.map(e => e === "threeVolt");
let endIsBot = ends.map(e => isOnBreadboardBottom(e));
@ -115,10 +145,21 @@ namespace pxsim {
function copyDoubleArray(a: string[][]) {
return a.map(b => b.map(p => p));
}
function readPin(arg: string): string {
function merge2<A, B>(a: A, b: B): A & B {
let res: any = {};
for (let aKey in a)
res[aKey] = (<any>a)[aKey];
for (let bKey in b)
res[bKey] = (<any>b)[bKey];
return <A & B>res;
}
function merge3<A, B, C>(a: A, b: B, c: C): A & B & C {
return merge2(merge2(a, b), c);
}
function readPin(arg: string): MicrobitPin {
U.assert(!!arg, "Invalid pin: " + arg);
let pin = arg.split("DigitalPin.")[1];
return pin;
return <MicrobitPin>pin;
}
function mkReverseMap(map: {[key: string]: string}) {
let origKeys: string[] = [];
@ -135,25 +176,239 @@ namespace pxsim {
}
return newMap;
}
function isConnectedToBB(pin: PartPinDefinition): boolean {
return pin.orientation === "-Z" && pin.style === "male";
}
class Allocator {
//TODO: better handling of allocation errors
private opts: AllocatorOpts;
private availablePowerPins = {
top: {
threeVolt: mkRange(26, 51).map(n => <BBRowCol>["+", `${n}`]),
ground: mkRange(26, 51).map(n => <BBRowCol>["-", `${n}`]),
threeVolt: mkRange(26, 51).map(n => <BBLoc>{type: "breadboard", row: "+", col: `${n}`}),
ground: mkRange(26, 51).map(n => <BBLoc>{type: "breadboard", row: "-", col: `${n}`}),
},
bottom: {
threeVolt: mkRange(1, 26).map(n => <BBRowCol>["+", `${n}`]),
ground: mkRange(1, 26).map(n => <BBRowCol>["-", `${n}`]),
threeVolt: mkRange(1, 26).map(n => <BBLoc>{type: "breadboard", row: "+", col: `${n}`}),
ground: mkRange(1, 26).map(n => <BBLoc>{type: "breadboard", row: "-", col: `${n}`}),
},
};
private powerUsage: PowerUsage;
private availableWireColors: string[];
constructor(opts: AllocatorOpts) {
this.opts = opts;
}
private allocateLocation(location: WireLocationDefinition, opts: AllocLocOpts): Loc {
private allocPartIRs(def: PartDefinition, name: string, bbFit: PartBBFit): PartIR[] {
let partIRs: PartIR[] = [];
let mkIR = (def: PartDefinition, name: string, instPins?: PinTarget[], partParams?: Map<string>): PartIR => {
let pinIRs: PinIR[] = [];
for (let i = 0; i < def.numberOfPins; i++) {
let pinDef = def.pinDefinitions[i];
let pinTarget: PinTarget;
if (typeof pinDef.target === "string") {
pinTarget = <PinTarget>pinDef.target;
} else {
let instIdx = (<PinInstantiationIdx>pinDef.target).pinInstantiationIdx;
U.assert(!!instPins && instPins[instIdx] !== undefined,
`No pin found for PinInstantiationIdx: ${instIdx}. (Is the part missing an ArguementRole or "trackArgs=" annotations?)`);
pinTarget = instPins[instIdx];
}
let pinLoc = def.visual.pinLocations[i];
let adjustedY = bbFit.yOffset + pinLoc.y;
let relativeRowIdx = Math.round(adjustedY / def.visual.pinDistance);
let relativeYOffset = adjustedY - relativeRowIdx * def.visual.pinDistance;
let adjustedX = bbFit.xOffset + pinLoc.x;
let relativeColIdx = Math.round(adjustedX / def.visual.pinDistance);
let relativeXOffset = adjustedX - relativeColIdx * def.visual.pinDistance;
let pinBBFit: PinBBFit = {
partRelativeRowIdx: relativeRowIdx,
partRelativeColIdx: relativeColIdx,
xOffset: relativeXOffset,
yOffset: relativeYOffset
};
pinIRs.push({
def: pinDef,
loc: pinLoc,
target: pinTarget,
bbFit: pinBBFit,
});
}
return {
name: name,
def: def,
pins: pinIRs,
partParams: partParams || {},
bbFit: bbFit
};
};
if (def.instantiation.kind === "singleton") {
partIRs.push(mkIR(def, name));
} else if (def.instantiation.kind === "function") {
let fnAlloc = def.instantiation as PartFunctionDefinition;
let fnNm = fnAlloc.fullyQualifiedName;
let callsitesTrackedArgs = <string[]>this.opts.fnArgs[fnNm];
U.assert(!!callsitesTrackedArgs && !!callsitesTrackedArgs.length, "Failed to read pin(s) from callsite for: " + fnNm);
callsitesTrackedArgs.forEach(fnArgsStr => {
let fnArgsSplit = fnArgsStr.split(",");
U.assert(fnArgsSplit.length === fnAlloc.argumentRoles.length,
`Mismatch between number of arguments at callsite (function name: ${fnNm}) vs number of argument roles in part definition (part: ${name}).`);
let instPins: PinTarget[] = [];
let paramArgs: Map<string> = {};
fnArgsSplit.forEach((arg, idx) => {
let role = fnAlloc.argumentRoles[idx];
if (role.partParameter !== undefined) {
paramArgs[role.partParameter] = arg;
}
if (role.pinInstantiationIdx !== undefined) {
let instIdx = role.pinInstantiationIdx;
let pin = readPin(arg);
instPins[instIdx] = pin;
}
});
partIRs.push(mkIR(def, name, instPins, paramArgs));
});
}
return partIRs;
}
private computePartDimensions(def: PartDefinition, name: string): PartBBFit {
let pinLocs = def.visual.pinLocations;
let pinDefs = def.pinDefinitions;
let numPins = def.numberOfPins;
U.assert(pinLocs.length === numPins, `Mismatch between "numberOfPins" and length of "visual.pinLocations" for "${name}"`);
U.assert(pinDefs.length === numPins, `Mismatch between "numberOfPins" and length of "pinDefinitions" for "${name}"`);
U.assert(numPins > 0, `Part "${name}" has no pins`);
let pins = pinLocs.map((loc, idx) => merge3({idx: idx}, loc, pinDefs[idx]));
let bbPins = pins.filter(p => p.orientation === "-Z");
let hasBBPins = bbPins.length > 0;
let pinDist = def.visual.pinDistance;
let xOff: number;
let yOff: number;
let colCount: number;
let rowCount: number;
if (hasBBPins) {
let refPin = bbPins[0];
let refPinColIdx = Math.ceil(refPin.x / pinDist);
let refPinRowIdx = Math.ceil(refPin.y / pinDist);
xOff = refPinColIdx * pinDist - refPin.x;
yOff = refPinRowIdx * pinDist - refPin.y;
colCount = Math.ceil((xOff + def.visual.width) / pinDist) + 1;
rowCount = Math.ceil((yOff + def.visual.height) / pinDist) + 1;
} else {
colCount = Math.ceil(def.visual.width / pinDist);
rowCount = Math.ceil(def.visual.height / pinDist);
xOff = colCount * pinDist - def.visual.width;
yOff = rowCount * pinDist - def.visual.height;
}
return {
xOffset: xOff,
yOffset: yOff,
rowCount: rowCount,
colCount: colCount
};
}
private allocColumns(colCounts: {colCount: number}[]): number[] {
let partsCount = colCounts.length;
const totalColumnsCount = visuals.BREADBOARD_MID_COLS; //TODO allow multiple breadboards
let totalSpaceNeeded = colCounts.map(d => d.colCount).reduce((p, n) => p + n, 0);
let extraSpace = totalColumnsCount - totalSpaceNeeded;
if (extraSpace <= 0) {
console.log("Not enough breadboard space!");
//TODO
}
let padding = Math.floor(extraSpace / (partsCount - 1 + 2));
let partSpacing = padding; //Math.floor(extraSpace/(partsCount-1));
let totalPartPadding = extraSpace - partSpacing * (partsCount - 1);
let leftPadding = Math.floor(totalPartPadding / 2);
let rightPadding = Math.ceil(totalPartPadding / 2);
let nextAvailableCol = 1 + leftPadding;
let partStartCol = colCounts.map(part => {
let col = nextAvailableCol;
nextAvailableCol += part.colCount + partSpacing;
return col;
});
return partStartCol;
}
private placeParts(parts: PartIR[]): PartPlacement[] {
const totalRowsCount = visuals.BREADBOARD_MID_ROWS + 2; // 10 letters + 2 for the middle gap
let startColumnIndices = this.allocColumns(parts.map(p => p.bbFit));
let startRowIndicies = parts.map(p => {
let extraRows = totalRowsCount - p.bbFit.rowCount;
let topPad = Math.floor(extraRows / 2);
let startIdx = topPad;
if (startIdx > 4)
startIdx = 4;
if (startIdx < 1)
startIdx = 1;
return startIdx;
});
let placements = parts.map((p, idx) => {
let row = startRowIndicies[idx];
let col = startColumnIndices[idx];
return merge2({startColumnIdx: col, startRowIdx: row}, p);
});
return placements;
}
private nextColor(): string {
if (!this.availableWireColors || this.availableWireColors.length <= 0) {
this.availableWireColors = visuals.GPIO_WIRE_COLORS.map(c => c);
}
return this.availableWireColors.pop();
}
private allocWireIRs(part: PartPlacement): PartIRAndWireIRs {
let groupToColor: string[] = [];
let wires: WireIR[] = part.pins.map((pin, pinIdx) => {
let end = pin.target;
let start: WireIRLoc;
let colIdx = part.startColumnIdx + pin.bbFit.partRelativeColIdx;
let colName = visuals.getColumnName(colIdx);
let pinRowIdx = part.startRowIdx + pin.bbFit.partRelativeRowIdx;
if (pinRowIdx >= 7) //account for middle gap
pinRowIdx -= 2;
if (isConnectedToBB(pin.def)) {
//make a wire from bb top or bottom to target
let connectedToTop = pinRowIdx < 5;
let rowName = connectedToTop ? "j" : "a";
start = {
type: "breadboard",
row: rowName,
col: colName,
};
} else {
//make a wire directly from pin to target
let rowName = visuals.getRowName(pinRowIdx);
start = {
type: "breadboard",
row: rowName,
col: colName,
xOffset: pin.bbFit.xOffset / part.def.visual.pinDistance,
yOffset: pin.bbFit.yOffset / part.def.visual.pinDistance
}
}
let color: string;
if (end === "ground") {
color = GROUND_COLOR;
} else if (end === "threeVolt") {
color = POWER_COLOR;
} else if (typeof pin.def.colorGroup === "number") {
if (groupToColor[pin.def.colorGroup]) {
color = groupToColor[pin.def.colorGroup];
} else {
color = groupToColor[pin.def.colorGroup] = this.nextColor();
}
} else {
color = this.nextColor()
}
return {
start: start,
end: end,
color: color,
pinIdx: pinIdx,
}
});
return merge2(part, {wires: wires});
}
private allocLocation(location: WireIRLoc, opts: AllocLocOpts): Loc {
if (location === "ground" || location === "threeVolt") {
//special case if there is only a single ground or three volt pin in the whole build
if (location === "ground" && this.powerUsage.singleGround) {
@ -164,8 +419,8 @@ namespace pxsim {
return {type: "dalboard", pin: boardThreeVoltPin};
}
U.assert(!!opts.nearestBBPin);
let nearestCoord = this.opts.getBBCoord(opts.nearestBBPin);
U.assert(!!opts.referenceBBPin);
let nearestCoord = this.opts.getBBCoord(opts.referenceBBPin);
let firstTopAndBot = [
this.availablePowerPins.top.ground[0] || this.availablePowerPins.top.threeVolt[0],
this.availablePowerPins.bottom.ground[0] || this.availablePowerPins.bottom.threeVolt[0]
@ -177,7 +432,7 @@ namespace pxsim {
//TODO
}
let nearTop = visuals.findClosestCoordIdx(nearestCoord, firstTopAndBot) == 0;
let barPins: BBRowCol[];
let barPins: BBLoc[];
if (nearTop) {
if (location === "ground") {
barPins = this.availablePowerPins.top.ground;
@ -203,17 +458,9 @@ namespace pxsim {
this.availablePowerPins.bottom.ground.splice(closestPinIdx, 1);
this.availablePowerPins.bottom.threeVolt.splice(closestPinIdx, 1);
}
return {type: "breadboard", rowCol: pin};
} else if (location[0] === "breadboard") {
U.assert(!!opts.startColumn);
let row = <string>location[1];
let col = (<number>location[2] + opts.startColumn).toString();
return {type: "breadboard", rowCol: [row, col]}
} else if (location[0] === "GPIO") {
U.assert(!!opts.cmpGPIOPins);
let idx = <number>location[1];
let pin = opts.cmpGPIOPins[idx];
return {type: "dalboard", pin: pin};
return pin;
} else if ((<BBLoc>location).type === "breadboard") {
return <BBLoc>location;
} else if (location === "MOSI" || location === "MISO" || location === "SCK") {
if (!this.opts.boardDef.spiPins)
console.debug("No SPI pin mappings found!");
@ -225,12 +472,15 @@ namespace pxsim {
let pin = (<any>this.opts.boardDef.i2cPins)[location as string] as string;
return {type: "dalboard", pin: pin};
} else {
//TODO
U.assert(false);
return null;
//it must be a MicrobitPin
U.assert(typeof location === "string", "Unknown location type: " + location);
let mbPin = <MicrobitPin>location;
let boardPin = this.opts.boardDef.gpioPinMap[mbPin];
U.assert(!!boardPin, "Unknown pin: " + location);
return {type: "dalboard", pin: boardPin};
}
}
private getBoardGroundPin() {
private getBoardGroundPin(): string {
let boardGround = this.opts.boardDef.groundPins[0] || null;
if (!boardGround) {
console.log("No available ground pin on board!");
@ -238,7 +488,7 @@ namespace pxsim {
}
return boardGround;
}
private getBoardThreeVoltPin() {
private getBoardThreeVoltPin(): string {
let threeVoltPin = this.opts.boardDef.threeVoltPins[0] || null;
if (!threeVoltPin) {
console.log("No available 3.3V pin on board!");
@ -246,14 +496,14 @@ namespace pxsim {
}
return threeVoltPin;
}
private allocatePowerWires(powerUsage: PowerUsage): WireInst[] {
private allocPowerWires(powerUsage: PowerUsage): PartAndWiresInst {
let boardGroundPin = this.getBoardGroundPin();
let threeVoltPin = this.getBoardThreeVoltPin();
let topLeft: BBRowCol = ["-", "26"];
let botLeft: BBRowCol = ["-", "1"];
let topRight: BBRowCol = ["-", "50"];
let botRight: BBRowCol = ["-", "25"];
let top: BBRowCol, bot: BBRowCol;
const topLeft: BBLoc = {type: "breadboard", row: "-", col: "26"};
const botLeft: BBLoc = {type: "breadboard", row: "-", col: "1"};
const topRight: BBLoc = {type: "breadboard", row: "-", col: "50"};
const botRight: BBLoc = {type: "breadboard", row: "-", col: "25"};
let top: BBLoc, bot: BBLoc;
if (this.opts.boardDef.attachPowerOnRight) {
top = topRight;
bot = botRight;
@ -261,296 +511,162 @@ namespace pxsim {
top = topLeft;
bot = botLeft;
}
const GROUND_COLOR = "blue";
const POWER_COLOR = "red";
const wires: WireInst[] = [];
let groundStep = 0;
let threeVoltStep = (powerUsage.bottomGround || powerUsage.topGround) ? 1 : 0;
let groundWires: WireInst[] = [];
let threeVoltWires: WireInst[] = [];
if (powerUsage.bottomGround && powerUsage.topGround) {
//bb top - <==> bb bot -
wires.push({
start: this.allocateLocation("ground", {nearestBBPin: top}),
end: this.allocateLocation("ground", {nearestBBPin: bot}),
color: GROUND_COLOR, assemblyStep: groundStep
groundWires.push({
start: this.allocLocation("ground", {referenceBBPin: top}),
end: this.allocLocation("ground", {referenceBBPin: bot}),
color: GROUND_COLOR,
});
}
if (powerUsage.topGround) {
//board - <==> bb top -
wires.push({
start: this.allocateLocation("ground", {nearestBBPin: top}),
groundWires.push({
start: this.allocLocation("ground", {referenceBBPin: top}),
end: {type: "dalboard", pin: boardGroundPin},
color: GROUND_COLOR, assemblyStep: groundStep
color: GROUND_COLOR,
});
} else if (powerUsage.bottomGround) {
//board - <==> bb bot -
wires.push({
start: this.allocateLocation("ground", {nearestBBPin: bot}),
groundWires.push({
start: this.allocLocation("ground", {referenceBBPin: bot}),
end: {type: "dalboard", pin: boardGroundPin},
color: GROUND_COLOR, assemblyStep: groundStep
color: GROUND_COLOR,
});
}
if (powerUsage.bottomThreeVolt && powerUsage.bottomGround) {
//bb top + <==> bb bot +
wires.push({
start: this.allocateLocation("threeVolt", {nearestBBPin: top}),
end: this.allocateLocation("threeVolt", {nearestBBPin: bot}),
color: POWER_COLOR, assemblyStep: threeVoltStep
threeVoltWires.push({
start: this.allocLocation("threeVolt", {referenceBBPin: top}),
end: this.allocLocation("threeVolt", {referenceBBPin: bot}),
color: POWER_COLOR,
});
}
if (powerUsage.topThreeVolt) {
//board + <==> bb top +
wires.push({
start: this.allocateLocation("threeVolt", {nearestBBPin: top}),
threeVoltWires.push({
start: this.allocLocation("threeVolt", {referenceBBPin: top}),
end: {type: "dalboard", pin: threeVoltPin},
color: POWER_COLOR, assemblyStep: threeVoltStep
color: POWER_COLOR,
});
} else if (powerUsage.bottomThreeVolt) {
//board + <==> bb bot +
wires.push({
start: this.allocateLocation("threeVolt", {nearestBBPin: bot}),
threeVoltWires.push({
start: this.allocLocation("threeVolt", {referenceBBPin: bot}),
end: {type: "dalboard", pin: threeVoltPin},
color: POWER_COLOR, assemblyStep: threeVoltStep
color: POWER_COLOR,
});
}
return wires;
let assembly: AssemblyStep[] = [];
if (groundWires.length > 0)
assembly.push({wireIndices: groundWires.map((w, i) => i)});
let numGroundWires = groundWires.length;
if (threeVoltWires.length > 0)
assembly.push({wireIndices: threeVoltWires.map((w, i) => i + numGroundWires)});
return {
wires: groundWires.concat(threeVoltWires),
assembly: assembly
};
}
private allocateWire(wireDef: WireDefinition, opts: AllocWireOpts): WireInst {
let ends = [wireDef.start, wireDef.end];
private allocWire(wireIR: WireIR): WireInst {
let ends = [wireIR.start, wireIR.end];
let endIsPower = ends.map(e => e === "ground" || e === "threeVolt");
//allocate non-power first so we know the nearest pin for the power end
let endInsts = ends.map((e, idx) => !endIsPower[idx] ? this.allocateLocation(e, opts) : null)
let endInsts = ends.map((e, idx) => !endIsPower[idx] ? this.allocLocation(e, {}) : null)
//allocate power pins closest to the other end of the wire
endInsts = endInsts.map((e, idx) => {
if (e)
return e;
let locInst = <BBLoc>endInsts[1 - idx]; // non-power end
let l = this.allocateLocation(ends[idx], {
nearestBBPin: locInst.rowCol,
startColumn: opts.startColumn,
cmpGPIOPins: opts.cmpGPIOPins,
let l = this.allocLocation(ends[idx], {
referenceBBPin: locInst,
});
return l;
});
return {start: endInsts[0], end: endInsts[1], color: wireDef.color, assemblyStep: wireDef.assemblyStep};
return {start: endInsts[0], end: endInsts[1], color: wireIR.color};
}
private allocatePartialCmps(): PartialCmpAlloc[] {
let cmpNmAndDefs = this.opts.cmpList.map(cmpName => <[string, PartDefinition]>[cmpName, this.opts.cmpDefs[cmpName]]).filter(d => !!d[1]);
let cmpNmsList = cmpNmAndDefs.map(p => p[0]);
let cmpDefsList = cmpNmAndDefs.map(p => p[1]);
let partialCmps: PartialCmpAlloc[] = [];
cmpDefsList.forEach((def, idx) => {
let nm = cmpNmsList[idx];
if (def.pinAllocation.type === "predefined") {
let mbPins = (<PredefinedPinAlloc>def.pinAllocation).pins;
let pinsAssigned = mbPins.map(p => this.opts.boardDef.gpioPinMap[p]);
partialCmps.push({
name: nm,
def: def,
pinsAssigned: pinsAssigned,
pinsNeeded: 0,
breadboardColumnsNeeded: def.breadboardColumnsNeeded,
});
} else if (def.pinAllocation.type === "factoryfunction") {
let fnPinAlloc = (<FactoryFunctionPinAlloc>def.pinAllocation);
let fnNm = fnPinAlloc.functionName;
let fnsAndArgs = <string[]>this.opts.fnArgs[fnNm];
let success = false;
if (fnsAndArgs && fnsAndArgs.length) {
let pinArgPoses = fnPinAlloc.pinArgPositions;
let otherArgPoses = fnPinAlloc.otherArgPositions || [];
fnsAndArgs.forEach(fnArgsStr => {
let fnArgsSplit = fnArgsStr.split(",");
let pinArgs: string[] = [];
pinArgPoses.forEach(i => {
pinArgs.push(fnArgsSplit[i]);
});
let mbPins = pinArgs.map(arg => readPin(arg));
let otherArgs: string[] = [];
otherArgPoses.forEach(i => {
otherArgs.push(fnArgsSplit[i]);
});
let pinsAssigned = mbPins.map(p => this.opts.boardDef.gpioPinMap[p]);
partialCmps.push({
name: nm,
def: def,
pinsAssigned: pinsAssigned,
pinsNeeded: 0,
breadboardColumnsNeeded: def.breadboardColumnsNeeded,
otherArgs: otherArgs.length ? otherArgs : null,
});
});
} else {
// failed to find pin allocation from callsites
console.debug("Failed to read pin(s) from callsite for: " + fnNm);
let pinsNeeded = fnPinAlloc.pinArgPositions.length;
partialCmps.push({
name: nm,
def: def,
pinsAssigned: [],
pinsNeeded: pinsNeeded,
breadboardColumnsNeeded: def.breadboardColumnsNeeded,
});
private allocPart(ir: PartPlacement): PartInst {
let bbConnections = ir.pins
.filter(p => isConnectedToBB(p.def))
.map(p => {
let rowIdx = ir.startRowIdx + p.bbFit.partRelativeRowIdx;
if (rowIdx >= 7) //account for middle gap
rowIdx -= 2;
let rowName = visuals.getRowName(rowIdx);
let colIdx = ir.startColumnIdx + p.bbFit.partRelativeColIdx;
let colName = visuals.getColumnName(colIdx);
return <BBLoc>{
type: "breadboard",
row: rowName,
col: colName,
}
} else if (def.pinAllocation.type === "auto") {
let pinsNeeded = (<AutoPinAlloc>def.pinAllocation).gpioPinsNeeded;
partialCmps.push({
name: nm,
def: def,
pinsAssigned: [],
pinsNeeded: pinsNeeded,
breadboardColumnsNeeded: def.breadboardColumnsNeeded,
});
}
});
return partialCmps;
}
private allocateGPIOPins(partialCmps: PartialCmpAlloc[]): string[][] {
let availableGPIOBlocks = copyDoubleArray(this.opts.boardDef.gpioPinBlocks);
let sortAvailableGPIOBlocks = () => availableGPIOBlocks.sort((a, b) => a.length - b.length); //smallest blocks first
// determine blocks needed
let blockAssignments: AllocBlock[] = [];
let preassignedPins: string[] = [];
partialCmps.forEach((cmp, idx) => {
if (cmp.pinsAssigned && cmp.pinsAssigned.length) {
//already assigned
blockAssignments.push({cmpIdx: idx, cmpBlkIdx: 0, gpioNeeded: 0, gpioAssigned: cmp.pinsAssigned});
preassignedPins = preassignedPins.concat(cmp.pinsAssigned);
} else if (cmp.pinsNeeded) {
if (typeof cmp.pinsNeeded === "number") {
//individual pins
for (let i = 0; i < cmp.pinsNeeded; i++) {
blockAssignments.push(
{cmpIdx: idx, cmpBlkIdx: 0, gpioNeeded: 1, gpioAssigned: []});
}
} else {
//blocks of pins
let blocks = <number[]>cmp.pinsNeeded;
blocks.forEach((numNeeded, blkIdx) => {
blockAssignments.push(
{cmpIdx: idx, cmpBlkIdx: blkIdx, gpioNeeded: numNeeded, gpioAssigned: []});
});
}
}
});
// remove assigned blocks
availableGPIOBlocks.forEach(blks => {
for (let i = blks.length - 1; 0 <= i; i--) {
let pin = blks[i];
if (0 <= preassignedPins.indexOf(pin)) {
blks.splice(i, 1);
}
}
});
// sort by size of blocks
let sortBlockAssignments = () => blockAssignments.sort((a, b) => b.gpioNeeded - a.gpioNeeded); //largest blocks first
// allocate each block
if (0 < blockAssignments.length && 0 < availableGPIOBlocks.length) {
do {
sortBlockAssignments();
sortAvailableGPIOBlocks();
let assignment = blockAssignments[0];
let smallestAvailableBlockThatFits: string[];
for (let j = 0; j < availableGPIOBlocks.length; j++) {
smallestAvailableBlockThatFits = availableGPIOBlocks[j];
if (assignment.gpioNeeded <= availableGPIOBlocks[j].length) {
break;
}
}
if (!smallestAvailableBlockThatFits || smallestAvailableBlockThatFits.length <= 0) {
break; // out of pins
}
while (0 < assignment.gpioNeeded && 0 < smallestAvailableBlockThatFits.length) {
assignment.gpioNeeded--;
let pin = smallestAvailableBlockThatFits[0];
smallestAvailableBlockThatFits.splice(0, 1);
assignment.gpioAssigned.push(pin);
}
sortBlockAssignments();
} while (0 < blockAssignments[0].gpioNeeded);
}
if (0 < blockAssignments.length && 0 < blockAssignments[0].gpioNeeded) {
console.debug("Not enough GPIO pins!");
return null;
}
let cmpGPIOPinBlocks: string[][][] = partialCmps.map((def, cmpIdx) => {
if (!def)
return null;
let assignments = blockAssignments.filter(a => a.cmpIdx === cmpIdx);
let gpioPins: string[][] = [];
for (let i = 0; i < assignments.length; i++) {
let a = assignments[i];
let blk = gpioPins[a.cmpBlkIdx] || (gpioPins[a.cmpBlkIdx] = []);
a.gpioAssigned.forEach(p => blk.push(p));
}
return gpioPins;
});
let cmpGPIOPins = cmpGPIOPinBlocks.map(blks => blks.reduce((p, n) => p.concat(n), []));
return cmpGPIOPins;
}
private allocateColumns(partialCmps: PartialCmpAlloc[]): number[] {
let componentsCount = partialCmps.length;
let totalAvailableSpace = 30; //TODO allow multiple breadboards
let totalSpaceNeeded = partialCmps.map(d => d.breadboardColumnsNeeded).reduce((p, n) => p + n, 0);
let extraSpace = totalAvailableSpace - totalSpaceNeeded;
if (extraSpace <= 0) {
console.log("Not enough breadboard space!");
//TODO
}
let padding = Math.floor(extraSpace / (componentsCount - 1 + 2));
let componentSpacing = padding; //Math.floor(extraSpace/(componentsCount-1));
let totalCmpPadding = extraSpace - componentSpacing * (componentsCount - 1);
let leftPadding = Math.floor(totalCmpPadding / 2);
let rightPadding = Math.ceil(totalCmpPadding / 2);
let nextAvailableCol = 1 + leftPadding;
let cmpStartCol = partialCmps.map(cmp => {
let col = nextAvailableCol;
nextAvailableCol += cmp.breadboardColumnsNeeded + componentSpacing;
return col;
});
return cmpStartCol;
}
private allocateComponent(partialCmp: PartialCmpAlloc, startColumn: number, microbitPins: string[]): CmpInst {
return {
name: partialCmp.name,
breadboardStartColumn: startColumn,
breadboardStartRow: partialCmp.def.breadboardStartRow,
assemblyStep: partialCmp.def.assemblyStep,
visual: partialCmp.def.visual,
microbitPins: microbitPins,
otherArgs: partialCmp.otherArgs,
};
}
public allocateAll(): AllocatorResult {
let cmpList = this.opts.cmpList;
let basicWires: WireInst[] = [];
let cmpsAndWires: CmpAndWireInst[] = [];
if (cmpList.length > 0) {
let partialCmps = this.allocatePartialCmps();
let allWireDefs = partialCmps.map(p => p.def.wires).reduce((p, n) => p.concat(n), []);
let allPowerUsage = allWireDefs.map(w => computePowerUsage(w));
this.powerUsage = mergePowerUsage(allPowerUsage);
basicWires = this.allocatePowerWires(this.powerUsage);
let cmpGPIOPins = this.allocateGPIOPins(partialCmps);
let reverseMap = mkReverseMap(this.opts.boardDef.gpioPinMap);
let cmpMicrobitPins = cmpGPIOPins.map(pins => pins.map(p => reverseMap[p]));
let cmpStartCol = this.allocateColumns(partialCmps);
let cmps = partialCmps.map((c, idx) => this.allocateComponent(c, cmpStartCol[idx], cmpMicrobitPins[idx]));
let wires = partialCmps.map((c, idx) => c.def.wires.map(d => this.allocateWire(d, {
cmpGPIOPins: cmpGPIOPins[idx],
startColumn: cmpStartCol[idx],
})));
cmpsAndWires = cmps.map((c, idx) => {
return {component: c, wires: wires[idx]}
});
let part: PartInst = {
name: ir.name,
visual: ir.def.visual,
bbFit: ir.bbFit,
startColumnIdx: ir.startColumnIdx,
startRowIdx: ir.startRowIdx,
breadboardConnections: bbConnections,
params: ir.partParams,
simulationBehavior: ir.def.simulationBehavior
}
return part;
}
public allocAll(): AllocatorResult {
let partNmAndDefs = this.opts.partsList
.map(partName => {return {name: partName, def: this.opts.partDefs[partName]}})
.filter(d => !!d.def);
if (partNmAndDefs.length > 0) {
let partNmsList = partNmAndDefs.map(p => p.name);
let partDefsList = partNmAndDefs.map(p => p.def);
let dimensions = partNmAndDefs.map(nmAndPart => this.computePartDimensions(nmAndPart.def, nmAndPart.name));
let partIRs: PartIR[] = [];
partNmAndDefs.forEach((nmAndDef, idx) => {
let dims = dimensions[idx];
let irs = this.allocPartIRs(nmAndDef.def, nmAndDef.name, dims);
partIRs = partIRs.concat(irs);
})
let partPlacements = this.placeParts(partIRs);
let partsAndWireIRs = partPlacements.map(p => this.allocWireIRs(p));
let allWireIRs = partsAndWireIRs.map(p => p.wires).reduce((p, n) => p.concat(n), []);
let allPowerUsage = allWireIRs.map(w => computePowerUsage(w));
this.powerUsage = mergePowerUsage(allPowerUsage);
let basicWires = this.allocPowerWires(this.powerUsage);
let partsAndWires: PartAndWiresInst[] = partsAndWireIRs.map((irs, idx) => {
let part = this.allocPart(irs);
let wires = irs.wires.map(w => this.allocWire(w));
let pinIdxToWireIdx: number[] = [];
irs.wires.forEach((wIR, idx) => {
pinIdxToWireIdx[wIR.pinIdx] = idx;
});
let assembly: AssemblyStep[] = irs.def.assembly.map(stepDef => {
return {
part: stepDef.part,
wireIndices: (stepDef.pinIndices || []).map(i => pinIdxToWireIdx[i])
}
});
return {
part: part,
wires: wires,
assembly: assembly
}
});
let all = [basicWires].concat(partsAndWires);
return {
partsAndWires: all
}
} else {
return {
partsAndWires: []
}
}
return {
powerWires: basicWires,
components: cmpsAndWires
};
}
}
export function allocateDefinitions(opts: AllocatorOpts): AllocatorResult {
return new Allocator(opts).allocateAll();
return new Allocator(opts).allocAll();
}
}

View File

@ -84,8 +84,8 @@ namespace pxsim {
let viewHost = new visuals.BoardHost({
state: this,
boardDef: boardDef,
cmpsList: cmpsList,
cmpDefs: cmpDefs,
partsList: cmpsList,
partDefs: cmpDefs,
fnArgs: fnArgs,
maxWidth: "100%",
maxHeight: "100%",

View File

@ -53,7 +53,7 @@ namespace pxsim {
marginWhenBreadboarding: [0, 0, 80, 0],
}
export const builtinComponentSimVisual: Map<() => visuals.IBoardComponent<any>> = {
export const builtinComponentSimVisual: Map<() => visuals.IBoardPart<any>> = {
"buttonpair": () => new visuals.ButtonPairView(),
"ledmatrix": () => new visuals.LedMatrixView(),
"neopixel": () => new visuals.NeoPixelView(),

View File

@ -20,7 +20,7 @@ namespace pxsim.instructions {
const LBL_LEFT_PAD = 5;
const REQ_WIRE_HEIGHT = 45;
const REQ_CMP_HEIGHT = 55;
const REQ_CMP_SCALE = 0.5 * 4;
const REQ_CMP_SCALE = 0.5 * 3;
type Orientation = "landscape" | "portrait";
const ORIENTATION: Orientation = "portrait";
const PPI = 96.0;
@ -284,18 +284,18 @@ namespace pxsim.instructions {
div.appendChild(svgEl);
return div;
}
function mkCmpDiv(cmp: "wire" | string | PartVisualDefinition, opts: mkCmpDivOpts): HTMLElement {
function mkCmpDiv(cmp: "wire" | PartVisualDefinition, opts: mkCmpDivOpts): HTMLElement {
let el: visuals.SVGElAndSize;
if (cmp == "wire") {
//TODO: support non-croc wire parts
el = visuals.mkWirePart([0, 0], opts.wireClr || "red", opts.crocClips);
} else if (typeof cmp == "string") {
let builtinVis = <string>cmp;
let cnstr = builtinComponentPartVisual[builtinVis];
el = cnstr([0, 0]);
} else {
let partVis = <PartVisualDefinition>cmp;
el = visuals.mkGenericPartSVG(partVis);
if (typeof partVis.builtIn == "string") {
let cnstr = builtinComponentPartVisual[partVis.builtIn];
el = cnstr([0, 0]);
} else {
el = visuals.mkGenericPartSVG(partVis);
}
}
return wrapSvg(el, opts);
}
@ -305,40 +305,33 @@ namespace pxsim.instructions {
fnArgs: any,
allAlloc: AllocatorResult,
stepToWires: WireInst[][],
stepToCmps: CmpInst[][]
stepToCmps: PartInst[][]
allWires: WireInst[],
allCmps: CmpInst[],
allCmps: PartInst[],
lastStep: number,
colorToWires: Map<WireInst[]>,
allWireColors: string[],
};
function mkBoardProps(allocOpts: AllocatorOpts): BoardProps {
let allocRes = allocateDefinitions(allocOpts);
let {powerWires, components} = allocRes;
let stepToWires: WireInst[][] = [];
let stepToCmps: CmpInst[][] = [];
powerWires.forEach(w => {
let step = w.assemblyStep + 1;
(stepToWires[step] || (stepToWires[step] = [])).push(w)
});
let getMaxStep = (ns: { assemblyStep: number }[]) => ns.reduce((m, n) => Math.max(m, n.assemblyStep), 0);
let stepOffset = powerWires.length > 0 ? getMaxStep(powerWires) + 2 : 1;
components.forEach(cAndWs => {
let {component, wires} = cAndWs;
let cStep = component.assemblyStep + stepOffset;
let arr = stepToCmps[cStep] || (stepToCmps[cStep] = []);
arr.push(component);
let wSteps = wires.map(w => w.assemblyStep + stepOffset);
wires.forEach((w, i) => {
let wStep = wSteps[i];
let arr = stepToWires[wStep] || (stepToWires[wStep] = []);
arr.push(w);
let stepToCmps: PartInst[][] = [];
let stepOffset = 0;
allocRes.partsAndWires.forEach(cAndWs => {
let part = cAndWs.part;
let wires = cAndWs.wires;
cAndWs.assembly.forEach((step, idx) => {
if (step.part && part)
stepToCmps[stepOffset + idx] = [part]
if (step.wireIndices && step.wireIndices.length > 0 && wires)
stepToWires[stepOffset + idx] = step.wireIndices.map(i => wires[i])
})
stepOffset = Math.max(cStep, wSteps.reduce((m, n) => Math.max(m, n), 0)) + 1;
stepOffset += cAndWs.assembly.length;
});
let lastStep = stepOffset - 1;
let allCmps = components.map(p => p.component);
let allWires = powerWires.concat(components.map(p => p.wires).reduce((p, n) => p.concat(n), []));
let numSteps = stepOffset;
let lastStep = numSteps - 1;
let allCmps = allocRes.partsAndWires.map(r => r.part).filter(p => !!p);
let allWires = allocRes.partsAndWires.map(r => r.wires || []).reduce((p, n) => p.concat(n), []);
let colorToWires: Map<WireInst[]> = {}
let allWireColors: string[] = [];
allWires.forEach(w => {
@ -350,7 +343,7 @@ namespace pxsim.instructions {
});
return {
boardDef: allocOpts.boardDef,
cmpDefs: allocOpts.cmpDefs,
cmpDefs: allocOpts.partDefs,
fnArgs: allocOpts.fnArgs,
allAlloc: allocRes,
stepToWires: stepToWires,
@ -368,7 +361,7 @@ namespace pxsim.instructions {
state: state,
boardDef: boardDef,
forceBreadboard: true,
cmpDefs: cmpDefs,
partDefs: cmpDefs,
maxWidth: `${width}px`,
fnArgs: fnArgs,
wireframe: buildMode,
@ -397,6 +390,19 @@ namespace pxsim.instructions {
}
for (let i = 0; i <= step; i++) {
let cmps = props.stepToCmps[i];
if (cmps) {
cmps.forEach(partInst => {
let cmp = board.addPart(partInst)
//last step
if (i === step) {
//highlight locations pins
partInst.breadboardConnections.forEach(bbLoc => board.highlightBreadboardPin(bbLoc));
svg.addClass(cmp.element, "notgrayed");
}
});
}
let wires = props.stepToWires[i];
if (wires) {
wires.forEach(w => {
@ -405,13 +411,12 @@ namespace pxsim.instructions {
if (i === step) {
//location highlights
if (w.start.type == "breadboard") {
let lbls = board.highlightBreadboardPin((<BBLoc>w.start).rowCol);
let lbls = board.highlightBreadboardPin((<BBLoc>w.start));
} else {
board.highlightBoardPin((<BoardLoc>w.start).pin);
}
if (w.end.type == "breadboard") {
let [row, col] = (<BBLoc>w.end).rowCol;
let lbls = board.highlightBreadboardPin((<BBLoc>w.end).rowCol);
let lbls = board.highlightBreadboardPin((<BBLoc>w.end));
} else {
board.highlightBoardPin((<BoardLoc>w.end).pin);
}
@ -420,24 +425,6 @@ namespace pxsim.instructions {
}
});
}
let cmps = props.stepToCmps[i];
if (cmps) {
cmps.forEach(cmpInst => {
let cmp = board.addComponent(cmpInst)
let colOffset = (<any>cmpInst.visual).breadboardStartColIdx || 0;
let rowCol: BBRowCol = [`${cmpInst.breadboardStartRow}`, `${colOffset + cmpInst.breadboardStartColumn}`];
//last step
if (i === step) {
board.highlightBreadboardPin(rowCol);
if (cmpInst.visual === "buttonpair") {
//TODO: don't specialize this
let rowCol2: BBRowCol = [`${cmpInst.breadboardStartRow}`, `${cmpInst.breadboardStartColumn + 3}`];
board.highlightBreadboardPin(rowCol2);
}
svg.addClass(cmp.element, "notgrayed");
}
});
}
}
}
function mkPanel() {
@ -463,7 +450,7 @@ namespace pxsim.instructions {
cmps.forEach(c => {
let quant = 1;
// TODO: don't special case this
if (c.visual === "buttonpair") {
if (c.visual.builtIn === "buttonpair") {
quant = 2;
}
let cmp = mkCmpDiv(c.visual, {
@ -516,7 +503,7 @@ namespace pxsim.instructions {
let wires = (props.stepToWires[step] || []);
let mkLabel = (loc: Loc) => {
if (loc.type === "breadboard") {
let [row, col] = (<BBLoc>loc).rowCol;
let {row, col} = (<BBLoc>loc);
return `(${row},${col})`
} else
return (<BoardLoc>loc).pin;
@ -536,17 +523,23 @@ namespace pxsim.instructions {
});
let cmps = (props.stepToCmps[step] || []);
cmps.forEach(c => {
let l: BBRowCol = [`${c.breadboardStartRow}`, `${c.breadboardStartColumn}`];
let locs = [l];
if (c.visual === "buttonpair") {
let locs: BBLoc[];
if (c.visual.builtIn === "buttonpair") {
//TODO: don't special case this
let l2: BBRowCol = [`${c.breadboardStartRow}`, `${c.breadboardStartColumn + 3}`];
locs.push(l2);
locs = [c.breadboardConnections[0], c.breadboardConnections[2]]
} else {
locs = [c.breadboardConnections[0]];
}
locs.forEach((l, i) => {
let [row, col] = l;
let topLbl: string;
if (l) {
let {row, col} = l;
topLbl = `(${row},${col})`;
} else {
topLbl = "";
}
let cmp = mkCmpDiv(c.visual, {
top: `(${row},${col})`,
top: topLbl,
topSize: LOC_LBL_SIZE,
cmpHeight: REQ_CMP_HEIGHT,
cmpScale: REQ_CMP_SCALE
@ -656,8 +649,8 @@ ${tsPackage}
activeComponents.sort();
let props = mkBoardProps({
boardDef: boardDef,
cmpDefs: cmpDefs,
cmpList: activeComponents,
partDefs: cmpDefs,
partsList: activeComponents,
fnArgs: fnArgs,
getBBCoord: dummyBreadboard.getCoord.bind(dummyBreadboard)
});

View File

@ -3,10 +3,18 @@
/// <reference path="../libs/microbit/dal.d.ts"/>
namespace pxsim {
export type BBRowCol = [/*row*/string, /*column*/string];
export type BoardPin = string;
export interface BBLoc { type: "breadboard", rowCol: BBRowCol };
export interface BoardLoc { type: "dalboard", pin: BoardPin };
export interface BBLoc {
type: "breadboard",
row: string,
col: string
xOffset?: number,
yOffset?: number
};
export interface BoardLoc {
type: "dalboard",
pin: BoardPin
};
export type Loc = BBLoc | BoardLoc;
export function initRuntimeWithDalBoard() {
@ -197,11 +205,12 @@ namespace pxsim.visuals {
return minIdx;
}
export interface IBoardComponent<T> {
export interface IBoardPart<T> {
style: string,
element: SVGElement,
overElement?: SVGElement,
defs: SVGElement[],
init(bus: EventBus, state: T, svgEl: SVGSVGElement, gpioPins: string[], otherArgs: string[]): void, //NOTE: constructors not supported in interfaces
init(bus: EventBus, state: T, svgEl: SVGSVGElement, otherParams: Map<string>): void, //NOTE: constructors not supported in interfaces
moveToCoord(xy: Coord): void,
updateState(): void,
updateTheme(): void,
@ -224,7 +233,8 @@ namespace pxsim.visuals {
}
export type WireColor =
"black" | "white" | "gray" | "purple" | "blue" | "green" | "yellow" | "orange" | "red" | "brown";
"black" | "white" | "gray" | "purple" | "blue" | "green" | "yellow" | "orange" | "red" | "brown" | "pink";
export const GPIO_WIRE_COLORS = ["pink", "green", "purple", "orange", "yellow"];
export const WIRE_COLOR_MAP: Map<string> = {
black: "#514f4d",
white: "#fcfdfc",
@ -236,6 +246,7 @@ namespace pxsim.visuals {
orange: "#fdb262",
red: "#f44f43",
brown: "#c89764",
pink: "#ff80fa"
}
export function mapWireColor(clr: WireColor | string): string {
return WIRE_COLOR_MAP[clr] || clr;

View File

@ -2,8 +2,8 @@ namespace pxsim.visuals {
export interface BoardHostOpts {
state: DalBoard,
boardDef: BoardDefinition,
cmpsList?: string[],
cmpDefs: Map<PartDefinition>,
partsList?: string[],
partDefs: Map<PartDefinition>,
fnArgs: any,
forceBreadboard?: boolean,
maxWidth?: string,
@ -11,13 +11,15 @@ namespace pxsim.visuals {
wireframe?: boolean
}
export class BoardHost {
private components: IBoardComponent<any>[] = [];
private parts: IBoardPart<any>[] = [];
private wireFactory: WireFactory;
private breadboard: Breadboard;
private fromBBCoord: (xy: Coord) => Coord;
private fromMBCoord: (xy: Coord) => Coord;
private boardView: BoardView;
private view: SVGSVGElement;
private partGroup: SVGGElement;
private partOverGroup: SVGGElement;
private style: SVGStyleElement;
private defs: SVGDefsElement;
private state: DalBoard;
@ -26,7 +28,7 @@ namespace pxsim.visuals {
constructor(opts: BoardHostOpts) {
this.state = opts.state;
let onboardCmps = opts.boardDef.onboardComponents || [];
let activeComponents = (opts.cmpsList || []).filter(c => onboardCmps.indexOf(c) < 0);
let activeComponents = (opts.partsList || []).filter(c => onboardCmps.indexOf(c) < 0);
activeComponents.sort();
this.useCrocClips = opts.boardDef.useCrocClips;
@ -68,6 +70,8 @@ namespace pxsim.visuals {
this.fromMBCoord = composition.toHostCoord1;
this.fromBBCoord = composition.toHostCoord2;
let pinDist = composition.scaleUnit;
this.partGroup = over;
this.partOverGroup = <SVGGElement>svg.child(this.view, "g");
this.style = <SVGStyleElement>svg.child(this.view, "style", {});
this.defs = <SVGDefsElement>svg.child(this.view, "defs", {});
@ -76,16 +80,18 @@ namespace pxsim.visuals {
let allocRes = allocateDefinitions({
boardDef: opts.boardDef,
cmpDefs: opts.cmpDefs,
partDefs: opts.partDefs,
fnArgs: opts.fnArgs,
getBBCoord: this.breadboard.getCoord.bind(this.breadboard),
cmpList: activeComponents,
partsList: activeComponents,
});
this.addAll(allocRes);
} else {
let el = this.boardView.getView().el;
this.view = el;
this.partGroup = <SVGGElement>svg.child(this.view, "g");
this.partOverGroup = <SVGGElement>svg.child(this.view, "g");
if (opts.maxWidth)
svg.hydrate(this.view, { width: opts.maxWidth });
if (opts.maxHeight)
@ -99,7 +105,7 @@ namespace pxsim.visuals {
this.boardView.highlightPin(pinNm);
}
public highlightBreadboardPin(rowCol: BBRowCol) {
public highlightBreadboardPin(rowCol: BBLoc) {
this.breadboard.highlightLoc(rowCol);
}
@ -120,10 +126,10 @@ namespace pxsim.visuals {
}
private updateState() {
this.components.forEach(c => c.updateState());
this.parts.forEach(c => c.updateState());
}
private getBBCoord(rowCol: BBRowCol) {
private getBBCoord(rowCol: BBLoc) {
let bbCoord = this.breadboard.getCoord(rowCol);
return this.fromBBCoord(bbCoord);
}
@ -134,7 +140,7 @@ namespace pxsim.visuals {
public getLocCoord(loc: Loc): Coord {
let coord: Coord;
if (loc.type === "breadboard") {
let rowCol = (<BBLoc>loc).rowCol;
let rowCol = (<BBLoc>loc);
coord = this.getBBCoord(rowCol);
} else {
let pinNm = (<BoardLoc>loc).pin;
@ -147,47 +153,62 @@ namespace pxsim.visuals {
return coord;
}
public addComponent(cmpDesc: CmpInst): IBoardComponent<any> {
let cmp: IBoardComponent<any> = null;
public addPart(partInst: PartInst): IBoardPart<any> {
let part: IBoardPart<any> = null;
let colOffset = 0;
if (typeof cmpDesc.visual === "string") {
let builtinVisual = cmpDesc.visual as string;
let cnstr = builtinComponentSimVisual[builtinVisual];
let stateFn = builtinComponentSimState[builtinVisual];
cmp = cnstr();
cmp.init(this.state.bus, stateFn(this.state), this.view, cmpDesc.microbitPins, cmpDesc.otherArgs);
if (partInst.simulationBehavior) {
//TODO: seperate simulation behavior from builtin visual
let builtinBehavior = partInst.simulationBehavior;
let cnstr = builtinComponentSimVisual[builtinBehavior];
let stateFn = builtinComponentSimState[builtinBehavior];
part = cnstr();
part.init(this.state.bus, stateFn(this.state), this.view, partInst.params);
} else {
let vis = cmpDesc.visual as PartVisualDefinition;
cmp = new GenericPart(vis);
colOffset = vis.extraColumnOffset || 0;
let vis = partInst.visual as PartVisualDefinition;
part = new GenericPart(vis);
}
this.components.push(cmp);
this.view.appendChild(cmp.element);
if (cmp.defs)
cmp.defs.forEach(d => this.defs.appendChild(d));
this.style.textContent += cmp.style || "";
let rowCol = <BBRowCol>[`${cmpDesc.breadboardStartRow}`, `${colOffset + cmpDesc.breadboardStartColumn}`];
this.parts.push(part);
this.partGroup.appendChild(part.element);
if (part.overElement)
this.partOverGroup.appendChild(part.overElement);
if (part.defs)
part.defs.forEach(d => this.defs.appendChild(d));
this.style.textContent += part.style || "";
let colIdx = partInst.startColumnIdx;
let rowIdx = partInst.startRowIdx;
let row = getRowName(rowIdx);
let col = getColumnName(colIdx);
let xOffset = partInst.bbFit.xOffset / partInst.visual.pinDistance;
let yOffset = partInst.bbFit.yOffset / partInst.visual.pinDistance;
let rowCol = <BBLoc>{
type: "breadboard",
row: row,
col: col,
xOffset: xOffset,
yOffset: yOffset
};
let coord = this.getBBCoord(rowCol);
cmp.moveToCoord(coord);
part.moveToCoord(coord);
let getCmpClass = (type: string) => `sim-${type}-cmp`;
let cls = getCmpClass(name);
svg.addClass(cmp.element, cls);
svg.addClass(cmp.element, "sim-cmp");
cmp.updateTheme();
cmp.updateState();
return cmp;
let cls = getCmpClass(partInst.name);
svg.addClass(part.element, cls);
svg.addClass(part.element, "sim-cmp");
part.updateTheme();
part.updateState();
return part;
}
public addWire(inst: WireInst): Wire {
return this.wireFactory.addWire(inst.start, inst.end, inst.color, this.useCrocClips);
}
public addAll(basicWiresAndCmpsAndWires: AllocatorResult) {
let {powerWires, components} = basicWiresAndCmpsAndWires;
powerWires.forEach(w => this.addWire(w));
components.forEach((cAndWs, idx) => {
let {component, wires} = cAndWs;
wires.forEach(w => this.addWire(w));
this.addComponent(component);
});
public addAll(allocRes: AllocatorResult) {
allocRes.partsAndWires.forEach(pAndWs => {
let part = pAndWs.part;
if (part)
this.addPart(part)
let wires = pAndWs.wires;
if (wires)
wires.forEach(w => this.addWire(w));
})
}
}
}

View File

@ -107,10 +107,10 @@ namespace pxsim.visuals {
}
`
// Pin rows and coluns
const MID_ROWS = 10;
export const BREADBOARD_MID_ROWS = 10;
export const BREADBOARD_MID_COLS = 30;
const MID_ROW_GAPS = [4, 4];
const MID_ROW_AND_GAPS = MID_ROWS + MID_ROW_GAPS.length;
const MID_COLS = 30;
const MID_ROW_AND_GAPS = BREADBOARD_MID_ROWS + MID_ROW_GAPS.length;
const BAR_ROWS = 2;
const BAR_COLS = 25;
const POWER_ROWS = BAR_ROWS * 2;
@ -118,14 +118,14 @@ namespace pxsim.visuals {
const BAR_COL_GAPS = [4, 9, 14, 19];
const BAR_COL_AND_GAPS = BAR_COLS + BAR_COL_GAPS.length;
// Essential dimensions
const WIDTH = PIN_DIST * (MID_COLS + 3);
const WIDTH = PIN_DIST * (BREADBOARD_MID_COLS + 3);
const HEIGHT = PIN_DIST * (MID_ROW_AND_GAPS + POWER_ROWS + 5.5);
const MID_RATIO = 2.0 / 3.0;
const BAR_RATIO = (1.0 - MID_RATIO) * 0.5;
const MID_HEIGHT = HEIGHT * MID_RATIO;
const BAR_HEIGHT = HEIGHT * BAR_RATIO;
// Pin grids
const MID_GRID_WIDTH = (MID_COLS - 1) * PIN_DIST;
const MID_GRID_WIDTH = (BREADBOARD_MID_COLS - 1) * PIN_DIST;
const MID_GRID_HEIGHT = (MID_ROW_AND_GAPS - 1) * PIN_DIST;
const MID_GRID_X = (WIDTH - MID_GRID_WIDTH) / 2.0;
const MID_GRID_Y = BAR_HEIGHT + (MID_HEIGHT - MID_GRID_HEIGHT) / 2.0;
@ -152,6 +152,10 @@ namespace pxsim.visuals {
const SMALL_CHANNEL_HEIGHT = PIN_DIST * 0.05;
// Background
const BACKGROUND_ROUNDING = PIN_DIST * 0.3;
// Row and column helpers
const alphabet = "abcdefghij".split("").reverse();
export function getColumnName(colIdx: number): string { return `${colIdx + 1}` };
export function getRowName(rowIdx: number): string { return alphabet[rowIdx] };
export interface GridPin {
el: SVGElement,
@ -321,12 +325,14 @@ namespace pxsim.visuals {
return null;
return pin;
}
public getCoord(rowCol: BBRowCol): Coord {
let [row, col] = rowCol;
public getCoord(rowCol: BBLoc): Coord {
let {row, col, xOffset, yOffset} = rowCol;
let pin = this.getPin(row, col);
if (!pin)
return null;
return [pin.cx, pin.cy];
let xOff = (xOffset || 0) * PIN_DIST;
let yOff = (yOffset || 0) * PIN_DIST;
return [pin.cx + xOff, pin.cy + yOff];
}
public getPinDist() {
@ -371,14 +377,11 @@ namespace pxsim.visuals {
mkChannel(BAR_HEIGHT + MID_HEIGHT, SMALL_CHANNEL_HEIGHT);
//-----pins
const getMidTopOrBot = (rowIdx: number) => rowIdx < MID_ROWS / 2.0 ? "b" : "t";
const getMidTopOrBot = (rowIdx: number) => rowIdx < BREADBOARD_MID_ROWS / 2.0 ? "b" : "t";
const getBarTopOrBot = (colIdx: number) => colIdx < POWER_COLS / 2.0 ? "b" : "t";
const alphabet = "abcdefghij".split("").reverse();
const getColName = (colIdx: number) => `${colIdx + 1}`;
const getMidRowName = (rowIdx: number) => alphabet[rowIdx];
const getMidGroupName = (rowIdx: number, colIdx: number) => {
let botOrTop = getMidTopOrBot(rowIdx);
let colNm = getColName(colIdx);
let colNm = getColumnName(colIdx);
return `${botOrTop}${colNm}`;
};
const getBarRowName = (rowIdx: number) => rowIdx === 0 ? "-" : "+";
@ -392,13 +395,13 @@ namespace pxsim.visuals {
let midGridRes = mkGrid({
xOffset: MID_GRID_X,
yOffset: MID_GRID_Y,
rowCount: MID_ROWS,
colCount: MID_COLS,
rowCount: BREADBOARD_MID_ROWS,
colCount: BREADBOARD_MID_COLS,
pinDist: PIN_DIST,
mkPin: mkBBPin,
mkHoverPin: mkBBHoverPin,
getRowName: getMidRowName,
getColName: getColName,
getRowName: getRowName,
getColName: getColumnName,
getGroupName: getMidGroupName,
rowIdxsWithGap: MID_ROW_GAPS,
});
@ -415,7 +418,7 @@ namespace pxsim.visuals {
mkPin: mkBBPin,
mkHoverPin: mkBBHoverPin,
getRowName: getBarRowName,
getColName: getColName,
getColName: getColumnName,
getGroupName: getBarGroupName,
colIdxsWithGap: BAR_COL_GAPS,
});
@ -433,7 +436,7 @@ namespace pxsim.visuals {
mkPin: mkBBPin,
mkHoverPin: mkBBHoverPin,
getRowName: getBarRowName,
getColName: getColName,
getColName: getColumnName,
getGroupName: getBarGroupName,
colIdxsWithGap: BAR_COL_GAPS.map(g => g + BAR_COLS),
});
@ -460,39 +463,39 @@ namespace pxsim.visuals {
const mkBBLabelAtPin = (row: string, col: string, xOffset: number, yOffset: number, txt: string, group?: string): GridLabel => {
let size = PIN_LBL_SIZE;
let rotation = LBL_ROTATION;
let loc = this.getCoord([row, col]);
let loc = this.getCoord({type: "breadboard", row: row, col: col});
let [cx, cy] = loc;
let t = mkBBLabel(cx + xOffset, cy + yOffset, size, rotation, txt, group);
return t;
}
//columns
for (let colIdx = 0; colIdx < MID_COLS; colIdx++) {
let colNm = getColName(colIdx);
for (let colIdx = 0; colIdx < BREADBOARD_MID_COLS; colIdx++) {
let colNm = getColumnName(colIdx);
//top
let rowTIdx = 0;
let rowTNm = getMidRowName(rowTIdx);
let rowTNm = getRowName(rowTIdx);
let groupT = getMidGroupName(rowTIdx, colIdx);
let lblT = mkBBLabelAtPin(rowTNm, colNm, 0, -PIN_DIST, colNm, groupT);
this.allLabels.push(lblT);
//bottom
let rowBIdx = MID_ROWS - 1;
let rowBNm = getMidRowName(rowBIdx);
let rowBIdx = BREADBOARD_MID_ROWS - 1;
let rowBNm = getRowName(rowBIdx);
let groupB = getMidGroupName(rowBIdx, colIdx);
let lblB = mkBBLabelAtPin(rowBNm, colNm, 0, +PIN_DIST, colNm, groupB);
this.allLabels.push(lblB);
}
//rows
for (let rowIdx = 0; rowIdx < MID_ROWS; rowIdx++) {
let rowNm = getMidRowName(rowIdx);
for (let rowIdx = 0; rowIdx < BREADBOARD_MID_ROWS; rowIdx++) {
let rowNm = getRowName(rowIdx);
//top
let colTIdx = 0;
let colTNm = getColName(colTIdx);
let colTNm = getColumnName(colTIdx);
let lblT = mkBBLabelAtPin(rowNm, colTNm, -PIN_DIST, 0, rowNm);
this.allLabels.push(lblT);
//top
let colBIdx = MID_COLS - 1;
let colBNm = getColName(colBIdx);
let colBIdx = BREADBOARD_MID_COLS - 1;
let colBNm = getColumnName(colBIdx);
let lblB = mkBBLabelAtPin(rowNm, colBNm, +PIN_DIST, 0, rowNm);
this.allLabels.push(lblB);
}
@ -635,8 +638,8 @@ namespace pxsim.visuals {
return {el: this.bb, y: 0, x: 0, w: WIDTH, h: HEIGHT};
}
public highlightLoc(rowCol: BBRowCol) {
let [row, col] = rowCol;
public highlightLoc(rowCol: BBLoc) {
let {row, col} = rowCol;
let pin = this.rowColToPin[row][col];
let {cx, cy} = pin;
let lbls = this.rowColToLbls[row][col];

View File

@ -92,7 +92,7 @@ namespace pxsim.visuals {
pointer-events:none;
}
`;
export class ButtonPairView implements IBoardComponent<ButtonPairState> {
export class ButtonPairView implements IBoardPart<ButtonPairState> {
public element: SVGElement;
public defs: SVGElement[];
public style = BUTTON_PAIR_STYLE;

View File

@ -5,13 +5,13 @@ namespace pxsim.visuals {
image: partVisual.image,
width: partVisual.width,
height: partVisual.height,
imageUnitDist: partVisual.pinDist,
imageUnitDist: partVisual.pinDistance,
targetUnitDist: PIN_DIST
});
return imgAndSize;
}
export class GenericPart implements IBoardComponent<any> {
export class GenericPart implements IBoardPart<any> {
public style: string = "";
public element: SVGElement;
defs: SVGElement[] = [];
@ -19,11 +19,6 @@ namespace pxsim.visuals {
constructor(partVisual: PartVisualDefinition) {
let imgAndSize = mkGenericPartSVG(partVisual);
let img = imgAndSize.el;
let scaleFn = mkScaleFn(partVisual.pinDist, PIN_DIST);
let [pinX, pinY] = partVisual.firstPin;
let left = -scaleFn(pinX);
let top = -scaleFn(pinY);
translateEl(img, [left, top]); // So that 0,0 is on the first pin
this.element = svg.elt("g");
this.element.appendChild(img);
}
@ -33,7 +28,7 @@ namespace pxsim.visuals {
}
//unused
init(bus: EventBus, state: any, svgEl: SVGSVGElement, gpioPins: string[], otherArgs: string[]): void { }
init(bus: EventBus, state: any, svgEl: SVGSVGElement): void { }
updateState(): void { }
updateTheme(): void { }
}

View File

@ -68,7 +68,7 @@ namespace pxsim.visuals {
}
`
export class LedMatrixView implements IBoardComponent<LedMatrixState> {
export class LedMatrixView implements IBoardPart<LedMatrixState> {
private background: SVGElement;
private ledsOuter: SVGElement[];
private leds: SVGElement[];

View File

@ -107,13 +107,12 @@ namespace pxsim.visuals {
public cy: number;
constructor(xy: Coord = [0, 0]) {
let circle = <SVGElement>svg.elt("rect");
let el = <SVGElement>svg.elt("rect");
let r = PIXEL_RADIUS;
let [cx, cy] = xy;
let y = cy - r;
let x = 0;
svg.hydrate(circle, { x: "-50%", y: y, width: "100%", height: r*2, class: "sim-neopixel" });
this.el = circle;
svg.hydrate(el, { x: "-50%", y: y, width: "100%", height: r * 2, class: "sim-neopixel" });
this.el = el;
this.cy = cy;
}
@ -192,9 +191,15 @@ namespace pxsim.visuals {
}
};
function gpioPinToPinNumber(gpioPin: string): number {
let pinNumStr = gpioPin.split("P")[1];
let pinNum = Number(pinNumStr) + 7 /*MICROBIT_ID_IO_P0; TODO: don't hardcode this, import enums.d.ts*/;
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 {
@ -214,7 +219,7 @@ namespace pxsim.visuals {
return mode;
}
export class NeoPixelView implements IBoardComponent<NeoPixelState> {
export class NeoPixelView implements IBoardPart<NeoPixelState> {
public style: string = `
.sim-neopixel-canvas {
}
@ -232,6 +237,7 @@ namespace pxsim.visuals {
}
`;
public element: SVGElement;
public overElement: SVGElement;
public defs: SVGElement[];
private state: NeoPixelState;
private canvas: NeoPixelCanvas;
@ -241,22 +247,24 @@ namespace pxsim.visuals {
private pin: number;
private mode: NeoPixelMode;
public init(bus: EventBus, state: NeoPixelState, svgEl: SVGSVGElement, gpioPins: string[], otherArgs: string[]): void {
U.assert(otherArgs.length === 1, "NeoPixels assumes a RGB vs RGBW mode is passed to it");
let modeStr = otherArgs[0];
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 = gpioPins[0];
this.pin = gpioPinToPinNumber(pinStr);
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.child(this.stripGroup, "g", { class: "sim-neopixel-canvas-parent" });
let canvasG = svg.elt("g", { class: "sim-neopixel-canvas-parent" });
this.overElement = canvasG;
canvasG.appendChild(canvas.canvas);
this.updateStripLoc();
}
@ -268,6 +276,7 @@ namespace pxsim.visuals {
}
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.
}

View File

@ -455,7 +455,7 @@ namespace pxsim.visuals {
let wireEls: Wire;
if (withCrocs && end.type == "dalboard") {
let boardPin = (<BoardLoc>end).pin;
if (boardPin == "P0" || boardPin == "P1" || boardPin == "P0" || boardPin == "GND" || boardPin == "+3v3" ) {
if (boardPin == "P0" || boardPin == "P1" || boardPin == "P2" || boardPin == "GND" || boardPin == "+3v3" ) {
//HACK
wireEls = this.drawWireWithCrocs(startLoc, endLoc, color);
} else {