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": { "buttonpair": {
"visual": "ledmatrix", "simulationBehavior": "buttonpair",
"breadboardColumnsNeeded": 8, "visual": {
"breadboardStartRow": "h", "builtIn": "buttonpair",
"pinAllocation": { "width": 75,
"type": "auto", "height": 45,
"gpioPinsNeeded": [5, 5] "pinDistance": 15,
"pinLocations": [
{"x": 0, "y": 0},
{"x": 30, "y": 45},
{"x": 45, "y": 0},
{"x": 75, "y": 45}
]
}, },
"assemblyStep": 0, "numberOfPins": 4,
"wires": [ "pinDefinitions": [
{"start": ["breadboard", "j", 0], "end": ["GPIO", 5], "color": "purple", "assemblyStep": 1}, {"target": "P14", "style": "male", "orientation": "-Z"},
{"start": ["breadboard", "j", 1], "end": ["GPIO", 6], "color": "purple", "assemblyStep": 1}, {"target": "ground", "style": "male", "orientation": "-Z"},
{"start": ["breadboard", "j", 2], "end": ["GPIO", 7], "color": "purple", "assemblyStep": 1}, {"target": "P15", "style": "male", "orientation": "-Z"},
{"start": ["breadboard", "j", 3], "end": ["GPIO", 8], "color": "purple", "assemblyStep": 1}, {"target": "ground", "style": "male", "orientation": "-Z"}
{"start": ["breadboard", "a", 7], "end": ["GPIO", 9], "color": "purple", "assemblyStep": 1}, ],
{"start": ["breadboard", "a", 0], "end": ["GPIO", 0], "color": "green", "assemblyStep": 2}, "instantiation": {
{"start": ["breadboard", "a", 1], "end": ["GPIO", 1], "color": "green", "assemblyStep": 2}, "kind": "singleton"
{"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"]
}, },
"assemblyStep": 0, "assembly": [
"wires": [ {"part": true},
{"start": ["breadboard", "j", 0], "end": ["GPIO", 0], "color": "yellow", "assemblyStep": 1}, {"pinIndices": [0, 1]},
{"start": ["breadboard", "a", 2], "end": "ground", "color": "blue", "assemblyStep": 1}, {"pinIndices": [2, 3]}
{"start": ["breadboard", "j", 3], "end": ["GPIO", 1], "color": "orange", "assemblyStep": 2},
{"start": ["breadboard", "a", 5], "end": "ground", "color": "blue", "assemblyStep": 2}
] ]
}, },
"neopixel": { "neopixel": {
"visual": "neopixel", "simulationBehavior": "neopixel",
"breadboardColumnsNeeded": 5, "visual": {
"breadboardStartRow": "h", "builtIn": "neopixel",
"pinAllocation": { "width": 58,
"type": "factoryfunction", "height": 113,
"functionName": "neopixel.create", "pinDistance": 9,
"pinArgPositions": [0], "pinLocations": [
"otherArgPositions": [1] {"x": 10, "y": 0},
{"x": 19, "y": 0},
{"x": 28, "y": 0}
]
}, },
"assemblyStep": 0, "numberOfPins": 3,
"wires": [ "pinDefinitions": [
{"start": ["breadboard", "j", 1], "end": "ground", "color": "blue", "assemblyStep": 1}, {"target": {"pinInstantiationIdx": 0}, "style": "solder", "orientation": "+Z"},
{"start": ["breadboard", "j", 2], "end": "threeVolt", "color": "red", "assemblyStep": 2}, {"target": "threeVolt", "style": "solder", "orientation": "+Z"},
{"start": ["breadboard", "j", 3], "end": ["GPIO", 0], "color": "green", "assemblyStep": 2} {"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": { "speaker": {
"numberOfPins": 2,
"visual": { "visual": {
"image": "/parts/speaker.svg", "image": "/parts/speaker.svg",
"width": 500, "width": 500,
"height": 500, "height": 500,
"firstPin": [180, 135], "pinDistance": 70,
"pinDist": 70, "pinLocations": [
"extraColumnOffset": 1 {"x": 180, "y": 135},
{"x": 320, "y": 135}
]
}, },
"breadboardColumnsNeeded": 5, "pinDefinitions": [
"breadboardStartRow": "f", {"target": "P0", "style": "male", "orientation": "-Z"},
"pinAllocation": { {"target": "ground", "style": "male", "orientation": "-Z"}
"type": "auto", ],
"gpioPinsNeeded": 1 "instantiation": {"kind": "singleton"},
}, "assembly": [
"assemblyStep": 0, {"part": true, "pinIndices": [0]},
"wires": [ {"pinIndices": [1]}
{"start": ["breadboard", "j", 1], "end": ["GPIO", 0], "color": "#ff80fa", "assemblyStep": 1},
{"start": ["breadboard", "j", 3], "end": "ground", "color": "blue", "assemblyStep": 1}
] ]
} }
} }

View File

@ -1,60 +1,81 @@
namespace pxsim { namespace pxsim {
export interface AllocatorOpts { const GROUND_COLOR = "blue";
const POWER_COLOR = "red";
export interface AllocatorOpts {
boardDef: BoardDefinition, boardDef: BoardDefinition,
cmpDefs: Map<PartDefinition>, partDefs: Map<PartDefinition>,
partsList: string[]
fnArgs: any, fnArgs: any,
getBBCoord: (loc: BBRowCol) => visuals.Coord, // Used for finding the nearest available power pins
cmpList: string[] getBBCoord: (loc: BBLoc) => visuals.Coord,
}; };
export interface AllocatorResult { export interface AllocatorResult {
powerWires: WireInst[], partsAndWires: PartAndWiresInst[],
components: CmpAndWireInst[]
} }
export interface PartInst {
export interface CmpAndWireInst {
component: CmpInst,
wires: WireInst[]
}
export interface CmpInst {
name: string, name: string,
breadboardStartColumn: number, simulationBehavior?: string,
breadboardStartRow: string, visual: PartVisualDefinition,
assemblyStep: number, bbFit: PartBBFit,
visual: string | PartVisualDefinition, startColumnIdx: number,
microbitPins: string[], startRowIdx: number,
otherArgs?: string[], breadboardConnections: BBLoc[],
params: Map<string>,
} }
export interface WireInst { export interface WireInst {
start: Loc, start: Loc,
end: Loc, end: Loc,
color: string, 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, name: string,
def: PartDefinition, def: PartDefinition,
pinsAssigned: string[], partParams: Map<string>,
pinsNeeded: number | number[], pins: PinIR[],
breadboardColumnsNeeded: number, bbFit: PartBBFit,
otherArgs?: string[],
}
interface AllocLocOpts {
nearestBBPin?: BBRowCol,
startColumn?: number,
cmpGPIOPins?: string[],
}; };
interface AllocWireOpts { interface PartPlacement extends PartIR {
startColumn: number, startColumnIdx: number,
cmpGPIOPins: string[], startRowIdx: number,
} };
interface AllocBlock { type WireIRLoc = PinTarget | BBLoc;
cmpIdx: number, interface WireIR {
cmpBlkIdx: number, pinIdx: number,
gpioNeeded: number, start: WireIRLoc,
gpioAssigned: string[] end: WireIRLoc,
color: string,
} }
interface PartIRAndWireIRs extends PartPlacement {
wires: WireIR[],
};
interface PowerUsage { interface PowerUsage {
topGround: boolean, topGround: boolean,
topThreeVolt: boolean, topThreeVolt: boolean,
@ -63,18 +84,27 @@ namespace pxsim {
singleGround: boolean, singleGround: boolean,
singleThreeVolt: 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; let isBot = false;
if (location[0] === "breadboard") { if (typeof location !== "string" && (<BBLoc>location).type === "breadboard") {
let row = <string>location[1]; let bbLoc = <BBLoc>location;
let row = bbLoc.row;
isBot = 0 <= ["a", "b", "c", "d", "e"].indexOf(row); isBot = 0 <= ["a", "b", "c", "d", "e"].indexOf(row);
} }
return isBot; return isBot;
} }
const arrCount = (a: boolean[]) => a.reduce((p, n) => p + (n ? 1 : 0), 0); const arrCount = (a: boolean[]) => a.reduce((p, n) => p + (n ? 1 : 0), 0);
const arrAny = (a: boolean[]) => arrCount(a) > 0; const arrAny = (a: boolean[]) => arrCount(a) > 0;
function computePowerUsage(wireDef: WireDefinition): PowerUsage { function computePowerUsage(wire: WireIR): PowerUsage {
let ends = [wireDef.start, wireDef.end]; let ends = [wire.start, wire.end];
let endIsGround = ends.map(e => e === "ground"); let endIsGround = ends.map(e => e === "ground");
let endIsThreeVolt = ends.map(e => e === "threeVolt"); let endIsThreeVolt = ends.map(e => e === "threeVolt");
let endIsBot = ends.map(e => isOnBreadboardBottom(e)); let endIsBot = ends.map(e => isOnBreadboardBottom(e));
@ -115,10 +145,21 @@ namespace pxsim {
function copyDoubleArray(a: string[][]) { function copyDoubleArray(a: string[][]) {
return a.map(b => b.map(p => p)); 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); U.assert(!!arg, "Invalid pin: " + arg);
let pin = arg.split("DigitalPin.")[1]; let pin = arg.split("DigitalPin.")[1];
return pin; return <MicrobitPin>pin;
} }
function mkReverseMap(map: {[key: string]: string}) { function mkReverseMap(map: {[key: string]: string}) {
let origKeys: string[] = []; let origKeys: string[] = [];
@ -135,25 +176,239 @@ namespace pxsim {
} }
return newMap; return newMap;
} }
function isConnectedToBB(pin: PartPinDefinition): boolean {
return pin.orientation === "-Z" && pin.style === "male";
}
class Allocator { class Allocator {
//TODO: better handling of allocation errors
private opts: AllocatorOpts; private opts: AllocatorOpts;
private availablePowerPins = { private availablePowerPins = {
top: { top: {
threeVolt: 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 => <BBRowCol>["-", `${n}`]), ground: mkRange(26, 51).map(n => <BBLoc>{type: "breadboard", row: "-", col: `${n}`}),
}, },
bottom: { bottom: {
threeVolt: 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 => <BBRowCol>["-", `${n}`]), ground: mkRange(1, 26).map(n => <BBLoc>{type: "breadboard", row: "-", col: `${n}`}),
}, },
}; };
private powerUsage: PowerUsage; private powerUsage: PowerUsage;
private availableWireColors: string[];
constructor(opts: AllocatorOpts) { constructor(opts: AllocatorOpts) {
this.opts = opts; 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") { if (location === "ground" || location === "threeVolt") {
//special case if there is only a single ground or three volt pin in the whole build //special case if there is only a single ground or three volt pin in the whole build
if (location === "ground" && this.powerUsage.singleGround) { if (location === "ground" && this.powerUsage.singleGround) {
@ -164,8 +419,8 @@ namespace pxsim {
return {type: "dalboard", pin: boardThreeVoltPin}; return {type: "dalboard", pin: boardThreeVoltPin};
} }
U.assert(!!opts.nearestBBPin); U.assert(!!opts.referenceBBPin);
let nearestCoord = this.opts.getBBCoord(opts.nearestBBPin); let nearestCoord = this.opts.getBBCoord(opts.referenceBBPin);
let firstTopAndBot = [ let firstTopAndBot = [
this.availablePowerPins.top.ground[0] || this.availablePowerPins.top.threeVolt[0], this.availablePowerPins.top.ground[0] || this.availablePowerPins.top.threeVolt[0],
this.availablePowerPins.bottom.ground[0] || this.availablePowerPins.bottom.threeVolt[0] this.availablePowerPins.bottom.ground[0] || this.availablePowerPins.bottom.threeVolt[0]
@ -177,7 +432,7 @@ namespace pxsim {
//TODO //TODO
} }
let nearTop = visuals.findClosestCoordIdx(nearestCoord, firstTopAndBot) == 0; let nearTop = visuals.findClosestCoordIdx(nearestCoord, firstTopAndBot) == 0;
let barPins: BBRowCol[]; let barPins: BBLoc[];
if (nearTop) { if (nearTop) {
if (location === "ground") { if (location === "ground") {
barPins = this.availablePowerPins.top.ground; barPins = this.availablePowerPins.top.ground;
@ -203,17 +458,9 @@ namespace pxsim {
this.availablePowerPins.bottom.ground.splice(closestPinIdx, 1); this.availablePowerPins.bottom.ground.splice(closestPinIdx, 1);
this.availablePowerPins.bottom.threeVolt.splice(closestPinIdx, 1); this.availablePowerPins.bottom.threeVolt.splice(closestPinIdx, 1);
} }
return {type: "breadboard", rowCol: pin}; return pin;
} else if (location[0] === "breadboard") { } else if ((<BBLoc>location).type === "breadboard") {
U.assert(!!opts.startColumn); return <BBLoc>location;
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};
} else if (location === "MOSI" || location === "MISO" || location === "SCK") { } else if (location === "MOSI" || location === "MISO" || location === "SCK") {
if (!this.opts.boardDef.spiPins) if (!this.opts.boardDef.spiPins)
console.debug("No SPI pin mappings found!"); 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; let pin = (<any>this.opts.boardDef.i2cPins)[location as string] as string;
return {type: "dalboard", pin: pin}; return {type: "dalboard", pin: pin};
} else { } else {
//TODO //it must be a MicrobitPin
U.assert(false); U.assert(typeof location === "string", "Unknown location type: " + location);
return null; 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; let boardGround = this.opts.boardDef.groundPins[0] || null;
if (!boardGround) { if (!boardGround) {
console.log("No available ground pin on board!"); console.log("No available ground pin on board!");
@ -238,7 +488,7 @@ namespace pxsim {
} }
return boardGround; return boardGround;
} }
private getBoardThreeVoltPin() { private getBoardThreeVoltPin(): string {
let threeVoltPin = this.opts.boardDef.threeVoltPins[0] || null; let threeVoltPin = this.opts.boardDef.threeVoltPins[0] || null;
if (!threeVoltPin) { if (!threeVoltPin) {
console.log("No available 3.3V pin on board!"); console.log("No available 3.3V pin on board!");
@ -246,14 +496,14 @@ namespace pxsim {
} }
return threeVoltPin; return threeVoltPin;
} }
private allocatePowerWires(powerUsage: PowerUsage): WireInst[] { private allocPowerWires(powerUsage: PowerUsage): PartAndWiresInst {
let boardGroundPin = this.getBoardGroundPin(); let boardGroundPin = this.getBoardGroundPin();
let threeVoltPin = this.getBoardThreeVoltPin(); let threeVoltPin = this.getBoardThreeVoltPin();
let topLeft: BBRowCol = ["-", "26"]; const topLeft: BBLoc = {type: "breadboard", row: "-", col: "26"};
let botLeft: BBRowCol = ["-", "1"]; const botLeft: BBLoc = {type: "breadboard", row: "-", col: "1"};
let topRight: BBRowCol = ["-", "50"]; const topRight: BBLoc = {type: "breadboard", row: "-", col: "50"};
let botRight: BBRowCol = ["-", "25"]; const botRight: BBLoc = {type: "breadboard", row: "-", col: "25"};
let top: BBRowCol, bot: BBRowCol; let top: BBLoc, bot: BBLoc;
if (this.opts.boardDef.attachPowerOnRight) { if (this.opts.boardDef.attachPowerOnRight) {
top = topRight; top = topRight;
bot = botRight; bot = botRight;
@ -261,296 +511,162 @@ namespace pxsim {
top = topLeft; top = topLeft;
bot = botLeft; bot = botLeft;
} }
const GROUND_COLOR = "blue"; let groundWires: WireInst[] = [];
const POWER_COLOR = "red"; let threeVoltWires: WireInst[] = [];
const wires: WireInst[] = [];
let groundStep = 0;
let threeVoltStep = (powerUsage.bottomGround || powerUsage.topGround) ? 1 : 0;
if (powerUsage.bottomGround && powerUsage.topGround) { if (powerUsage.bottomGround && powerUsage.topGround) {
//bb top - <==> bb bot - //bb top - <==> bb bot -
wires.push({ groundWires.push({
start: this.allocateLocation("ground", {nearestBBPin: top}), start: this.allocLocation("ground", {referenceBBPin: top}),
end: this.allocateLocation("ground", {nearestBBPin: bot}), end: this.allocLocation("ground", {referenceBBPin: bot}),
color: GROUND_COLOR, assemblyStep: groundStep color: GROUND_COLOR,
}); });
} }
if (powerUsage.topGround) { if (powerUsage.topGround) {
//board - <==> bb top - //board - <==> bb top -
wires.push({ groundWires.push({
start: this.allocateLocation("ground", {nearestBBPin: top}), start: this.allocLocation("ground", {referenceBBPin: top}),
end: {type: "dalboard", pin: boardGroundPin}, end: {type: "dalboard", pin: boardGroundPin},
color: GROUND_COLOR, assemblyStep: groundStep color: GROUND_COLOR,
}); });
} else if (powerUsage.bottomGround) { } else if (powerUsage.bottomGround) {
//board - <==> bb bot - //board - <==> bb bot -
wires.push({ groundWires.push({
start: this.allocateLocation("ground", {nearestBBPin: bot}), start: this.allocLocation("ground", {referenceBBPin: bot}),
end: {type: "dalboard", pin: boardGroundPin}, end: {type: "dalboard", pin: boardGroundPin},
color: GROUND_COLOR, assemblyStep: groundStep color: GROUND_COLOR,
}); });
} }
if (powerUsage.bottomThreeVolt && powerUsage.bottomGround) { if (powerUsage.bottomThreeVolt && powerUsage.bottomGround) {
//bb top + <==> bb bot + //bb top + <==> bb bot +
wires.push({ threeVoltWires.push({
start: this.allocateLocation("threeVolt", {nearestBBPin: top}), start: this.allocLocation("threeVolt", {referenceBBPin: top}),
end: this.allocateLocation("threeVolt", {nearestBBPin: bot}), end: this.allocLocation("threeVolt", {referenceBBPin: bot}),
color: POWER_COLOR, assemblyStep: threeVoltStep color: POWER_COLOR,
}); });
} }
if (powerUsage.topThreeVolt) { if (powerUsage.topThreeVolt) {
//board + <==> bb top + //board + <==> bb top +
wires.push({ threeVoltWires.push({
start: this.allocateLocation("threeVolt", {nearestBBPin: top}), start: this.allocLocation("threeVolt", {referenceBBPin: top}),
end: {type: "dalboard", pin: threeVoltPin}, end: {type: "dalboard", pin: threeVoltPin},
color: POWER_COLOR, assemblyStep: threeVoltStep color: POWER_COLOR,
}); });
} else if (powerUsage.bottomThreeVolt) { } else if (powerUsage.bottomThreeVolt) {
//board + <==> bb bot + //board + <==> bb bot +
wires.push({ threeVoltWires.push({
start: this.allocateLocation("threeVolt", {nearestBBPin: bot}), start: this.allocLocation("threeVolt", {referenceBBPin: bot}),
end: {type: "dalboard", pin: threeVoltPin}, 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 { private allocWire(wireIR: WireIR): WireInst {
let ends = [wireDef.start, wireDef.end]; let ends = [wireIR.start, wireIR.end];
let endIsPower = ends.map(e => e === "ground" || e === "threeVolt"); let endIsPower = ends.map(e => e === "ground" || e === "threeVolt");
//allocate non-power first so we know the nearest pin for the power end //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 //allocate power pins closest to the other end of the wire
endInsts = endInsts.map((e, idx) => { endInsts = endInsts.map((e, idx) => {
if (e) if (e)
return e; return e;
let locInst = <BBLoc>endInsts[1 - idx]; // non-power end let locInst = <BBLoc>endInsts[1 - idx]; // non-power end
let l = this.allocateLocation(ends[idx], { let l = this.allocLocation(ends[idx], {
nearestBBPin: locInst.rowCol, referenceBBPin: locInst,
startColumn: opts.startColumn,
cmpGPIOPins: opts.cmpGPIOPins,
}); });
return l; 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[] { private allocPart(ir: PartPlacement): PartInst {
let cmpNmAndDefs = this.opts.cmpList.map(cmpName => <[string, PartDefinition]>[cmpName, this.opts.cmpDefs[cmpName]]).filter(d => !!d[1]); let bbConnections = ir.pins
let cmpNmsList = cmpNmAndDefs.map(p => p[0]); .filter(p => isConnectedToBB(p.def))
let cmpDefsList = cmpNmAndDefs.map(p => p[1]); .map(p => {
let partialCmps: PartialCmpAlloc[] = []; let rowIdx = ir.startRowIdx + p.bbFit.partRelativeRowIdx;
cmpDefsList.forEach((def, idx) => { if (rowIdx >= 7) //account for middle gap
let nm = cmpNmsList[idx]; rowIdx -= 2;
if (def.pinAllocation.type === "predefined") { let rowName = visuals.getRowName(rowIdx);
let mbPins = (<PredefinedPinAlloc>def.pinAllocation).pins; let colIdx = ir.startColumnIdx + p.bbFit.partRelativeColIdx;
let pinsAssigned = mbPins.map(p => this.opts.boardDef.gpioPinMap[p]); let colName = visuals.getColumnName(colIdx);
partialCmps.push({ return <BBLoc>{
name: nm, type: "breadboard",
def: def, row: rowName,
pinsAssigned: pinsAssigned, col: colName,
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,
});
} }
} 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 { 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({ let viewHost = new visuals.BoardHost({
state: this, state: this,
boardDef: boardDef, boardDef: boardDef,
cmpsList: cmpsList, partsList: cmpsList,
cmpDefs: cmpDefs, partDefs: cmpDefs,
fnArgs: fnArgs, fnArgs: fnArgs,
maxWidth: "100%", maxWidth: "100%",
maxHeight: "100%", maxHeight: "100%",

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -5,13 +5,13 @@ namespace pxsim.visuals {
image: partVisual.image, image: partVisual.image,
width: partVisual.width, width: partVisual.width,
height: partVisual.height, height: partVisual.height,
imageUnitDist: partVisual.pinDist, imageUnitDist: partVisual.pinDistance,
targetUnitDist: PIN_DIST targetUnitDist: PIN_DIST
}); });
return imgAndSize; return imgAndSize;
} }
export class GenericPart implements IBoardComponent<any> { export class GenericPart implements IBoardPart<any> {
public style: string = ""; public style: string = "";
public element: SVGElement; public element: SVGElement;
defs: SVGElement[] = []; defs: SVGElement[] = [];
@ -19,11 +19,6 @@ namespace pxsim.visuals {
constructor(partVisual: PartVisualDefinition) { constructor(partVisual: PartVisualDefinition) {
let imgAndSize = mkGenericPartSVG(partVisual); let imgAndSize = mkGenericPartSVG(partVisual);
let img = imgAndSize.el; 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 = svg.elt("g");
this.element.appendChild(img); this.element.appendChild(img);
} }
@ -33,7 +28,7 @@ namespace pxsim.visuals {
} }
//unused //unused
init(bus: EventBus, state: any, svgEl: SVGSVGElement, gpioPins: string[], otherArgs: string[]): void { } init(bus: EventBus, state: any, svgEl: SVGSVGElement): void { }
updateState(): void { } updateState(): void { }
updateTheme(): 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 background: SVGElement;
private ledsOuter: SVGElement[]; private ledsOuter: SVGElement[];
private leds: SVGElement[]; private leds: SVGElement[];

View File

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