From 02615f5150cead1fa9ba5b03864814400e91f092 Mon Sep 17 00:00:00 2001 From: Bane Stojkovic Date: Thu, 8 Oct 2026 14:23:36 +0200 Subject: [PATCH] netlist and node name improvements --- .../org/diylc/netlist/NetlistBuilder.java | 17 ++ .../src/main/java/org/diylc/netlist/Node.java | 34 ++- .../diylc-core/src/main/resources/update.xml | 8 + .../java/org/diylc/netlist/NodeNameTest.java | 37 +++ .../diylc/components/micro/ArduinoMega.java | 4 +- .../diylc/components/micro/ArduinoNano.java | 26 +- .../diylc/components/micro/ArduinoUno.java | 12 +- .../components/micro/RaspberryPiPico.java | 8 +- .../org/diylc/components/micro/Teensy.java | 22 +- .../components/tube/Jumbo4PinTubeSocket.java | 2 +- .../diylc/components/maker/BoardRailTest.java | 152 ++++++++++ .../maker/MakerVariantSweepTest.java | 262 ++++++++++++++++++ .../components/micro/ArduinoNanoTest.java | 4 +- .../components/micro/ArduinoUnoTest.java | 4 +- .../diylc/components/micro/TeensyTest.java | 2 +- diylc/docs/plans/maker-peripherals.md | 7 +- 16 files changed, 555 insertions(+), 46 deletions(-) create mode 100644 diylc/diylc-library/src/test/java/org/diylc/components/maker/BoardRailTest.java create mode 100644 diylc/diylc-library/src/test/java/org/diylc/components/maker/MakerVariantSweepTest.java diff --git a/diylc/diylc-core/src/main/java/org/diylc/netlist/NetlistBuilder.java b/diylc/diylc-core/src/main/java/org/diylc/netlist/NetlistBuilder.java index bebc62ec..e040fd82 100644 --- a/diylc/diylc-core/src/main/java/org/diylc/netlist/NetlistBuilder.java +++ b/diylc/diylc-core/src/main/java/org/diylc/netlist/NetlistBuilder.java @@ -411,6 +411,23 @@ public class NetlistBuilder { if (((IContinuity) c).arePointsConnected(i, j)) connections.add(new Connection(c.getControlPoint(i), c.getControlPoint(j), z)); } + // Pins of one component that name the same rail are the same node: a board's eight ground + // pins land on one plane, and grounding one of them must not leave the rest floating. + // Switches are excluded because their connectivity depends on the position, which a name + // cannot express -- a switch states it through ISwitch.arePointsConnected instead. + if (!(c instanceof ISwitch)) { + for (int i = 0; i < c.getControlPointCount() - 1; i++) { + String rail = Node.railName(c.getControlPointNodeName(i)); + if (rail == null) { + continue; + } + for (int j = i + 1; j < c.getControlPointCount(); j++) { + if (rail.equals(Node.railName(c.getControlPointNodeName(j)))) { + connections.add(new Connection(c.getControlPoint(i), c.getControlPoint(j), z)); + } + } + } + } // handle switches if (ISwitch.class.isAssignableFrom(type.getInstanceClass()) && switchPositions.containsKey(c)) { diff --git a/diylc/diylc-core/src/main/java/org/diylc/netlist/Node.java b/diylc/diylc-core/src/main/java/org/diylc/netlist/Node.java index 902dfa5a..0c029670 100644 --- a/diylc/diylc-core/src/main/java/org/diylc/netlist/Node.java +++ b/diylc/diylc-core/src/main/java/org/diylc/netlist/Node.java @@ -22,7 +22,10 @@ package org.diylc.netlist; import java.awt.geom.Point2D; +import java.util.Arrays; +import java.util.HashSet; import java.util.Objects; +import java.util.Set; import org.diylc.core.ICommonNode; import org.diylc.core.IDIYComponent; @@ -34,6 +37,10 @@ import org.diylc.core.IDIYComponent; */ public class Node implements Comparable { + /** Pin names that mark an absent connection, so repeats of them are not one net. */ + private static final Set NON_NET_NAMES = + new HashSet(Arrays.asList("NC", "RSV")); + private IDIYComponent component; private int pointIndex; @@ -60,8 +67,9 @@ public class Node implements Comparable { * MCU1.D11 rather than MCU1.D11 (~, MOSI). The bare name identifies the * pin; everything in parentheses documents its alternate functions and only adds noise here. * - *

A trailing disambiguator such as the _1 in GND_1 is deliberately - * kept, because those mark genuinely different pins that a netlist has to tell apart. + *

