diff --git a/docs/raspberry-pi.md b/docs/raspberry-pi.md new file mode 100644 index 00000000..c770056f --- /dev/null +++ b/docs/raspberry-pi.md @@ -0,0 +1,29 @@ +# Raspberry Pi and Raspbian + +It is possible to run the web editor or [command line interface](/cli) from Raspbian on Raspberry Pi 2 or 3. + +## Web editor + +The web editor requires to install IceWeasel (Firefox) as the built-in browser cannot handle it. + +``` +sudo apt-get install iceweasel +``` + +Once installed simply navigate to https://codethemicrobit.com or type + +``` +firefox https://codethemicrobit.com +``` + +## Command line + +The PXT command line also works on Raspbian and allows to run a local server and/or edit programs from any text editor. + +* Node.JS 6.0 needs installed + +To install all the tools, + +``` +curl -s https://raw.githubusercontent.com/Microsoft/pxt-rpi/master/install.sh | sh - +``` \ No newline at end of file diff --git a/libs/microbit-bluetooth/_locales/microbit-bluetooth-jsdoc-strings.json b/libs/microbit-bluetooth/_locales/microbit-bluetooth-jsdoc-strings.json index 74316739..1c243821 100644 --- a/libs/microbit-bluetooth/_locales/microbit-bluetooth-jsdoc-strings.json +++ b/libs/microbit-bluetooth/_locales/microbit-bluetooth-jsdoc-strings.json @@ -1,5 +1,4 @@ { - "bluetooth": "Support for additional Bluetooth services.", "bluetooth.onBluetoothConnected": "Register code to run when the micro:bit is connected to over Bluetooth", "bluetooth.onBluetoothConnected|param|body": "Code to run when a Bluetooth connection is established", "bluetooth.onBluetoothDisconnected": "Register code to run when a bluetooth connection to the micro:bit is lost", @@ -10,6 +9,7 @@ "bluetooth.startLEDService": "Starts the Bluetooth LED service", "bluetooth.startMagnetometerService": "Starts the Bluetooth magnetometer service", "bluetooth.startTemperatureService": "Starts the Bluetooth temperature service", + "bluetooth.startUartService": "Starts the Bluetooth UART service", "bluetooth.uartRead": "Reads from the Bluetooth UART service buffer, returning its contents when the specified delimiter character is encountered.", "bluetooth.uartWrite": "Writes to the Bluetooth UART service buffer. From there the data is transmitted over Bluetooth to a connected device." } \ No newline at end of file diff --git a/libs/microbit-bluetooth/_locales/microbit-bluetooth-strings.json b/libs/microbit-bluetooth/_locales/microbit-bluetooth-strings.json index 6c744fed..0fc2a04e 100644 --- a/libs/microbit-bluetooth/_locales/microbit-bluetooth-strings.json +++ b/libs/microbit-bluetooth/_locales/microbit-bluetooth-strings.json @@ -7,6 +7,7 @@ "bluetooth.startLEDService|block": "bluetooth led service", "bluetooth.startMagnetometerService|block": "bluetooth magnetometer service", "bluetooth.startTemperatureService|block": "bluetooth temperature service", + "bluetooth.startUartService|block": "bluetooth uart service", "bluetooth.uartRead|block": "bluetooth uart read %del=bluetooth_uart_delimiter_conv", "bluetooth.uartWrite|block": "bluetooth uart write %data", "bluetooth|block": "bluetooth" diff --git a/libs/microbit/_locales/microbit-jsdoc-strings.json b/libs/microbit/_locales/microbit-jsdoc-strings.json index 1d33c526..81ff1d58 100644 --- a/libs/microbit/_locales/microbit-jsdoc-strings.json +++ b/libs/microbit/_locales/microbit-jsdoc-strings.json @@ -22,6 +22,8 @@ "control": "Runtime and event utilities.", "control.inBackground": "Schedules code that run in the background.", "control.reset": "Resets the BBC micro:bit.", + "control.waitMicros": "Blocks the current fiber for the given microseconds", + "control.waitMicros|param|micros": "number of micro-seconds to wait. eg: 4", "game": "A single-LED sprite game engine", "game.addScore": "Adds points to the current score", "game.addScore|param|points": "amount of points to change, eg: 1", @@ -52,9 +54,12 @@ "input.onLogoDown|param|body": "TODO", "input.onLogoUp": "Attaches code to run when the logo is oriented upwards and the board is vertical.", "input.onLogoUp|param|body": "TODO", - "input.onPinPressed": "Do something when a pin(``P0``, ``P1`` or both ``P2``) is pressed.", - "input.onPinPressed|param|body": "TODO", - "input.onPinPressed|param|name": "TODO", + "input.onPinPressed": "Do something when a pin is pressed.", + "input.onPinPressed|param|body": "the code to run when the pin is pressed", + "input.onPinPressed|param|name": "the pin that needs to be pressed", + "input.onPinReleased": "Do something when a pin is released.", + "input.onPinReleased|param|body": "the code to run when the pin is released", + "input.onPinReleased|param|name": "the pin that needs to be released", "input.onScreenDown": "Attaches code to run when the screen is facing down.", "input.onScreenDown|param|body": "TODO", "input.onScreenUp": "Attaches code to run when the screen is facing up.", @@ -152,6 +157,8 @@ "pins.setPull": "Configures the pull of this pin.", "pins.setPull|param|name": "pin to set the pull mode on", "pins.setPull|param|pull": "one of the mbed pull configurations: PullUp, PullDown, PullNone ", + "pins.spiWrite": "Write to the SPI slave and return the response", + "pins.spiWrite|param|value": "Data to be sent to the SPI slave", "serial": "Reading and writing data over a serial connection.", "serial.readLine": "Reads a line of text from the serial port.", "serial.redirect": "Dynamically configuring the serial instance to use pins other than USBTX and USBRX.", diff --git a/libs/microbit/_locales/microbit-strings.json b/libs/microbit/_locales/microbit-strings.json index ae88c239..ddd6ab34 100644 --- a/libs/microbit/_locales/microbit-strings.json +++ b/libs/microbit/_locales/microbit-strings.json @@ -12,6 +12,7 @@ "basic|block": "basic", "control.inBackground|block": "run in background", "control.reset|block": "reset", + "control.waitMicros|block": "wait (µs)%micros", "control|block": "control", "game.addScore|block": "change score by|%points", "game.gameOver|block": "game over", @@ -28,7 +29,8 @@ "input.magneticForce|block": "magnetic force (µT)|%NAME", "input.onButtonPressed|block": "on button|%NAME|pressed", "input.onGesture|block": "on |%NAME", - "input.onPinPressed|block": "on pin|%NAME|pressed", + "input.onPinPressed|block": "on pin %NAME|pressed", + "input.onPinReleased|block": "on pin %NAME|released", "input.pinIsPressed|block": "pin %NAME|is pressed", "input.rotation|block": "rotation (°)|%NAME", "input.runningTime|block": "running time (ms)", @@ -41,6 +43,7 @@ "led.point|block": "point|x %x|y %y", "led.setBrightness|block": "set brightness %value", "led.stopAnimation|block": "stop animation", + "led.toggle|block": "toggle|x %x|y %y", "led.unplot|block": "unplot|x %x|y %y", "led|block": "led", "music.beat|block": "%fraction|beat", @@ -65,6 +68,7 @@ "pins.servoSetPulse|block": "servo set pulse|pin %value|to (µs) %micros", "pins.servoWritePin|block": "servo write|pin %name|to %value", "pins.setPull|block": "set pull|pin %pin|to %pull", + "pins.spiWrite|block": "spi write %value", "pins|block": "pins", "serial.readLine|block": "serial read line", "serial.redirect|block": "serial redirect to|TX %tx|RX %rx|at baud rate %rate", diff --git a/libs/microbit/core.cpp b/libs/microbit/core.cpp index 79bfc6b9..4a8969eb 100644 --- a/libs/microbit/core.cpp +++ b/libs/microbit/core.cpp @@ -166,6 +166,7 @@ namespace Array_ { int removeElement(RefCollection *c, uint32_t x) { return c->removeElement(x); } } + // Import some stuff directly namespace pxt { //% @@ -181,10 +182,12 @@ namespace pxt { //% Action mkAction(int reflen, int totallen, int startptr); //% - RefRecord* mkRecord(int reflen, int totallen); - //% RefRecord* mkClassInstance(int offset); //% + void RefRecord_destroy(RefRecord *r); + //% + void RefRecord_print(RefRecord *r); + //% void debugMemLeaks(); //% int incr(uint32_t e); @@ -308,6 +311,72 @@ namespace pxtrt { } } + //% + RefMap *mkMap() { + return new RefMap(); + } + + //% + uint32_t mapGet(RefMap *map, uint32_t key) { + int i = map->findIdx(key); + if (i < 0) { + map->unref(); + return 0; + } + uint32_t r = map->data[i].val; + map->unref(); + return r; + } + + //% + uint32_t mapGetRef(RefMap *map, uint32_t key) { + int i = map->findIdx(key); + if (i < 0) { + map->unref(); + return 0; + } + uint32_t r = incr(map->data[i].val); + map->unref(); + return r; + } + + //% + void mapSet(RefMap *map, uint32_t key, uint32_t val) { + int i = map->findIdx(key); + if (i < 0) { + map->data.push_back({ + key << 1, + val + }); + } else { + if (map->data[i].key & 1) { + decr(map->data[i].val); + map->data[i].key = key << 1; + } + map->data[i].val = val; + } + map->unref(); + } + + //% + void mapSetRef(RefMap *map, uint32_t key, uint32_t val) { + int i = map->findIdx(key); + if (i < 0) { + map->data.push_back({ + (key << 1) | 1, + val + }); + } else { + if (map->data[i].key & 1) { + decr(map->data[i].val); + } else { + map->data[i].key = (key << 1) | 1; + } + map->data[i].val = val; + } + map->unref(); + } + // // Debugger // diff --git a/libs/microbit/pxtparts.json b/libs/microbit/pxtparts.json index cf0f73f1..63cf9a5e 100644 --- a/libs/microbit/pxtparts.json +++ b/libs/microbit/pxtparts.json @@ -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]} ] } } \ No newline at end of file diff --git a/package.json b/package.json index e5b1f815..96940a41 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pxt-microbit", - "version": "0.3.78", + "version": "0.3.85", "description": "micro:bit target for PXT", "keywords": [ "JavaScript", @@ -29,6 +29,6 @@ "typescript": "^1.8.7" }, "dependencies": { - "pxt-core": "0.3.92" + "pxt-core": "0.3.97" } } diff --git a/pxtarget.json b/pxtarget.json index 8d9a9ef3..e83414f6 100644 --- a/pxtarget.json +++ b/pxtarget.json @@ -76,6 +76,7 @@ "simulator": { "autoRun": true, "aspectRatio": 1.22, + "parts": true, "partsAspectRatio": 0.69, "builtinParts": { "accelerometer": true, @@ -91,7 +92,7 @@ "yottaTarget": "bbc-microbit-classic-gcc", "yottaCorePackage": "pxt-microbit-core", "githubCorePackage": "microsoft/pxt-microbit-core", - "gittag": "v0.4.2", + "gittag": "v0.5.0", "serviceId": "ws" }, "serial": { @@ -147,4 +148,4 @@ "userVoiceApiKey": "WEkkIGaj1WtJnSUF59iwaA", "userVoiceForumId": 402381 } -} \ No newline at end of file +} diff --git a/sim/allocator.ts b/sim/allocator.ts index b7bd09b2..cb1acaaa 100644 --- a/sim/allocator.ts +++ b/sim/allocator.ts @@ -1,60 +1,81 @@ - namespace pxsim { - export interface AllocatorOpts { + const GROUND_COLOR = "blue"; + const POWER_COLOR = "red"; + + export interface AllocatorOpts { boardDef: BoardDefinition, - cmpDefs: Map, + partDefs: Map, + 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, } 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, + 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 = location[1]; + if (typeof location !== "string" && (location).type === "breadboard") { + let 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: A, b: B): A & B { + let res: any = {}; + for (let aKey in a) + res[aKey] = (a)[aKey]; + for (let bKey in b) + res[bKey] = (b)[bKey]; + return res; + } + function merge3(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 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 => ["+", `${n}`]), - ground: mkRange(26, 51).map(n => ["-", `${n}`]), + threeVolt: mkRange(26, 51).map(n => {type: "breadboard", row: "+", col: `${n}`}), + ground: mkRange(26, 51).map(n => {type: "breadboard", row: "-", col: `${n}`}), }, bottom: { - threeVolt: mkRange(1, 26).map(n => ["+", `${n}`]), - ground: mkRange(1, 26).map(n => ["-", `${n}`]), + threeVolt: mkRange(1, 26).map(n => {type: "breadboard", row: "+", col: `${n}`}), + ground: mkRange(1, 26).map(n => {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): 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 = pinDef.target; + } else { + let instIdx = (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 = 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 = {}; + 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 = location[1]; - let col = (location[2] + opts.startColumn).toString(); - return {type: "breadboard", rowCol: [row, col]} - } else if (location[0] === "GPIO") { - U.assert(!!opts.cmpGPIOPins); - let idx = location[1]; - let pin = opts.cmpGPIOPins[idx]; - return {type: "dalboard", pin: pin}; + return pin; + } else if ((location).type === "breadboard") { + return 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 = (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 = 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 = 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 = (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 = (def.pinAllocation); - let fnNm = fnPinAlloc.functionName; - let fnsAndArgs = 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 { + type: "breadboard", + row: rowName, + col: colName, } - } else if (def.pinAllocation.type === "auto") { - let pinsNeeded = (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 = 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(); } } \ No newline at end of file diff --git a/sim/dalboard.ts b/sim/dalboard.ts index b95f354b..8ab23f27 100644 --- a/sim/dalboard.ts +++ b/sim/dalboard.ts @@ -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%", diff --git a/sim/definitions.ts b/sim/definitions.ts index 154f4001..2d29b786 100644 --- a/sim/definitions.ts +++ b/sim/definitions.ts @@ -53,7 +53,7 @@ namespace pxsim { marginWhenBreadboarding: [0, 0, 80, 0], } - export const builtinComponentSimVisual: Map<() => visuals.IBoardComponent> = { + export const builtinComponentSimVisual: Map<() => visuals.IBoardPart> = { "buttonpair": () => new visuals.ButtonPairView(), "ledmatrix": () => new visuals.LedMatrixView(), "neopixel": () => new visuals.NeoPixelView(), diff --git a/sim/instructions/instructions.ts b/sim/instructions/instructions.ts index 1a91b9c3..63e60d5f 100644 --- a/sim/instructions/instructions.ts +++ b/sim/instructions/instructions.ts @@ -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; @@ -59,7 +59,7 @@ namespace pxsim.instructions { border-color: ${BORDER_COLOR}; border-style: solid; border-radius: ${BORDER_RADIUS}px; - display: block; + display: inline-block; width: ${PANEL_WIDTH}px; height: ${PANEL_HEIGHT}px; position: relative; @@ -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 = cmp; - let cnstr = builtinComponentPartVisual[builtinVis]; - el = cnstr([0, 0]); } else { let partVis = 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, 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 = {} 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((w.start).rowCol); + let lbls = board.highlightBreadboardPin((w.start)); } else { board.highlightBoardPin((w.start).pin); } if (w.end.type == "breadboard") { - let [row, col] = (w.end).rowCol; - let lbls = board.highlightBreadboardPin((w.end).rowCol); + let lbls = board.highlightBreadboardPin((w.end)); } else { board.highlightBoardPin((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 = (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] = (loc).rowCol; + let {row, col} = (loc); return `(${row},${col})` } else return (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) }); diff --git a/sim/simlib.ts b/sim/simlib.ts index 5e855eb5..1bdb3d91 100644 --- a/sim/simlib.ts +++ b/sim/simlib.ts @@ -3,10 +3,18 @@ /// 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 { + export interface IBoardPart { 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): 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 = { 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; diff --git a/sim/visuals/boardhost.ts b/sim/visuals/boardhost.ts index fdbffb50..5adf86fc 100644 --- a/sim/visuals/boardhost.ts +++ b/sim/visuals/boardhost.ts @@ -2,8 +2,8 @@ namespace pxsim.visuals { export interface BoardHostOpts { state: DalBoard, boardDef: BoardDefinition, - cmpsList?: string[], - cmpDefs: Map, + partsList?: string[], + partDefs: Map, fnArgs: any, forceBreadboard?: boolean, maxWidth?: string, @@ -11,13 +11,15 @@ namespace pxsim.visuals { wireframe?: boolean } export class BoardHost { - private components: IBoardComponent[] = []; + private parts: IBoardPart[] = []; 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 = svg.child(this.view, "g"); this.style = svg.child(this.view, "style", {}); this.defs = 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 = svg.child(this.view, "g"); + this.partOverGroup = 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 = (loc).rowCol; + let rowCol = (loc); coord = this.getBBCoord(rowCol); } else { let pinNm = (loc).pin; @@ -147,47 +153,62 @@ namespace pxsim.visuals { return coord; } - public addComponent(cmpDesc: CmpInst): IBoardComponent { - let cmp: IBoardComponent = null; + public addPart(partInst: PartInst): IBoardPart { + let part: IBoardPart = 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 = [`${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 = { + 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)); + }) } } } \ No newline at end of file diff --git a/sim/visuals/breadboard.ts b/sim/visuals/breadboard.ts index f213cf39..f9f88978 100644 --- a/sim/visuals/breadboard.ts +++ b/sim/visuals/breadboard.ts @@ -80,7 +80,8 @@ namespace pxsim.visuals { fill: #BBB; } .grayed .sim-bb-pin { - fill: #BBB; + fill:none; + stroke: #BBB; } .grayed .sim-bb-label { fill: #BBB; @@ -106,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; @@ -117,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; @@ -151,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, @@ -320,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() { @@ -370,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 ? "-" : "+"; @@ -391,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, }); @@ -414,7 +418,7 @@ namespace pxsim.visuals { mkPin: mkBBPin, mkHoverPin: mkBBHoverPin, getRowName: getBarRowName, - getColName: getColName, + getColName: getColumnName, getGroupName: getBarGroupName, colIdxsWithGap: BAR_COL_GAPS, }); @@ -432,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), }); @@ -459,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); } @@ -634,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]; diff --git a/sim/visuals/buttonpair.ts b/sim/visuals/buttonpair.ts index 9ae5e7f4..2e326d52 100644 --- a/sim/visuals/buttonpair.ts +++ b/sim/visuals/buttonpair.ts @@ -92,7 +92,7 @@ namespace pxsim.visuals { pointer-events:none; } `; - export class ButtonPairView implements IBoardComponent { + export class ButtonPairView implements IBoardPart { public element: SVGElement; public defs: SVGElement[]; public style = BUTTON_PAIR_STYLE; diff --git a/sim/visuals/genericpart.ts b/sim/visuals/genericpart.ts index e31eee72..e14599b5 100644 --- a/sim/visuals/genericpart.ts +++ b/sim/visuals/genericpart.ts @@ -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 { + export class GenericPart implements IBoardPart { 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 { } } diff --git a/sim/visuals/ledmatrix.ts b/sim/visuals/ledmatrix.ts index 971cbde8..44239a52 100644 --- a/sim/visuals/ledmatrix.ts +++ b/sim/visuals/ledmatrix.ts @@ -68,7 +68,7 @@ namespace pxsim.visuals { } ` - export class LedMatrixView implements IBoardComponent { + export class LedMatrixView implements IBoardPart { private background: SVGElement; private ledsOuter: SVGElement[]; private leds: SVGElement[]; diff --git a/sim/visuals/neopixel.ts b/sim/visuals/neopixel.ts index 6a8c45d8..86777300 100644 --- a/sim/visuals/neopixel.ts +++ b/sim/visuals/neopixel.ts @@ -102,26 +102,17 @@ namespace pxsim.visuals { }); return { el: img, x: l, y: t, w: w, h: h }; } - export class NeoPixel implements SVGAndSize { - public el: SVGCircleElement; - public w: number; - public h: number; - public x: number; - public y: number; - public cx: number; + export class NeoPixel { + public el: SVGElement; public cy: number; constructor(xy: Coord = [0, 0]) { - let circle = svg.elt("circle"); + let el = svg.elt("rect"); let r = PIXEL_RADIUS; let [cx, cy] = xy; - svg.hydrate(circle, { cx: cx, cy: cy, r: r, class: "sim-neopixel" }); - this.el = circle; - this.w = r * 2; - this.h = r * 2; - this.x = cx - r; - this.y = cy - r; - this.cx = cx; + let y = cy - r; + svg.hydrate(el, { x: "-50%", y: y, width: "100%", height: r * 2, class: "sim-neopixel" }); + this.el = el; this.cy = cy; } @@ -200,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 { @@ -222,7 +219,7 @@ namespace pxsim.visuals { return mode; } - export class NeoPixelView implements IBoardComponent { + export class NeoPixelView implements IBoardPart { public style: string = ` .sim-neopixel-canvas { } @@ -240,6 +237,7 @@ namespace pxsim.visuals { } `; public element: SVGElement; + public overElement: SVGElement; public defs: SVGElement[]; private state: NeoPixelState; private canvas: NeoPixelCanvas; @@ -249,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): 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 = 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(); } @@ -276,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. } diff --git a/sim/visuals/wiring.ts b/sim/visuals/wiring.ts index d5a0b693..b527b805 100644 --- a/sim/visuals/wiring.ts +++ b/sim/visuals/wiring.ts @@ -455,7 +455,7 @@ namespace pxsim.visuals { let wireEls: Wire; if (withCrocs && end.type == "dalboard") { let boardPin = (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 {