A trailing disambiguator such as the _1 in GND_1 is kept, so the + * netlist can still say which pad a connection reached. Whether two such pins are the same + * net is a separate question, answered by {@link #railName(String)}. * * @param nodeName raw name from {@link IDIYComponent#getControlPointNodeName(int)} * @return the sanitized name, or null if the raw name was null @@ -75,6 +83,28 @@ public class Node implements Comparable { // Never let sanitizing leave a pin nameless return sanitized.isEmpty() ? nodeName.trim() : sanitized; } + + /** + * The rail a pin belongs to: its sanitized name with a trailing _1, _2 + * disambiguator removed, so GND_1 and GND_2 report the same rail. + * + *

Only a numeric suffix is dropped. An underscore does three unrelated jobs across the + * pinouts in this library: it disambiguates a repeated rail, it namespaces a connector's signals + * (ETH_TX-, USB_D+), and it qualifies a genuinely separate net + * (MISO_16U2, 3V3_EN). Cutting at the first underscore instead would + * report an Ethernet header as one rail. + * + * @param nodeName raw name from {@link IDIYComponent#getControlPointNodeName(int)} + * @return the rail name, or null if the pin has no name or names no net at all + */ + public static String railName(String nodeName) { + String sanitized = sanitizeNodeName(nodeName); + if (sanitized == null || sanitized.isEmpty()) { + return null; + } + String rail = sanitized.replaceAll("_\\d+$", ""); + return NON_NET_NAMES.contains(rail) ? null : rail; + } public Point2D getPoint2D() { return component.getControlPoint(pointIndex); diff --git a/diylc/diylc-core/src/main/resources/update.xml b/diylc/diylc-core/src/main/resources/update.xml index e572800b..95923f9a 100644 --- a/diylc/diylc-core/src/main/resources/update.xml +++ b/diylc/diylc-core/src/main/resources/update.xml @@ -565,6 +565,14 @@ IMPROVEMENT Bill of Materials now lists the selected variant of microcontroller boards and displays, so different versions of the same board are no longer merged into a single row + + BUG_FIX + 4-pin Jumbo Tube Socket - the two filament pins are now labeled F1 and F2 instead of both being labeled F, so they no longer read as the same node + + + IMPROVEMENT + Netlists now treat the repeated supply pins of a microcontroller or display board as one node, so grounding a single GND pin no longer leaves the others floating + https://github.com/bancika/diy-layout-creator/releases diff --git a/diylc/diylc-core/src/test/java/org/diylc/netlist/NodeNameTest.java b/diylc/diylc-core/src/test/java/org/diylc/netlist/NodeNameTest.java index 99c7978f..286fcc0f 100644 --- a/diylc/diylc-core/src/test/java/org/diylc/netlist/NodeNameTest.java +++ b/diylc/diylc-core/src/test/java/org/diylc/netlist/NodeNameTest.java @@ -48,4 +48,41 @@ public class NodeNameTest { Assert.assertEquals("(only annotation)", Node.sanitizeNodeName("(only annotation)")); Assert.assertNull(Node.sanitizeNodeName(null)); } + + @Test + public void railNameDropsOnlyANumericDisambiguator() { + Assert.assertEquals("GND", Node.railName("GND_1")); + Assert.assertEquals("GND", Node.railName("GND_12")); + Assert.assertEquals("GND", Node.railName("GND")); + Assert.assertEquals("GND", Node.railName("GND (Pin 6)")); + Assert.assertEquals("+5V", Node.railName("+5V_2")); + // the annotation is stripped before the disambiguator, so an annotated rail still resolves + Assert.assertEquals("3.3V", Node.railName("3.3V_2 (250mA)")); + Assert.assertEquals("GND", Node.railName("GND_4 (Mid)")); + } + + @Test + public void railNameKeepsNonNumericQualifiers() { + // namespaced connector signals + Assert.assertEquals("ETH_TX-", Node.railName("ETH_TX-")); + Assert.assertEquals("USB_D+", Node.railName("USB_D+")); + // qualifiers that name a separate net + Assert.assertEquals("MISO_16U2", Node.railName("MISO_16U2")); + Assert.assertEquals("3V3_EN", Node.railName("3V3_EN")); + // a bare trailing digit is part of the name, not a disambiguator + Assert.assertEquals("GP1", Node.railName("GP1")); + Assert.assertEquals("GND1", Node.railName("GND1")); + // digits that are not at the end are part of the name too: the 16U2 bridge's ground and 5V + // are separate nets from the board's own, and USB_5V is a connector pin, not a 5V rail + Assert.assertEquals("GND_16U2", Node.railName("GND_16U2")); + Assert.assertEquals("5V_16U2", Node.railName("5V_16U2")); + Assert.assertEquals("USB_5V", Node.railName("USB_5V")); + } + + @Test + public void railNameRejectsPinsThatNameNoNet() { + Assert.assertNull(Node.railName("NC_1")); + Assert.assertNull(Node.railName("RSV_2")); + Assert.assertNull(Node.railName(null)); + } } diff --git a/diylc/diylc-library/src/main/java/org/diylc/components/micro/ArduinoMega.java b/diylc/diylc-library/src/main/java/org/diylc/components/micro/ArduinoMega.java index 2222b8bf..0d8ceda1 100644 --- a/diylc/diylc-library/src/main/java/org/diylc/components/micro/ArduinoMega.java +++ b/diylc/diylc-library/src/main/java/org/diylc/components/micro/ArduinoMega.java @@ -64,7 +64,7 @@ public class ArduinoMega extends AbstractMakerBoard { // Pin names in sequence (98 pins total) public static final String[] PIN_NAMES = new String[] { // Power Header (0..7) - "NC", "IOREF", "RESET", "3.3V", "5V", "GND1", "GND2", "VIN", + "NC", "IOREF", "RESET", "3.3V", "5V", "GND_1", "GND_2", "VIN", // Analog Low A0..A7 (8..15) "A0", "A1", "A2", "A3", "A4", "A5", "A6", "A7", // Analog High A8..A15 (16..23) @@ -72,7 +72,7 @@ public class ArduinoMega extends AbstractMakerBoard { // Digital Low D0..D7 (24..31) "D0 (RX0)", "D1 (TX0)", "D2 (~)", "D3 (~)", "D4 (~)", "D5 (~)", "D6 (~)", "D7 (~)", // Digital High D8..D13, GND, AREF, SDA, SCL (32..41) - "D8 (~)", "D9 (~)", "D10 (~)", "D11 (~)", "D12 (~)", "D13 (~)", "GND3", "AREF", "SDA", "SCL", + "D8 (~)", "D9 (~)", "D10 (~)", "D11 (~)", "D12 (~)", "D13 (~)", "GND_3", "AREF", "SDA", "SCL", // Communication Header D14..D21 (42..49) "D14 (TX3)", "D15 (RX3)", "D16 (TX2)", "D17 (RX2)", "D18 (TX1)", "D19 (RX1)", "D20 (SDA)", "D21 (SCL)", // Double Digital 2x18 Header (50..85: D22..D53, GNDx2, 5Vx2) diff --git a/diylc/diylc-library/src/main/java/org/diylc/components/micro/ArduinoNano.java b/diylc/diylc-library/src/main/java/org/diylc/components/micro/ArduinoNano.java index 16efc7fd..848cd99b 100644 --- a/diylc/diylc-library/src/main/java/org/diylc/components/micro/ArduinoNano.java +++ b/diylc/diylc-library/src/main/java/org/diylc/components/micro/ArduinoNano.java @@ -78,9 +78,9 @@ public class ArduinoNano extends AbstractMakerBoard { public static final String[] PIN_NAMES = new String[] { // Left row (0..14, top to bottom) - "D1 (TX)", "D0 (RX)", "RST1", "GND1", "D2", "D3 (~)", "D4", "D5 (~)", "D6 (~)", "D7", "D8", "D9 (~)", "D10 (~)", "D11 (~)", "D12", + "D1 (TX)", "D0 (RX)", "RST1", "GND_1", "D2", "D3 (~)", "D4", "D5 (~)", "D6 (~)", "D7", "D8", "D9 (~)", "D10 (~)", "D11 (~)", "D12", // Right row (15..29, top to bottom) - "VIN", "GND2", "RST2", "5V", "A7", "A6", "A5", "A4", "A3", "A2", "A1", "A0", "AREF", "3.3V", "D13", + "VIN", "GND_2", "RST2", "5V", "A7", "A6", "A5", "A4", "A3", "A2", "A1", "A0", "AREF", "3.3V", "D13", // ICSP (30..35) "MISO", "5V_ICSP", "SCK", "MOSI", "RST_ICSP", "GND_ICSP" }; @@ -93,26 +93,26 @@ public class ArduinoNano extends AbstractMakerBoard { // Nano Every (ABX00028): PWM on D3, D5, D6, D9, D10 only, and D13 is SCK. public static final String[] PIN_NAMES_EVERY = new String[] { // Left row (0..14, top to bottom) - "D1 (TX)", "D0 (RX)", "RST1", "GND1", "D2", "D3 (~)", "D4", "D5 (~)", "D6 (~)", "D7", "D8", "D9 (~)", "D10 (~)", "D11 (MOSI)", "D12 (MISO)", + "D1 (TX)", "D0 (RX)", "RST1", "GND_1", "D2", "D3 (~)", "D4", "D5 (~)", "D6 (~)", "D7", "D8", "D9 (~)", "D10 (~)", "D11 (MOSI)", "D12 (MISO)", // Right row (15..29, top to bottom) - "VIN", "GND2", "RST2", "5V", "A7", "A6", "A5 (SCL)", "A4 (SDA)", "A3", "A2", "A1", "A0 (DAC0)", "AREF", "3.3V", "D13 (SCK)" + "VIN", "GND_2", "RST2", "5V", "A7", "A6", "A5 (SCL)", "A4 (SDA)", "A3", "A2", "A1", "A0 (DAC0)", "AREF", "3.3V", "D13 (SCK)" }; // Nano 33 IoT (ABX00027): the SAMD21 adds PWM on D2 relative to the Every. public static final String[] PIN_NAMES_33_IOT = new String[] { // Left row (0..14, top to bottom) - "D1 (TX)", "D0 (RX)", "RST1", "GND1", "D2 (~)", "D3 (~)", "D4", "D5 (~)", "D6 (~)", "D7", "D8", "D9 (~)", "D10 (~)", "D11 (MOSI)", "D12 (MISO)", + "D1 (TX)", "D0 (RX)", "RST1", "GND_1", "D2 (~)", "D3 (~)", "D4", "D5 (~)", "D6 (~)", "D7", "D8", "D9 (~)", "D10 (~)", "D11 (MOSI)", "D12 (MISO)", // Right row (15..29, top to bottom) - "VIN", "GND2", "RST2", "5V", "A7", "A6", "A5 (SCL)", "A4 (SDA)", "A3", "A2", "A1", "A0 (DAC0)", "AREF", "3.3V", "D13 (SCK)" + "VIN", "GND_2", "RST2", "5V", "A7", "A6", "A5 (SCL)", "A4 (SDA)", "A3", "A2", "A1", "A0 (DAC0)", "AREF", "3.3V", "D13 (SCK)" }; // Nano 33 BLE and BLE Sense (ABX00030): the nRF52840 routes PWM to every digital pin, D13 // included. public static final String[] PIN_NAMES_33_BLE = new String[] { // Left row (0..14, top to bottom) - "D1 (TX)", "D0 (RX)", "RST1", "GND1", "D2 (~)", "D3 (~)", "D4 (~)", "D5 (~)", "D6 (~)", "D7 (~)", "D8 (~)", "D9 (~)", "D10 (~)", "D11 (~, MOSI)", "D12 (~, MISO)", + "D1 (TX)", "D0 (RX)", "RST1", "GND_1", "D2 (~)", "D3 (~)", "D4 (~)", "D5 (~)", "D6 (~)", "D7 (~)", "D8 (~)", "D9 (~)", "D10 (~)", "D11 (~, MOSI)", "D12 (~, MISO)", // Right row (15..29, top to bottom) - "VIN", "GND2", "RST2", "5V", "A7", "A6", "A5 (SCL)", "A4 (SDA)", "A3", "A2", "A1", "A0 (DAC0)", "AREF", "3.3V", "D13 (~, SCK)" + "VIN", "GND_2", "RST2", "5V", "A7", "A6", "A5 (SCL)", "A4 (SDA)", "A3", "A2", "A1", "A0 (DAC0)", "AREF", "3.3V", "D13 (~, SCK)" }; // Nano RP2040 Connect (ABX00053): every RP2040 GPIO has a PWM slice. Note REC (BOOTSEL) where @@ -121,17 +121,17 @@ public class ArduinoNano extends AbstractMakerBoard { // Left row (0..14, top to bottom) "D1 (TX)", "D0 (RX)", "RST1", "GND_1", "D2 (~)", "D3 (~)", "D4 (~)", "D5 (~)", "D6 (~)", "D7 (~)", "D8 (~)", "D9 (~)", "D10 (~)", "D11 (~, MOSI)", "D12 (~, MISO)", // Right row (15..29, top to bottom) - "VIN", "GND2", "REC", "5V", "A7", "A6", "A5 (SCL)", "A4 (SDA)", "A3", "A2", "A1", "A0", "AREF", "3.3V", "D13 (~, SCK)" + "VIN", "GND_2", "REC", "5V", "A7", "A6", "A5 (SCL)", "A4 (SDA)", "A3", "A2", "A1", "A0", "AREF", "3.3V", "D13 (~, SCK)" }; // Nano R4 (ABX00142): the Renesas RA4M1 board. Same PWM set as the classic, but D4 / D5 double as // the CAN pins, A1-A3 reach the on-chip OPAMP, A0 is the DAC, and BOOT replaces the second RESET. public static final String[] PIN_NAMES_R4 = new String[] { // Left row (0..14, top to bottom) - "D1 (TX)", "D0 (RX)", "RST1", "GND1", "D2", "D3 (~)", "D4 (CAN TX)", "D5 (~, CAN RX)", "D6 (~)", + "D1 (TX)", "D0 (RX)", "RST1", "GND_1", "D2", "D3 (~)", "D4 (CAN TX)", "D5 (~, CAN RX)", "D6 (~)", "D7", "D8", "D9 (~)", "D10 (~, CS)", "D11 (~, MOSI)", "D12 (MISO)", // Right row (15..29, top to bottom) - "VIN", "GND2", "BOOT", "5V", "A7", "A6", "A5 (SCL)", "A4 (SDA)", "A3 (OPAMP OUT)", + "VIN", "GND_2", "BOOT", "5V", "A7", "A6", "A5 (SCL)", "A4 (SDA)", "A3 (OPAMP OUT)", "A2 (OPAMP -)", "A1 (OPAMP +)", "A0 (DAC)", "AREF", "3.3V", "D13 (SCK)", // Qwiic socket (30), a single point at the centre of the connector so a wire can land on it "QWIIC" @@ -142,11 +142,11 @@ public class ArduinoNano extends AbstractMakerBoard { // number, which is what the ESP-IDF documentation refers to. public static final String[] PIN_NAMES_ESP32 = new String[] { // Left row (0..14, top to bottom) - "D1 (~, TX/GPIO43)", "D0 (~, RX/GPIO44)", "RST1", "GND1", "D2 (~, GPIO5)", "D3 (~, GPIO6)", + "D1 (~, TX/GPIO43)", "D0 (~, RX/GPIO44)", "RST1", "GND_1", "D2 (~, GPIO5)", "D3 (~, GPIO6)", "D4 (~, GPIO7)", "D5 (~, GPIO8)", "D6 (~, GPIO9)", "D7 (~, GPIO10)", "D8 (~, GPIO17)", "D9 (~, GPIO18)", "D10 (~, GPIO21)", "D11 (~, MOSI/GPIO38)", "D12 (~, MISO/GPIO47)", // Right row (15..29, top to bottom) - "VIN", "GND2", "B1", "VUSB", "A7 (~, GPIO14)", "A6 (~, GPIO13)", "A5 (~, SCL/GPIO12)", + "VIN", "GND_2", "B1", "VUSB", "A7 (~, GPIO14)", "A6 (~, GPIO13)", "A5 (~, SCL/GPIO12)", "A4 (~, SDA/GPIO11)", "A3 (~, GPIO4)", "A2 (~, GPIO3)", "A1 (~, GPIO2)", "A0 (~, GPIO1)", "B0", "3.3V", "D13 (~, SCK/GPIO48)" }; diff --git a/diylc/diylc-library/src/main/java/org/diylc/components/micro/ArduinoUno.java b/diylc/diylc-library/src/main/java/org/diylc/components/micro/ArduinoUno.java index 4de743c5..74e7a1db 100644 --- a/diylc/diylc-library/src/main/java/org/diylc/components/micro/ArduinoUno.java +++ b/diylc/diylc-library/src/main/java/org/diylc/components/micro/ArduinoUno.java @@ -75,13 +75,13 @@ public class ArduinoUno extends AbstractMakerBoard { // Pin names in sequence (44 pins total) public static final String[] PIN_NAMES = new String[] { // Power Header (0..7) - "NC", "IOREF", "RESET", "3.3V", "5V", "GND1", "GND2", "VIN", + "NC", "IOREF", "RESET", "3.3V", "5V", "GND_1", "GND_2", "VIN", // Analog Header (8..13) "A0", "A1", "A2", "A3", "A4", "A5", // Digital Low (14..21) "D0 (RX)", "D1 (TX)", "D2", "D3 (~)", "D4", "D5 (~)", "D6 (~)", "D7", // Digital High (22..31) - "D8", "D9 (~)", "D10 (~)", "D11 (~)", "D12", "D13", "GND3", "AREF", "SDA", "SCL", + "D8", "D9 (~)", "D10 (~)", "D11 (~)", "D12", "D13", "GND_3", "AREF", "SDA", "SCL", // Main ICSP Header (32..37, ATmega328P) "MISO", "5V_ICSP", "SCK", "MOSI", "RST_ICSP", "GND_ICSP", // Top-Left ICSP Header (38..43, ATmega16U2) @@ -112,13 +112,13 @@ public class ArduinoUno extends AbstractMakerBoard { // ICSP header, and I2C is shared with D2 / D3 rather than living solely on A4 / A5. public static final String[] PIN_NAMES_LEONARDO = new String[] { // Power Header (0..7) - "NC", "IOREF", "RESET", "3.3V", "5V", "GND1", "GND2", "VIN", + "NC", "IOREF", "RESET", "3.3V", "5V", "GND_1", "GND_2", "VIN", // Analog Header (8..13) "A0", "A1", "A2", "A3", "A4", "A5", // Digital Low (14..21) "D0 (RX)", "D1 (TX)", "D2 (SDA)", "D3 (~, SCL)", "D4", "D5 (~)", "D6 (~)", "D7", // Digital High (22..31) - "D8", "D9 (~)", "D10 (~)", "D11 (~)", "D12", "D13", "GND3", "AREF", "SDA", "SCL", + "D8", "D9 (~)", "D10 (~)", "D11 (~)", "D12", "D13", "GND_3", "AREF", "SDA", "SCL", // Main ICSP Header (32..37, ATmega32U4) "MISO", "5V_ICSP", "SCK", "MOSI", "RST_ICSP", "GND_ICSP" }; @@ -141,13 +141,13 @@ public class ArduinoUno extends AbstractMakerBoard { // carries a 10-pin SWD debug connector in the top right corner, modelled as a single node. public static final String[] PIN_NAMES_R4_MINIMA = new String[] { // Power Header (0..7) - "BOOT", "IOREF", "RESET", "3.3V", "5V", "GND1", "GND2", "VIN", + "BOOT", "IOREF", "RESET", "3.3V", "5V", "GND_1", "GND_2", "VIN", // Analog Header (8..13) "A0", "A1", "A2", "A3", "A4", "A5", // Digital Low (14..21) "D0 (RX)", "D1 (TX)", "D2", "D3 (~)", "D4", "D5 (~)", "D6 (~)", "D7", // Digital High (22..31) - "D8", "D9 (~)", "D10 (~)", "D11 (~)", "D12", "D13", "GND3", "AREF", "SDA", "SCL", + "D8", "D9 (~)", "D10 (~)", "D11 (~)", "D12", "D13", "GND_3", "AREF", "SDA", "SCL", // Main ICSP Header (32..37, RA4M1) "MISO", "5V_ICSP", "SCK", "MOSI", "RST_ICSP", "GND_ICSP", // SWD Connector (38) diff --git a/diylc/diylc-library/src/main/java/org/diylc/components/micro/RaspberryPiPico.java b/diylc/diylc-library/src/main/java/org/diylc/components/micro/RaspberryPiPico.java index 3626f753..239671d7 100644 --- a/diylc/diylc-library/src/main/java/org/diylc/components/micro/RaspberryPiPico.java +++ b/diylc/diylc-library/src/main/java/org/diylc/components/micro/RaspberryPiPico.java @@ -89,11 +89,11 @@ public class RaspberryPiPico extends AbstractMakerBoard { public static final String[] PIN_NAMES = new String[] { // Left row (pins 0..19) - "GP0", "GP1", "GND1", "GP2", "GP3", "GP4", "GP5", "GND2", "GP6", "GP7", - "GP8", "GP9", "GND3", "GP10", "GP11", "GP12", "GP13", "GND4", "GP14", "GP15", + "GP0", "GP1", "GND_1", "GP2", "GP3", "GP4", "GP5", "GND_2", "GP6", "GP7", + "GP8", "GP9", "GND_3", "GP10", "GP11", "GP12", "GP13", "GND_4", "GP14", "GP15", // Right row (pins 20..39) - "VBUS", "VSYS", "GND5", "3V3_EN", "3V3 (OUT)", "ADC_VREF", "GP28", "GND6", "GP27", "GP26", - "RUN", "GP22", "GND7", "GP21", "GP20", "GP19", "GP18", "GND8", "GP17", "GP16", + "VBUS", "VSYS", "GND_5", "3V3_EN", "3V3 (OUT)", "ADC_VREF", "GP28", "GND_6", "GP27", "GP26", + "RUN", "GP22", "GND_7", "GP21", "GP20", "GP19", "GP18", "GND_8", "GP17", "GP16", // SWD debug pins (pins 40..42) "SWCLK", "GND_SWD", "SWDIO" }; diff --git a/diylc/diylc-library/src/main/java/org/diylc/components/micro/Teensy.java b/diylc/diylc-library/src/main/java/org/diylc/components/micro/Teensy.java index c190bd72..ef1376c0 100644 --- a/diylc/diylc-library/src/main/java/org/diylc/components/micro/Teensy.java +++ b/diylc/diylc-library/src/main/java/org/diylc/components/micro/Teensy.java @@ -89,15 +89,15 @@ public class Teensy extends AbstractMakerBoard { // VUSB (33, near VIN): VUSB public static final String[] PIN_NAMES_40 = new String[] { // Left row (0..13) - "GND", "0 (RX1/CS1/CRX2)", "1 (TX1/MISO1/CTX2)", "2 (OUT2)", "3 (LRCLK2)", "4 (BCLK2)", + "GND_1", "0 (RX1/CS1/CRX2)", "1 (TX1/MISO1/CTX2)", "2 (OUT2)", "3 (LRCLK2)", "4 (BCLK2)", "5 (IN2)", "6 (OUT1D)", "7 (RX2/OUT1A)", "8 (TX2/IN1)", "9 (OUT1C)", "10 (CS/MQSR)", "11 (MOSI/CTX1)", "12 (MISO/MQSL)", // Right row (14..27) - "VIN (3.6-5.5V)", "GND", "3.3V (250mA)", "23 (A9/CRX1)", "22 (A8/CTX1)", "21 (A7/RX5)", + "VIN (3.6-5.5V)", "GND_2", "3.3V_1 (250mA)", "23 (A9/CRX1)", "22 (A8/CTX1)", "21 (A7/RX5)", "20 (A6/TX5)", "19 (A5/SCL0)", "18 (A4/SDA0)", "17 (A3/TX4/SDA1)", "16 (A2/RX4/SCL1)", "15 (A1/RX3/SPDIF IN)", "14 (A0/TX3/SPDIF OUT)", "13 (SCK/CRX1/LED)", // End row (28..32, left to right along bottom edge) - "VBAT", "3.3V (End)", "GND (End)", "Program", "On/Off", + "VBAT", "3.3V_2 (End)", "GND_3 (End)", "Program", "On/Off", // VUSB (33) "VUSB" }; @@ -125,19 +125,19 @@ public class Teensy extends AbstractMakerBoard { // VUSB (64, near VIN): VUSB public static final String[] PIN_NAMES_41 = new String[] { // Left row (0..23) - "GND", "0 (RX1/CS1/CRX2)", "1 (TX1/MISO1/CTX2)", "2 (OUT2)", "3 (LRCLK2)", "4 (BCLK2)", + "GND_1", "0 (RX1/CS1/CRX2)", "1 (TX1/MISO1/CTX2)", "2 (OUT2)", "3 (LRCLK2)", "4 (BCLK2)", "5 (IN2)", "6 (OUT1D)", "7 (RX2/OUT1A)", "8 (TX2/IN1)", "9 (OUT1C)", "10 (CS/MQSR)", - "11 (MOSI/CTX1)", "12 (MISO/MQSL)", "3.3V", "24 (A10/TX6/SCL2)", "25 (A11/RX6/SDA2)", + "11 (MOSI/CTX1)", "12 (MISO/MQSL)", "3.3V_1", "24 (A10/TX6/SCL2)", "25 (A11/RX6/SDA2)", "26 (A12/MOSI1)", "27 (A13/SCK1)", "28 (RX7)", "29 (TX7)", "30 (CRX3)", "31 (CTX3)", "32 (OUT1B)", // Right row (24..47) - "VIN (3.6-5.5V)", "GND", "3.3V (250mA)", "23 (A9/CRX1)", "22 (A8/CTX1)", "21 (A7/RX5)", + "VIN (3.6-5.5V)", "GND_2", "3.3V_2 (250mA)", "23 (A9/CRX1)", "22 (A8/CTX1)", "21 (A7/RX5)", "20 (A6/TX5)", "19 (A5/SCL)", "18 (A4/SDA)", "17 (A3/TX4/SDA1)", "16 (A2/RX4/SCL1)", - "15 (A1/RX3/SPDIF IN)", "14 (A0/TX3/SPDIF OUT)", "13 (SCK/LED)", "GND", "41 (A17)", + "15 (A1/RX3/SPDIF IN)", "14 (A0/TX3/SPDIF OUT)", "13 (SCK/LED)", "GND_3", "41 (A17)", "40 (A16)", "39 (A15/MISO1)", "38 (A14/CS1)", "37 (CS)", "36 (CS)", "35 (TX8)", "34 (RX8)", "33 (MCLK2)", // Middle cluster (48..52, left to right near push button) - "VBAT", "3.3V (Mid)", "GND (Mid)", "Program", "On/Off", + "VBAT", "3.3V_3 (Mid)", "GND_4 (Mid)", "Program", "On/Off", // Ethernet header (53..58, 3x2) "ETH_TX-", "ETH_LED", "ETH_RX+", "ETH_TX+", "ETH_GND", "ETH_RX-", // USB Host header (59..63, 1x5) @@ -160,14 +160,14 @@ public class Teensy extends AbstractMakerBoard { // severable VUSB pad next to Vin. Labels follow the PJRC Teensy 3.2 pinout card. public static final String[] PIN_NAMES_32 = new String[] { // Left row (0..13) - "GND", "0 (RX1)", "1 (TX1)", "2", "3 (PWM)", "4 (PWM)", "5 (PWM)", "6 (PWM)", "7 (RX3)", + "GND_1", "0 (RX1)", "1 (TX1)", "2", "3 (PWM)", "4 (PWM)", "5 (PWM)", "6 (PWM)", "7 (RX3)", "8 (TX3)", "9 (RX2/PWM)", "10 (TX2/CS/PWM)", "11 (MOSI)", "12 (MISO)", // Right row (14..27) - "Vin (3.6-6.0V)", "AGND", "3.3V (100mA)", "23 (A9/PWM)", "22 (A8/PWM)", "21 (A7/PWM)", + "Vin (3.6-6.0V)", "AGND", "3.3V_1 (100mA)", "23 (A9/PWM)", "22 (A8/PWM)", "21 (A7/PWM)", "20 (A6/PWM)", "19 (A5/SCL0)", "18 (A4/SDA0)", "17 (A3)", "16 (A2)", "15 (A1/CS)", "14 (A0)", "13 (SCK/LED)", // End row (28..32, left to right along bottom edge) - "VBAT", "3.3V (End)", "GND (End)", "Program", "A14/DAC", + "VBAT", "3.3V_2 (End)", "GND_2 (End)", "Program", "A14/DAC", // VUSB (33) "VUSB" }; diff --git a/diylc/diylc-library/src/main/java/org/diylc/components/tube/Jumbo4PinTubeSocket.java b/diylc/diylc-library/src/main/java/org/diylc/components/tube/Jumbo4PinTubeSocket.java index 0cceeba0..25976fd0 100644 --- a/diylc/diylc-library/src/main/java/org/diylc/components/tube/Jumbo4PinTubeSocket.java +++ b/diylc/diylc-library/src/main/java/org/diylc/components/tube/Jumbo4PinTubeSocket.java @@ -74,7 +74,7 @@ public class Jumbo4PinTubeSocket extends AbstractAngledComponent { public static Size PIN_DIAMETER = new Size(1d, SizeUnit.mm); public static Size HOLE_SIZE = new Size(5d, SizeUnit.mm); public static Size OCTAL_TICK_SIZE = new Size(2d, SizeUnit.mm); - private static String[] electrodeLabels = new String[] {"G", "F", "P", "F"}; + private static String[] electrodeLabels = new String[] {"G", "F1", "P", "F2"}; private String type = ""; diff --git a/diylc/diylc-library/src/test/java/org/diylc/components/maker/BoardRailTest.java b/diylc/diylc-library/src/test/java/org/diylc/components/maker/BoardRailTest.java new file mode 100644 index 00000000..bb968115 --- /dev/null +++ b/diylc/diylc-library/src/test/java/org/diylc/components/maker/BoardRailTest.java @@ -0,0 +1,152 @@ +/* + * + * DIY Layout Creator (DIYLC). + * Copyright (c) 2009-2025 held jointly by the individual authors. + * + * This file is part of DIYLC. + * + * DIYLC is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DIYLC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with DIYLC. If not, see . + * + */ +package org.diylc.components.maker; + +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; + +import org.diylc.appframework.miscutils.ConfigurationManager; +import org.diylc.core.IDIYComponent; +import org.diylc.netlist.Node; +import org.diylc.components.displays.WS2812BStrip; +import org.diylc.components.micro.ArduinoUno; +import org.diylc.components.micro.ESP32DevKit; +import org.diylc.components.micro.ESP8266NodeMCU; +import org.diylc.components.micro.RaspberryPi; +import org.diylc.components.micro.RaspberryPiPico; +import org.diylc.components.micro.Teensy; + +/** + * Pins a board joins inside itself, as the netlist decides them through + * {@link Node#railName(String)}. + *

+ * The negative cases carry the weight here. An underscore does three unrelated jobs across these + * pinouts - it disambiguates a repeated rail, it namespaces a connector's signals, and it qualifies + * a separate net - so a rule keyed on the text before the underscore would short an Ethernet or USB + * header together. Each of these was produced by a sweep of every board and variant. + * + * @author Branislav Stojkovic + */ +public class BoardRailTest { + + @BeforeClass + public static void setUp() { + try { + ConfigurationManager.getInstance().initialize("diylc"); + } catch (Exception ignored) { + } + } + + @Test + public void repeatedRailsAreOneNode() throws Exception { + ESP32DevKit esp = new ESP32DevKit(); + assertJoined(esp, "GND_1", "GND_2"); + + RaspberryPi pi = new RaspberryPi(); + assertJoined(pi, "GND (Pin 6)", "GND (Pin 9)"); + assertJoined(pi, "5V (Pin 2)", "5V (Pin 4)"); + assertJoined(pi, "3.3V (Pin 1)", "3.3V (Pin 17)"); + + RaspberryPiPico pico = new RaspberryPiPico(); + assertJoined(pico, "GND_1", "GND_8"); + + WS2812BStrip strip = new WS2812BStrip(); + assertJoined(strip, "GND_1", "GND_2"); + assertJoined(strip, "+5V_1", "+5V_2"); + } + + @Test + public void annotatedRailsAreOneNode() throws Exception { + Teensy teensy = new Teensy(); + teensy.setVersion(Teensy.TeensyVersion.Teensy_4_1); + assertJoined(teensy, "GND_1", "GND_4 (Mid)"); + assertJoined(teensy, "3.3V_1", "3.3V_2 (250mA)"); + } + + @Test + public void connectorSignalsSharingAPrefixAreNotJoined() throws Exception { + Teensy teensy = new Teensy(); + teensy.setVersion(Teensy.TeensyVersion.Teensy_4_1); + assertSeparate(teensy, "ETH_TX-", "ETH_RX+"); + assertSeparate(teensy, "ETH_TX-", "ETH_GND"); + assertSeparate(teensy, "USB_D-", "USB_D+"); + assertSeparate(teensy, "USB_5V", "USB_GND1"); + } + + @Test + public void qualifiedNamesAreSeparateNets() throws Exception { + ArduinoUno uno = new ArduinoUno(); + uno.setVersion(ArduinoUno.ArduinoUnoVersion.REV3); + // the USB bridge has its own ICSP header; it is not the main MCU's SPI + assertSeparate(uno, "MISO", "MISO_16U2"); + assertSeparate(uno, "SCK", "SCK_16U2"); + + RaspberryPiPico pico = new RaspberryPiPico(); + // the regulator's enable input is not its output + assertSeparate(pico, "3V3_EN", "3V3 (OUT)"); + } + + @Test + public void absentConnectionsAreNotANet() throws Exception { + ESP32DevKit esp = new ESP32DevKit(); + esp.setVersion(ESP32DevKit.DevKitVersion.ESP32_C6_DevKitC_1); + assertSeparate(esp, "NC_1", "NC_2"); + + ESP8266NodeMCU nodeMcu = new ESP8266NodeMCU(); + assertSeparate(nodeMcu, "RSV_1", "RSV_2"); + } + + @Test + public void signalPinsAreNeverJoined() throws Exception { + RaspberryPiPico pico = new RaspberryPiPico(); + assertSeparate(pico, "GP0", "GP1"); + assertSeparate(pico, "GP0", "GP28"); + } + + private static void assertJoined(IDIYComponent board, String first, String second) { + Assert.assertTrue(board.getClass().getSimpleName() + ": \"" + first + "\" and \"" + second + + "\" should be one node", + sameRail(board, indexOf(board, first), indexOf(board, second))); + } + + private static void assertSeparate(IDIYComponent board, String first, String second) { + Assert.assertFalse(board.getClass().getSimpleName() + ": \"" + first + "\" and \"" + second + + "\" should stay separate nodes", + sameRail(board, indexOf(board, first), indexOf(board, second))); + } + + private static boolean sameRail(IDIYComponent board, int index1, int index2) { + String rail = Node.railName(board.getControlPointNodeName(index1)); + return rail != null && rail.equals(Node.railName(board.getControlPointNodeName(index2))); + } + + private static int indexOf(IDIYComponent board, String nodeName) { + for (int i = 0; i < board.getControlPointCount(); i++) { + if (nodeName.equals(board.getControlPointNodeName(i))) { + return i; + } + } + throw new IllegalArgumentException( + board.getClass().getSimpleName() + " has no pin named \"" + nodeName + "\""); + } +} diff --git a/diylc/diylc-library/src/test/java/org/diylc/components/maker/MakerVariantSweepTest.java b/diylc/diylc-library/src/test/java/org/diylc/components/maker/MakerVariantSweepTest.java new file mode 100644 index 00000000..8ff92680 --- /dev/null +++ b/diylc/diylc-library/src/test/java/org/diylc/components/maker/MakerVariantSweepTest.java @@ -0,0 +1,262 @@ +/* + * + * DIY Layout Creator (DIYLC). + * Copyright (c) 2009-2025 held jointly by the individual authors. + * + * This file is part of DIYLC. + * + * DIYLC is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DIYLC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with DIYLC. If not, see . + * + */ +package org.diylc.components.maker; + +import java.awt.Graphics2D; +import java.awt.geom.Point2D; +import java.awt.image.BufferedImage; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; + +import org.diylc.appframework.miscutils.ConfigurationManager; +import org.diylc.common.ComponentType; +import org.diylc.common.PropertyWrapper; +import org.diylc.core.ComponentState; +import org.diylc.core.IDIYComponent; +import org.diylc.core.IDrawingObserver; +import org.diylc.core.Project; +import org.diylc.presenter.ComponentProcessor; + +/** + * Sweeps every registered maker component across the cross product of its enum properties. + *

+ * The per-class tests pin down the figures each variant was built from, and + * {@link MakerComponentsTest} covers discovery, icons and rotation. Both of those exercise the + * default variant only: a component is built with its no-argument constructor and never asked for + * another version. The variants are where the geometry actually differs, and a release makes + * control-point counts and positions permanent, so this walks all of them. + *

+ * The properties are swept as a cross product rather than one at a time because a variant is + * usually a pair - a panel size and an interface, a controller and a glass - and only the + * combination decides the pin count. + * + * @author Branislav Stojkovic + */ +public class MakerVariantSweepTest { + + private static final String[] PACKAGES = + {"org.diylc.components.displays", "org.diylc.components.micro"}; + + /** + * Orientation is swept inside the drawing check instead of joining the cross product: it moves + * the control points but cannot change how many there are or what they are called, so folding it + * in would only report the same defect once per rotation. + */ + private static final String ORIENTATION = "Orientation"; + + private static final Project PROJECT = new Project(); + + private static final IDrawingObserver OBSERVER = new IDrawingObserver() { + + @Override + public void stopTracking() {} + + @Override + public void startTracking() {} + + @Override + public void startTrackingContinuityArea(boolean positive) {} + + @Override + public void stopTrackingContinuityArea() {} + + @Override + public boolean isTrackingContinuityArea() { + return false; + } + + @Override + public void setContinuityMarker(String marker) {} + }; + + @BeforeClass + public static void setUp() { + try { + ConfigurationManager.getInstance().initialize("diylc"); + } catch (Exception ignored) { + } + } + + /** + * Problems are collected rather than asserted one at a time, so a run reports every bad variant + * at once instead of stopping at the first. + */ + @Test + public void everyVariantHasSoundControlPointsAndDraws() throws Exception { + List problems = new ArrayList(); + int swept = 0; + + for (Class> clazz : makerComponentClasses()) { + List enums = new ArrayList(); + PropertyWrapper orientation = null; + for (PropertyWrapper property : ComponentProcessor.getInstance().extractProperties(clazz)) { + if (!property.getType().isEnum()) { + continue; + } + if (ORIENTATION.equals(property.getName())) { + orientation = property; + } else { + enums.add(property); + } + } + + int combinations = 1; + for (PropertyWrapper property : enums) { + combinations *= property.getType().getEnumConstants().length; + } + + for (int index = 0; index < combinations; index++) { + IDIYComponent component = clazz.getDeclaredConstructor().newInstance(); + StringBuilder where = new StringBuilder(clazz.getSimpleName()); + int remaining = index; + boolean complete = true; + + for (PropertyWrapper property : enums) { + Object[] values = property.getType().getEnumConstants(); + Object value = values[remaining % values.length]; + remaining /= values.length; + where.append(" / ").append(property.getName()).append("=").append(value); + + PropertyWrapper applied = (PropertyWrapper) property.clone(); + applied.setValue(value); + try { + applied.writeTo(component); + } catch (Exception e) { + problems.add(where + ": will not apply -- " + e.getClass().getSimpleName()); + complete = false; + break; + } + } + + if (!complete) { + continue; + } + swept++; + + checkNodeNamesAreUnique(component, where.toString(), problems); + checkControlPointsAreDistinct(component, where.toString(), problems); + checkDrawsInEveryOrientation(component, orientation, where.toString(), problems); + } + } + + Assert.assertTrue("No maker components were discovered", swept > 0); + Assert.assertTrue(swept + " variants swept, " + problems.size() + " problem(s):\n" + + String.join("\n", problems), problems.isEmpty()); + } + + private static void checkNodeNamesAreUnique(IDIYComponent component, String where, + List problems) { + Set seen = new HashSet(); + for (int i = 0; i < component.getControlPointCount(); i++) { + String name = component.getControlPointNodeName(i); + // A null name is a point that carries no node, which is not a collision. + if (name != null && !seen.add(name)) { + problems.add(where + ": duplicate node name \"" + name + "\" at point " + i); + } + } + } + + private static void checkControlPointsAreDistinct(IDIYComponent component, String where, + List problems) { + int count = component.getControlPointCount(); + for (int i = 0; i < count; i++) { + Point2D first = component.getControlPoint(i); + for (int j = i + 1; j < count; j++) { + if (first.distance(component.getControlPoint(j)) <= 0.001) { + problems.add(where + ": control points " + i + " and " + j + " coincide"); + } + } + } + } + + private static void checkDrawsInEveryOrientation(IDIYComponent component, + PropertyWrapper orientation, String where, List problems) + throws CloneNotSupportedException { + Object[] orientations = + orientation == null ? new Object[] {null} : orientation.getType().getEnumConstants(); + + for (Object value : orientations) { + String at = where; + if (value != null) { + at = where + " / " + orientation.getName() + "=" + value; + PropertyWrapper applied = (PropertyWrapper) orientation.clone(); + applied.setValue(value); + try { + applied.writeTo(component); + } catch (Exception e) { + problems.add(at + ": will not apply -- " + e.getClass().getSimpleName()); + continue; + } + } + + BufferedImage image = new BufferedImage(600, 600, BufferedImage.TYPE_INT_ARGB); + Graphics2D g2d = image.createGraphics(); + // Translating the canvas rather than moving control point 0 keeps the component's own + // geometry intact: a drag sets every control point, so moving only the first leaves the rest + // stale and the component draws from a mix of old and new positions. + g2d.translate(300, 300); + try { + component.draw(g2d, ComponentState.NORMAL, false, PROJECT, OBSERVER); + component.draw(g2d, ComponentState.SELECTED, false, PROJECT, OBSERVER); + component.draw(g2d, ComponentState.NORMAL, true, PROJECT, OBSERVER); + component.drawIcon(g2d, 32, 32); + } catch (Exception e) { + problems.add(at + ": draw threw " + e.getClass().getSimpleName() + " -- " + e.getMessage()); + } finally { + g2d.dispose(); + } + } + } + + private static List>> makerComponentClasses() { + List>> classes = + new ArrayList>>(); + for (List category : ComponentProcessor.getInstance().getComponentTypes() + .values()) { + for (ComponentType type : category) { + String name = type.getInstanceClass().getName(); + for (String prefix : PACKAGES) { + if (name.startsWith(prefix + ".")) { + classes.add(type.getInstanceClass()); + } + } + } + } + // Discovery order is not stable, and a failure message naming variants reads better sorted. + Collections.sort(classes, new Comparator>>() { + + @Override + public int compare(Class> a, Class> b) { + return a.getName().compareTo(b.getName()); + } + }); + return classes; + } +} diff --git a/diylc/diylc-library/src/test/java/org/diylc/components/micro/ArduinoNanoTest.java b/diylc/diylc-library/src/test/java/org/diylc/components/micro/ArduinoNanoTest.java index 4a7e678d..5ef30900 100644 --- a/diylc/diylc-library/src/test/java/org/diylc/components/micro/ArduinoNanoTest.java +++ b/diylc/diylc-library/src/test/java/org/diylc/components/micro/ArduinoNanoTest.java @@ -47,8 +47,8 @@ public class ArduinoNanoTest { Assert.assertEquals("MISO", classic.getControlPointNodeName(30)); // the silkscreen prints what is on the board: both grounds and both resets are printed alike - Assert.assertEquals("GND1", classic.getControlPointNodeName(3)); - Assert.assertEquals("GND2", classic.getControlPointNodeName(16)); + Assert.assertEquals("GND_1", classic.getControlPointNodeName(3)); + Assert.assertEquals("GND_2", classic.getControlPointNodeName(16)); Assert.assertEquals("GND", classic.getSilkPinLabel(3)); Assert.assertEquals("GND", classic.getSilkPinLabel(16)); Assert.assertEquals("RST", classic.getSilkPinLabel(2)); diff --git a/diylc/diylc-library/src/test/java/org/diylc/components/micro/ArduinoUnoTest.java b/diylc/diylc-library/src/test/java/org/diylc/components/micro/ArduinoUnoTest.java index 506df47d..4dbde070 100644 --- a/diylc/diylc-library/src/test/java/org/diylc/components/micro/ArduinoUnoTest.java +++ b/diylc/diylc-library/src/test/java/org/diylc/components/micro/ArduinoUnoTest.java @@ -55,8 +55,8 @@ public class ArduinoUnoTest { // the silkscreen prints what is on the board rather than the node name: both grounds of the // power header say "GND" and the reserved first pin says nothing at all - Assert.assertEquals("GND1", r3.getControlPointNodeName(5)); - Assert.assertEquals("GND2", r3.getControlPointNodeName(6)); + Assert.assertEquals("GND_1", r3.getControlPointNodeName(5)); + Assert.assertEquals("GND_2", r3.getControlPointNodeName(6)); Assert.assertEquals("GND", r3.getSilkPinLabel(5)); Assert.assertEquals("GND", r3.getSilkPinLabel(6)); Assert.assertEquals("", r3.getSilkPinLabel(0)); diff --git a/diylc/diylc-library/src/test/java/org/diylc/components/micro/TeensyTest.java b/diylc/diylc-library/src/test/java/org/diylc/components/micro/TeensyTest.java index 61698fd3..61a87abf 100644 --- a/diylc/diylc-library/src/test/java/org/diylc/components/micro/TeensyTest.java +++ b/diylc/diylc-library/src/test/java/org/diylc/components/micro/TeensyTest.java @@ -36,7 +36,7 @@ public class TeensyTest { Teensy teensy40 = new Teensy(); Assert.assertEquals(TeensyVersion.Teensy_4_0, teensy40.getVersion()); Assert.assertEquals(34, teensy40.getControlPointCount()); - Assert.assertEquals("GND", teensy40.getControlPointNodeName(0)); + Assert.assertEquals("GND_1", teensy40.getControlPointNodeName(0)); Assert.assertEquals("0 (RX1/CS1/CRX2)", teensy40.getControlPointNodeName(1)); Assert.assertEquals("12 (MISO/MQSL)", teensy40.getControlPointNodeName(13)); Assert.assertEquals("VIN (3.6-5.5V)", teensy40.getControlPointNodeName(14)); diff --git a/diylc/docs/plans/maker-peripherals.md b/diylc/docs/plans/maker-peripherals.md index 490b8059..28b43f0e 100644 --- a/diylc/docs/plans/maker-peripherals.md +++ b/diylc/docs/plans/maker-peripherals.md @@ -127,8 +127,11 @@ What these tests are for, beyond the obvious count-and-dimension checks: clears the mounting holes; no hole is drilled where a pad is; a lit area that is measured really does disagree with the rule it replaced. A test that restates the constant passes while the component is wrong. -- **No duplicate node names,** on every variant. Duplicates reach the netlist as one node and are - invisible on the canvas. +- **No duplicate node names,** on every variant. The cost is legibility, not connectivity: nodes are + identified by component and control point index and the netlist graph is keyed on position, so two + pads named `GND` stay two nodes that merely print the same, and a reader cannot tell which one a + connection reached. They are invisible on the canvas too, since `getDisplayPinLabel` cuts the + suffix off and both silk as `GND`. `Teensy` is the one board that still repeats a bare name. - **A drawing smoke test** over every variant, state and orientation. - When a refactor is meant to change nothing, **render before and after and compare pixels.** The `digitCount` work was verified that way, and it caught a sub-pixel hole shift the unit tests