This commit is contained in:
Bane Stojkovic
2026-10-03 23:04:30 +02:00
parent 8693215c9c
commit 3cc61a4506
17 changed files with 546 additions and 534 deletions
@@ -78,6 +78,10 @@ public class MyPart extends AbstractLeadedComponent<Resistance> {
@Override
public void drawIcon(Graphics2D g2d, int width, int height) { ... }
public enum Version { // nested types last, just before the closing brace
...
}
}
```
@@ -90,6 +94,9 @@ Requirements the framework enforces:
depend on neighbouring components.
- Appearance constants are `public static` and deliberately **not final**, so they can be overridden
at runtime. Follow that.
- Nested types — a `Version` enum, a package or size enum, a small helper class — go at the **bottom
of the file**, after every method, so the outer class reads top to bottom without a block of enum
declarations in the middle of it.
## Editable properties
+5
View File
@@ -74,6 +74,11 @@ The formatter is `eclipse-java-google-style.xml` (Google Java Style):
runtime; keep that convention when adding new ones.
- `private static final Logger LOG = Logger.getLogger(TheClass.class);` — log4j 1.2, declared first
among the static members. Only declare one on a class that actually needs it; see *Logging* below.
- **Nested types go at the bottom of the file**, after every field, constructor and method of the
enclosing class, immediately before its closing brace. A version enum or a small helper class
declared halfway down the file pushes the code a reader came for out of sight; keeping them last
means the outer class reads top to bottom without interruption. Several nested types keep their
relative order.
### Comments
@@ -59,51 +59,6 @@ public class CharacterLCD extends AbstractMakerBoard {
private static final long serialVersionUID = 1L;
public enum LCDSize {
// Labels stay short because they reach the BOM's value column; the dimensions are carried by
// the fields beside them, not by the text. The bezel and the lit area are measured off the
// module and are sizes in their own right -- deriving them as margins off the board made the
// window grow with the board instead of staying the size of the part.
_16x2("16x2", 80.0, 35.0, 72.2, 24.1, 64.5, 14.5),
_20x4("20x4", 98.0, 60.0, 96.8, 39.3, 77.0, 25.2);
private final String label;
private final double widthMm;
private final double heightMm;
private final double bezelWidthMm;
private final double bezelHeightMm;
private final double displayWidthMm;
private final double displayHeightMm;
LCDSize(String label, double widthMm, double heightMm, double bezelWidthMm,
double bezelHeightMm, double displayWidthMm, double displayHeightMm) {
this.label = label;
this.widthMm = widthMm;
this.heightMm = heightMm;
this.bezelWidthMm = bezelWidthMm;
this.bezelHeightMm = bezelHeightMm;
this.displayWidthMm = displayWidthMm;
this.displayHeightMm = displayHeightMm;
}
@Override public String toString() { return label; }
public double getWidthMm() { return widthMm; }
public double getHeightMm() { return heightMm; }
public double getBezelWidthMm() { return bezelWidthMm; }
public double getBezelHeightMm() { return bezelHeightMm; }
public double getDisplayWidthMm() { return displayWidthMm; }
public double getDisplayHeightMm() { return displayHeightMm; }
}
public enum LCDInterface {
I2C_Backpack("I2C Backpack"),
Parallel_16Pin("Parallel HD44780");
private final String label;
LCDInterface(String label) { this.label = label; }
@Override public String toString() { return label; }
}
// clearance from the top edge to the parallel pin row
public static Size HEADER_OFFSET = new Size(2.54d, SizeUnit.mm);
// The parallel row is not centred on the board: its leftmost pin starts this far in from the
@@ -367,4 +322,49 @@ public class CharacterLCD extends AbstractMakerBoard {
g2d.setFont(new Font("SansSerif", Font.BOLD, 6));
StringUtils.drawCenteredText(g2d, "LCD", width / 2, height / 2 + 1, HorizontalAlignment.CENTER, VerticalAlignment.CENTER);
}
public enum LCDSize {
// Labels stay short because they reach the BOM's value column; the dimensions are carried by
// the fields beside them, not by the text. The bezel and the lit area are measured off the
// module and are sizes in their own right -- deriving them as margins off the board made the
// window grow with the board instead of staying the size of the part.
_16x2("16x2", 80.0, 35.0, 72.2, 24.1, 64.5, 14.5),
_20x4("20x4", 98.0, 60.0, 96.8, 39.3, 77.0, 25.2);
private final String label;
private final double widthMm;
private final double heightMm;
private final double bezelWidthMm;
private final double bezelHeightMm;
private final double displayWidthMm;
private final double displayHeightMm;
LCDSize(String label, double widthMm, double heightMm, double bezelWidthMm,
double bezelHeightMm, double displayWidthMm, double displayHeightMm) {
this.label = label;
this.widthMm = widthMm;
this.heightMm = heightMm;
this.bezelWidthMm = bezelWidthMm;
this.bezelHeightMm = bezelHeightMm;
this.displayWidthMm = displayWidthMm;
this.displayHeightMm = displayHeightMm;
}
@Override public String toString() { return label; }
public double getWidthMm() { return widthMm; }
public double getHeightMm() { return heightMm; }
public double getBezelWidthMm() { return bezelWidthMm; }
public double getBezelHeightMm() { return bezelHeightMm; }
public double getDisplayWidthMm() { return displayWidthMm; }
public double getDisplayHeightMm() { return displayHeightMm; }
}
public enum LCDInterface {
I2C_Backpack("I2C Backpack"),
Parallel_16Pin("Parallel HD44780");
private final String label;
LCDInterface(String label) { this.label = label; }
@Override public String toString() { return label; }
}
}
@@ -103,31 +103,6 @@ public class LEDBarGraph extends AbstractLabeledComponent<String> {
public static Size SEGMENT_WIDTH = new Size(4.8d, SizeUnit.mm);
public static Size SEGMENT_LENGTH = new Size(1.7d, SizeUnit.mm);
/**
* How many segments the bar carries, which decides everything else about the package: each
* segment has its own anode and cathode, so the pin count is twice the segment count and the
* body lengthens by one pin pitch per segment.
*/
public enum Segments {
_8("8 Segment", 8),
_10("10 Segment", 10),
_12("12 Segment", 12);
private final String label;
private final int count;
Segments(String label, int count) {
this.label = label;
this.count = count;
}
@Override public String toString() { return label; }
public int getCount() { return count; }
/** Anode and cathode per segment, split evenly between the two rows. */
public int getPinCount() { return count * 2; }
}
private String value = "";
private Orientation orientation = Orientation.DEFAULT;
private Segments segments = Segments._10;
@@ -521,4 +496,29 @@ public class LEDBarGraph extends AbstractLabeledComponent<String> {
return new Rectangle2D.Double(minX - margin, minY - margin, maxX - minX + 2 * margin,
maxY - minY + 2 * margin);
}
/**
* How many segments the bar carries, which decides everything else about the package: each
* segment has its own anode and cathode, so the pin count is twice the segment count and the
* body lengthens by one pin pitch per segment.
*/
public enum Segments {
_8("8 Segment", 8),
_10("10 Segment", 10),
_12("12 Segment", 12);
private final String label;
private final int count;
Segments(String label, int count) {
this.label = label;
this.count = count;
}
@Override public String toString() { return label; }
public int getCount() { return count; }
/** Anode and cathode per segment, split evenly between the two rows. */
public int getPinCount() { return count * 2; }
}
}
@@ -61,43 +61,6 @@ public class LEDMatrix extends AbstractMakerBoard {
public static Color LED_RED = Color.decode("#E53935");
public static Color LED_OFF = Color.decode("#333333");
/**
* Which board the modules sit on, which is not the same question as how many there are: two of
* these carry one module each. The original single puts its driver and both headers on a strip
* below the module, so the board is taller than it is wide. The compact single fits the driver
* under the module instead, leaving a board that is only the module, which is what lets several
* be butted together; the 4-in-1 is that same idea sold as a single PCB. Both of those hide
* every driver and bring their headers out on the short edges, standing the pin rows on end.
*/
public enum Modules {
Single_8x8("Single 8x8 (32x50mm)", 1, 32.0d, 50.0d, false),
Compact_8x8("Compact 8x8 (32x32mm)", 1, 32.0d, 32.0d, true),
FourInOne_32x8("4-in-1 32x8 (128x32mm)", 4, 128.0d, 32.0d, true);
private final String label;
private final int count;
private final double boardWidthMm;
private final double boardHeightMm;
private final boolean verticalHeaders;
Modules(String label, int count, double boardWidthMm, double boardHeightMm,
boolean verticalHeaders) {
this.label = label;
this.count = count;
this.boardWidthMm = boardWidthMm;
this.boardHeightMm = boardHeightMm;
this.verticalHeaders = verticalHeaders;
}
@Override public String toString() { return label; }
public int getCount() { return count; }
public double getBoardWidthMm() { return boardWidthMm; }
public double getBoardHeightMm() { return boardHeightMm; }
/** True where the headers leave by the short edges, so their rows run down rather than across. */
public boolean hasVerticalHeaders() { return verticalHeaders; }
}
// The modules are flush with each other and with the board, so the outline bounds them rather
// than their being inset from it, and the dot pitch runs unbroken across the whole strip.
public static Size MATRIX_SIZE = new Size(32.0d, SizeUnit.mm);
@@ -386,4 +349,41 @@ public class LEDMatrix extends AbstractMakerBoard {
}
}
}
/**
* Which board the modules sit on, which is not the same question as how many there are: two of
* these carry one module each. The original single puts its driver and both headers on a strip
* below the module, so the board is taller than it is wide. The compact single fits the driver
* under the module instead, leaving a board that is only the module, which is what lets several
* be butted together; the 4-in-1 is that same idea sold as a single PCB. Both of those hide
* every driver and bring their headers out on the short edges, standing the pin rows on end.
*/
public enum Modules {
Single_8x8("Single 8x8 (32x50mm)", 1, 32.0d, 50.0d, false),
Compact_8x8("Compact 8x8 (32x32mm)", 1, 32.0d, 32.0d, true),
FourInOne_32x8("4-in-1 32x8 (128x32mm)", 4, 128.0d, 32.0d, true);
private final String label;
private final int count;
private final double boardWidthMm;
private final double boardHeightMm;
private final boolean verticalHeaders;
Modules(String label, int count, double boardWidthMm, double boardHeightMm,
boolean verticalHeaders) {
this.label = label;
this.count = count;
this.boardWidthMm = boardWidthMm;
this.boardHeightMm = boardHeightMm;
this.verticalHeaders = verticalHeaders;
}
@Override public String toString() { return label; }
public int getCount() { return count; }
public double getBoardWidthMm() { return boardWidthMm; }
public double getBoardHeightMm() { return boardHeightMm; }
/** True where the headers leave by the short edges, so their rows run down rather than across. */
public boolean hasVerticalHeaders() { return verticalHeaders; }
}
}
@@ -59,17 +59,6 @@ public class OLEDDisplay extends AbstractMakerBoard {
private static final long serialVersionUID = 1L;
public enum OLEDInterface {
// Labels stay short because they reach the BOM's value column through getVariantLabel; the pin
// legend they used to carry is already in the node names.
I2C_4Pin("I2C"),
SPI_7Pin("SPI");
private final String label;
OLEDInterface(String label) { this.label = label; }
@Override public String toString() { return label; }
}
public static Color OLED_BLUE = Color.decode("#004488");
public static Color GLASS_COLOR = Color.decode("#0D1B2A");
public static Color GLASS_BORDER_COLOR = Color.decode("#334E68");
@@ -258,4 +247,15 @@ public class OLEDDisplay extends AbstractMakerBoard {
g2d.fillRect(10 + i * 3, 5, 2, 2);
}
}
public enum OLEDInterface {
// Labels stay short because they reach the BOM's value column through getVariantLabel; the pin
// legend they used to carry is already in the node names.
I2C_4Pin("I2C"),
SPI_7Pin("SPI");
private final String label;
OLEDInterface(String label) { this.label = label; }
@Override public String toString() { return label; }
}
}
@@ -60,98 +60,6 @@ public class SevenSegmentDisplay extends AbstractMakerBoard {
private static final long serialVersionUID = 1L;
/**
* The packages this component can draw, each carrying its own dimensions in millimetres.
*
* <p>Provenance differs between them and is worth keeping straight. The digit heights are
* definitional, since 0.36 and 0.56 inches are what the parts are named for. The 0.56 inch body,
* digit width and pitch are the figures this class has always carried and have never been checked
* against a datasheet. The 0.36 inch equivalents are **placeholders**, scaled from the digit
* height, and are not measurements at all. See the plan's section 11.10; none of this is settled
* until someone reads a datasheet or measures a part.
*/
public enum DisplayType {
// The parts these model: 5161AS for the single digit, 3641AS for the 0.36 inch four-digit
// package and 5641AS for the 0.56 inch one. The two four-digit parts differ in size but share
// a pinout, which is why one array serves both.
SingleDigit_10Pin("1-Digit 0.56\"", 12.7d, 19.0d, 8.1d, 14.2d, 0d, 15.24d, true),
FourDigit_0_36_12Pin("4-Digit 0.36\"", 30.0d, 14.0d, 5.2d, 9.14d, 7.5d, 10.16d, true),
FourDigit_0_56_12Pin("4-Digit 0.56\"", 50.3d, 19.0d, 8.1d, 14.2d, 12.7d, 15.24d, true),
TM1637_Module_4Pin("4-Digit TM1637 Module", 42.0d, 24.0d, 5.5d, 9.2d, 7.62d, 0d, false);
private final String label;
private final double bodyWidthMm;
private final double bodyLengthMm;
private final double digitWidthMm;
private final double digitHeightMm;
private final double digitPitchMm;
private final double rowSpacingMm;
private final boolean dualRow;
DisplayType(String label, double bodyWidthMm, double bodyLengthMm, double digitWidthMm,
double digitHeightMm, double digitPitchMm, double rowSpacingMm, boolean dualRow) {
this.label = label;
this.bodyWidthMm = bodyWidthMm;
this.bodyLengthMm = bodyLengthMm;
this.digitWidthMm = digitWidthMm;
this.digitHeightMm = digitHeightMm;
this.digitPitchMm = digitPitchMm;
this.rowSpacingMm = rowSpacingMm;
this.dualRow = dualRow;
}
/**
* Distance between the two pin rows. It cannot be one shared constant: a package only works if
* its rows sit within its body, and the 0.36 inch part is shorter than the 0.6 inch spacing the
* larger packages use.
*/
public double getRowSpacingMm() { return rowSpacingMm; }
@Override public String toString() { return label; }
public double getBodyWidthMm() { return bodyWidthMm; }
public double getBodyLengthMm() { return bodyLengthMm; }
public double getDigitWidthMm() { return digitWidthMm; }
public double getDigitHeightMm() { return digitHeightMm; }
public double getDigitPitchMm() { return digitPitchMm; }
/** True for the DIP packages, which carry their pins in two rows rather than one. */
public boolean isDualRow() { return dualRow; }
}
/**
* Which electrode the digits share. Both kinds are sold side by side and are identical in pinout,
* package and dimensions -- they differ only in internal polarity, so this changes nothing that
* is drawn and reaches only the BOM.
*/
public enum Common {
Cathode("Common Cathode"),
Anode("Common Anode");
private final String label;
Common(String label) { this.label = label; }
@Override public String toString() { return label; }
}
/**
* What the package carries besides the digits themselves. These are sold in every combination,
* and on a twelve-pin part they are not independent of each other: A-G plus DP and four digit
* commons already account for all twelve pins, so a colon has to share the decimal point's line
* or replace it. The property therefore describes the face of the part, not a fifth pin.
*/
public enum Punctuation {
None("No Punctuation"),
DecimalPoints("Decimal Points"),
Colon("Colon"),
Both("Colon + Decimal Points");
private final String label;
Punctuation(String label) { this.label = label; }
@Override public String toString() { return label; }
public boolean hasDecimalPoints() { return this == DecimalPoints || this == Both; }
public boolean hasColon() { return this == Colon || this == Both; }
}
public static Color BODY_BLACK = Color.decode("#1C1C1C");
public static Color FACE_BLACK = Color.decode("#111111");
public static Color FACE_BORDER = Color.decode("#333333");
@@ -675,4 +583,96 @@ public class SevenSegmentDisplay extends AbstractMakerBoard {
// one configured instance, so it keeps its decimal point whatever Punctuation is set to.
drawSevenSegmentDigit(g2d, 9, 5, width - 18, height - 10, "8.", LED_RED, true);
}
/**
* The packages this component can draw, each carrying its own dimensions in millimetres.
*
* <p>Provenance differs between them and is worth keeping straight. The digit heights are
* definitional, since 0.36 and 0.56 inches are what the parts are named for. The 0.56 inch body,
* digit width and pitch are the figures this class has always carried and have never been checked
* against a datasheet. The 0.36 inch equivalents are **placeholders**, scaled from the digit
* height, and are not measurements at all. See the plan's section 11.10; none of this is settled
* until someone reads a datasheet or measures a part.
*/
public enum DisplayType {
// The parts these model: 5161AS for the single digit, 3641AS for the 0.36 inch four-digit
// package and 5641AS for the 0.56 inch one. The two four-digit parts differ in size but share
// a pinout, which is why one array serves both.
SingleDigit_10Pin("1-Digit 0.56\"", 12.7d, 19.0d, 8.1d, 14.2d, 0d, 15.24d, true),
FourDigit_0_36_12Pin("4-Digit 0.36\"", 30.0d, 14.0d, 5.2d, 9.14d, 7.5d, 10.16d, true),
FourDigit_0_56_12Pin("4-Digit 0.56\"", 50.3d, 19.0d, 8.1d, 14.2d, 12.7d, 15.24d, true),
TM1637_Module_4Pin("4-Digit TM1637 Module", 42.0d, 24.0d, 5.5d, 9.2d, 7.62d, 0d, false);
private final String label;
private final double bodyWidthMm;
private final double bodyLengthMm;
private final double digitWidthMm;
private final double digitHeightMm;
private final double digitPitchMm;
private final double rowSpacingMm;
private final boolean dualRow;
DisplayType(String label, double bodyWidthMm, double bodyLengthMm, double digitWidthMm,
double digitHeightMm, double digitPitchMm, double rowSpacingMm, boolean dualRow) {
this.label = label;
this.bodyWidthMm = bodyWidthMm;
this.bodyLengthMm = bodyLengthMm;
this.digitWidthMm = digitWidthMm;
this.digitHeightMm = digitHeightMm;
this.digitPitchMm = digitPitchMm;
this.rowSpacingMm = rowSpacingMm;
this.dualRow = dualRow;
}
/**
* Distance between the two pin rows. It cannot be one shared constant: a package only works if
* its rows sit within its body, and the 0.36 inch part is shorter than the 0.6 inch spacing the
* larger packages use.
*/
public double getRowSpacingMm() { return rowSpacingMm; }
@Override public String toString() { return label; }
public double getBodyWidthMm() { return bodyWidthMm; }
public double getBodyLengthMm() { return bodyLengthMm; }
public double getDigitWidthMm() { return digitWidthMm; }
public double getDigitHeightMm() { return digitHeightMm; }
public double getDigitPitchMm() { return digitPitchMm; }
/** True for the DIP packages, which carry their pins in two rows rather than one. */
public boolean isDualRow() { return dualRow; }
}
/**
* Which electrode the digits share. Both kinds are sold side by side and are identical in pinout,
* package and dimensions -- they differ only in internal polarity, so this changes nothing that
* is drawn and reaches only the BOM.
*/
public enum Common {
Cathode("Common Cathode"),
Anode("Common Anode");
private final String label;
Common(String label) { this.label = label; }
@Override public String toString() { return label; }
}
/**
* What the package carries besides the digits themselves. These are sold in every combination,
* and on a twelve-pin part they are not independent of each other: A-G plus DP and four digit
* commons already account for all twelve pins, so a colon has to share the decimal point's line
* or replace it. The property therefore describes the face of the part, not a fifth pin.
*/
public enum Punctuation {
None("No Punctuation"),
DecimalPoints("Decimal Points"),
Colon("Colon"),
Both("Colon + Decimal Points");
private final String label;
Punctuation(String label) { this.label = label; }
@Override public String toString() { return label; }
public boolean hasDecimalPoints() { return this == DecimalPoints || this == Both; }
public boolean hasColon() { return this == Colon || this == Both; }
}
}
@@ -62,86 +62,6 @@ public class TFTDisplay extends AbstractMakerBoard {
private static final long serialVersionUID = 1L;
public enum Controller {
// Each board is dimensioned from its module outline, and the header sits centred on the short
// top edge at the offset recorded here. Active areas are the nominal diagonal taken at the
// panel's aspect ratio -- 2.8" at 4:3 gives 43.2 x 57.6, 1.8" at 4:5 gives 28.56 x 35.7, the
// square 1.54" gives 27.66 either way and the round 1.28" a 32.5 mm lit circle -- which makes
// them derivations rather than measurements, noted so they are not built upon. A glass length
// of zero means the module has no separate dark panel wider than its own lit area, so only the
// screen is drawn, and a hole size of zero means the board has no mounting holes at all.
// The round board is described by the same two outline figures as the others: its width is the
// disc diameter and its length runs from the tab's outer edge to the far side of the disc, so
// the tab's projection is the difference between them and is not carried separately. It is
// the one variant whose header is at the bottom, so its board grows upwards from the pin row
// rather than down from it.
ILI9341_2_8("2.8\" ILI9341 240x320 (Touch + SD)", 50.0d, 86.0d, 43.2d, 57.6d, 69.1d, 3.0d,
3.0d, 3.0d, 0d,
new String[] {"VCC", "GND", "CS", "RESET", "DC", "MOSI (SDI)", "SCK", "LED", "MISO (SDO)",
"T_CLK", "T_CS", "T_DIN", "T_DO", "T_IRQ"}),
ST7735_1_8("1.8\" ST7735 128x160", 34.0d, 45.8d, 28.56d, 35.7d, 0d, 1.5d, 3.0d, 3.0d, 0d,
new String[] {"GND", "VCC", "SCK", "SDA", "RES", "DC", "CS", "BL"}),
ST7789_1_54("1.54\" ST7789 240x240", 32.0d, 43.72d, 27.66d, 27.66d, 33.7d, 1.5d, 2.5d, 2.0d,
0d,
new String[] {"GND", "VCC", "SCL", "SDA", "RES", "DC", "CS", "BLK"}),
GC9A01_1_28("1.28\" GC9A01 240x240 Round", 38.0d, 45.5d, 32.5d, 32.5d, 0d, 1.76d, 0d, 0d,
22.9d,
new String[] {"RST", "CS", "DC", "SDA", "SCL", "GND", "VCC"});
private final String label;
private final double boardWidthMm;
private final double boardLengthMm;
private final double screenWidthMm;
private final double screenLengthMm;
private final double glassLengthMm;
private final double headerOffsetMm;
private final double holeInsetMm;
private final double holeSizeMm;
private final double tabWidthMm;
private final String[] pinNames;
Controller(String label, double boardWidthMm, double boardLengthMm, double screenWidthMm,
double screenLengthMm, double glassLengthMm, double headerOffsetMm, double holeInsetMm,
double holeSizeMm, double tabWidthMm, String[] pinNames) {
this.label = label;
this.boardWidthMm = boardWidthMm;
this.boardLengthMm = boardLengthMm;
this.screenWidthMm = screenWidthMm;
this.screenLengthMm = screenLengthMm;
this.glassLengthMm = glassLengthMm;
this.headerOffsetMm = headerOffsetMm;
this.holeInsetMm = holeInsetMm;
this.holeSizeMm = holeSizeMm;
this.tabWidthMm = tabWidthMm;
this.pinNames = pinNames;
}
@Override public String toString() { return label; }
public double getBoardWidthMm() { return boardWidthMm; }
public double getBoardLengthMm() { return boardLengthMm; }
public double getScreenWidthMm() { return screenWidthMm; }
public double getScreenLengthMm() { return screenLengthMm; }
public double getGlassLengthMm() { return glassLengthMm; }
public double getHeaderOffsetMm() { return headerOffsetMm; }
public double getHoleSizeMm() { return holeSizeMm; }
public String[] getPinNames() { return pinNames; }
/** Inset of every mounting hole from each of the two edges nearest it. */
public double getHoleInsetMm() { return holeInsetMm; }
public boolean hasMountingHoles() { return holeSizeMm > 0; }
public boolean hasGlass() { return glassLengthMm > 0; }
/** Width of the tab the header sits on; meaningful only for a round board. */
public double getTabWidthMm() { return tabWidthMm; }
/** How far the tab projects past the disc: whatever the outline is longer than it is wide. */
public double getTabProjectionMm() { return boardLengthMm - boardWidthMm; }
public boolean isRound() { return tabWidthMm > 0; }
}
/**
* Only the 2.8" board needs its own silkscreen names. Its node names carry the MOSI and MISO
* aliases, which the default label would print in full, and its touch pins are prefixed, which
@@ -388,4 +308,84 @@ public class TFTDisplay extends AbstractMakerBoard {
StringUtils.drawCenteredText(g2d, "TFT", width / 2, height / 2 + 3, HorizontalAlignment.CENTER,
VerticalAlignment.CENTER);
}
public enum Controller {
// Each board is dimensioned from its module outline, and the header sits centred on the short
// top edge at the offset recorded here. Active areas are the nominal diagonal taken at the
// panel's aspect ratio -- 2.8" at 4:3 gives 43.2 x 57.6, 1.8" at 4:5 gives 28.56 x 35.7, the
// square 1.54" gives 27.66 either way and the round 1.28" a 32.5 mm lit circle -- which makes
// them derivations rather than measurements, noted so they are not built upon. A glass length
// of zero means the module has no separate dark panel wider than its own lit area, so only the
// screen is drawn, and a hole size of zero means the board has no mounting holes at all.
// The round board is described by the same two outline figures as the others: its width is the
// disc diameter and its length runs from the tab's outer edge to the far side of the disc, so
// the tab's projection is the difference between them and is not carried separately. It is
// the one variant whose header is at the bottom, so its board grows upwards from the pin row
// rather than down from it.
ILI9341_2_8("2.8\" ILI9341 240x320 (Touch + SD)", 50.0d, 86.0d, 43.2d, 57.6d, 69.1d, 3.0d,
3.0d, 3.0d, 0d,
new String[] {"VCC", "GND", "CS", "RESET", "DC", "MOSI (SDI)", "SCK", "LED", "MISO (SDO)",
"T_CLK", "T_CS", "T_DIN", "T_DO", "T_IRQ"}),
ST7735_1_8("1.8\" ST7735 128x160", 34.0d, 45.8d, 28.56d, 35.7d, 0d, 1.5d, 3.0d, 3.0d, 0d,
new String[] {"GND", "VCC", "SCK", "SDA", "RES", "DC", "CS", "BL"}),
ST7789_1_54("1.54\" ST7789 240x240", 32.0d, 43.72d, 27.66d, 27.66d, 33.7d, 1.5d, 2.5d, 2.0d,
0d,
new String[] {"GND", "VCC", "SCL", "SDA", "RES", "DC", "CS", "BLK"}),
GC9A01_1_28("1.28\" GC9A01 240x240 Round", 38.0d, 45.5d, 32.5d, 32.5d, 0d, 1.76d, 0d, 0d,
22.9d,
new String[] {"RST", "CS", "DC", "SDA", "SCL", "GND", "VCC"});
private final String label;
private final double boardWidthMm;
private final double boardLengthMm;
private final double screenWidthMm;
private final double screenLengthMm;
private final double glassLengthMm;
private final double headerOffsetMm;
private final double holeInsetMm;
private final double holeSizeMm;
private final double tabWidthMm;
private final String[] pinNames;
Controller(String label, double boardWidthMm, double boardLengthMm, double screenWidthMm,
double screenLengthMm, double glassLengthMm, double headerOffsetMm, double holeInsetMm,
double holeSizeMm, double tabWidthMm, String[] pinNames) {
this.label = label;
this.boardWidthMm = boardWidthMm;
this.boardLengthMm = boardLengthMm;
this.screenWidthMm = screenWidthMm;
this.screenLengthMm = screenLengthMm;
this.glassLengthMm = glassLengthMm;
this.headerOffsetMm = headerOffsetMm;
this.holeInsetMm = holeInsetMm;
this.holeSizeMm = holeSizeMm;
this.tabWidthMm = tabWidthMm;
this.pinNames = pinNames;
}
@Override public String toString() { return label; }
public double getBoardWidthMm() { return boardWidthMm; }
public double getBoardLengthMm() { return boardLengthMm; }
public double getScreenWidthMm() { return screenWidthMm; }
public double getScreenLengthMm() { return screenLengthMm; }
public double getGlassLengthMm() { return glassLengthMm; }
public double getHeaderOffsetMm() { return headerOffsetMm; }
public double getHoleSizeMm() { return holeSizeMm; }
public String[] getPinNames() { return pinNames; }
/** Inset of every mounting hole from each of the two edges nearest it. */
public double getHoleInsetMm() { return holeInsetMm; }
public boolean hasMountingHoles() { return holeSizeMm > 0; }
public boolean hasGlass() { return glassLengthMm > 0; }
/** Width of the tab the header sits on; meaningful only for a round board. */
public double getTabWidthMm() { return tabWidthMm; }
/** How far the tab projects past the disc: whatever the outline is longer than it is wide. */
public double getTabProjectionMm() { return boardLengthMm - boardWidthMm; }
public boolean isRound() { return tabWidthMm > 0; }
}
}
@@ -55,62 +55,6 @@ public class WS2812BRing extends AbstractMakerBoard {
private static final long serialVersionUID = 1L;
public enum RingSize {
// The rings are dimensioned in inches, so both diameters are exact conversions of the measured
// figures (1.45 / 0.92, 1.75 / 1.25, 2.58 / 2.06) rather than roundings. LEDs are placed on the
// midpoint between the two rims. The measured LED circles are 1.16, 1.5 and 2.3 inches, which
// the midpoint reproduces exactly on the 16-LED ring and misses by 0.64 mm on the 12 and
// 0.51 mm on the 24 -- accepted deliberately rather than carrying a third diameter per ring.
// Pads sit in the gaps between consecutive LEDs and are not grouped together, so each ring
// lists its pads in order around the circle with the number of LEDs that follow each one
// before the next. The final figure wraps back round to the first pad, which means the gaps
// must sum to the LED count; the constructor checks that rather than trusting a transcription.
_12_LED("12 LEDs", 12, 36.83, 23.368,
new String[] {"OUT", "IN", "GND", "PWR"},
new int[] {2, 4, 2, 4}),
_16_LED("16 LEDs", 16, 44.45, 31.75,
new String[] {"IN", "OUT", "G_1", "G_2", "V+_1", "V+_2"},
new int[] {2, 5, 1, 4, 1, 3}),
_24_LED("24 LEDs", 24, 65.532, 52.324,
new String[] {"OUT", "IN", "G_1", "G_2", "PWR_1", "PWR_2"},
new int[] {2, 8, 2, 2, 2, 8});
private final String label;
private final int ledCount;
private final double outerDiameterMm;
private final double innerDiameterMm;
private final String[] padNames;
private final int[] padGapIndex;
RingSize(String label, int ledCount, double outerDiameterMm, double innerDiameterMm,
String[] padNames, int[] ledsAfterPad) {
this.label = label;
this.ledCount = ledCount;
this.outerDiameterMm = outerDiameterMm;
this.innerDiameterMm = innerDiameterMm;
this.padNames = padNames;
this.padGapIndex = new int[padNames.length];
int gap = 0;
for (int i = 0; i < padNames.length; i++) {
padGapIndex[i] = gap;
gap += ledsAfterPad[i];
}
if (gap != ledCount) {
throw new IllegalArgumentException(
label + ": pad gaps sum to " + gap + " but the ring carries " + ledCount + " LEDs");
}
}
@Override public String toString() { return label; }
public int getLedCount() { return ledCount; }
public double getOuterDiameterMm() { return outerDiameterMm; }
public double getInnerDiameterMm() { return innerDiameterMm; }
public String[] getPadNames() { return padNames; }
/** Which gap between LEDs the pad at {@code index} sits in, counted from the first pad. */
public int getPadGapIndex(int index) { return padGapIndex[index]; }
}
public static Color NEO_BLACK = Color.decode("#111111");
// Pads are small and sit near the rim; these are what the ring overrides the inherited solder
@@ -351,4 +295,60 @@ public class WS2812BRing extends AbstractMakerBoard {
g2d.fill(new Ellipse2D.Double(lx - dotR, ly - dotR, dotR * 2, dotR * 2));
}
}
public enum RingSize {
// The rings are dimensioned in inches, so both diameters are exact conversions of the measured
// figures (1.45 / 0.92, 1.75 / 1.25, 2.58 / 2.06) rather than roundings. LEDs are placed on the
// midpoint between the two rims. The measured LED circles are 1.16, 1.5 and 2.3 inches, which
// the midpoint reproduces exactly on the 16-LED ring and misses by 0.64 mm on the 12 and
// 0.51 mm on the 24 -- accepted deliberately rather than carrying a third diameter per ring.
// Pads sit in the gaps between consecutive LEDs and are not grouped together, so each ring
// lists its pads in order around the circle with the number of LEDs that follow each one
// before the next. The final figure wraps back round to the first pad, which means the gaps
// must sum to the LED count; the constructor checks that rather than trusting a transcription.
_12_LED("12 LEDs", 12, 36.83, 23.368,
new String[] {"OUT", "IN", "GND", "PWR"},
new int[] {2, 4, 2, 4}),
_16_LED("16 LEDs", 16, 44.45, 31.75,
new String[] {"IN", "OUT", "G_1", "G_2", "V+_1", "V+_2"},
new int[] {2, 5, 1, 4, 1, 3}),
_24_LED("24 LEDs", 24, 65.532, 52.324,
new String[] {"OUT", "IN", "G_1", "G_2", "PWR_1", "PWR_2"},
new int[] {2, 8, 2, 2, 2, 8});
private final String label;
private final int ledCount;
private final double outerDiameterMm;
private final double innerDiameterMm;
private final String[] padNames;
private final int[] padGapIndex;
RingSize(String label, int ledCount, double outerDiameterMm, double innerDiameterMm,
String[] padNames, int[] ledsAfterPad) {
this.label = label;
this.ledCount = ledCount;
this.outerDiameterMm = outerDiameterMm;
this.innerDiameterMm = innerDiameterMm;
this.padNames = padNames;
this.padGapIndex = new int[padNames.length];
int gap = 0;
for (int i = 0; i < padNames.length; i++) {
padGapIndex[i] = gap;
gap += ledsAfterPad[i];
}
if (gap != ledCount) {
throw new IllegalArgumentException(
label + ": pad gaps sum to " + gap + " but the ring carries " + ledCount + " LEDs");
}
}
@Override public String toString() { return label; }
public int getLedCount() { return ledCount; }
public double getOuterDiameterMm() { return outerDiameterMm; }
public double getInnerDiameterMm() { return innerDiameterMm; }
public String[] getPadNames() { return padNames; }
/** Which gap between LEDs the pad at {@code index} sits in, counted from the first pad. */
public int getPadGapIndex(int index) { return padGapIndex[index]; }
}
}
@@ -55,36 +55,6 @@ public class WS2812BStrip extends AbstractMakerBoard {
private static final long serialVersionUID = 1L;
/**
* Tape is sold by LEDs per metre, and everything else follows from that: the pitch is the metre
* divided by the count, so it is derived rather than stored. Width does not follow, and is the
* one figure here that is a stated norm rather than a measurement -- the 30 and 60 tapes are
* usually 10 mm and the 144 usually 12 mm, because at that pitch the package leaves no room for
* the conductor beside it.
*/
public enum Density {
_30("30 LED/m", 30, 10.0d),
_60("60 LED/m", 60, 10.0d),
_144("144 LED/m", 144, 12.0d);
private final String label;
private final int ledsPerMetre;
private final double widthMm;
Density(String label, int ledsPerMetre, double widthMm) {
this.label = label;
this.ledsPerMetre = ledsPerMetre;
this.widthMm = widthMm;
}
@Override public String toString() { return label; }
public int getLedsPerMetre() { return ledsPerMetre; }
public double getWidthMm() { return widthMm; }
/** Centre-to-centre LED spacing: one metre shared out between that many LEDs. */
public double getPitchMm() { return 1000.0d / ledsPerMetre; }
}
public static Color TAPE_WHITE = Color.decode("#F2F2F2");
// How far in from the cut end the pads sit, and the air left between a pad and the nearest LED.
@@ -311,4 +281,34 @@ public class WS2812BStrip extends AbstractMakerBoard {
g2d.fillRect(3 + i * (width - 6) / 4, (int) (height / 2.0 - 3), 5, 6);
}
}
/**
* Tape is sold by LEDs per metre, and everything else follows from that: the pitch is the metre
* divided by the count, so it is derived rather than stored. Width does not follow, and is the
* one figure here that is a stated norm rather than a measurement -- the 30 and 60 tapes are
* usually 10 mm and the 144 usually 12 mm, because at that pitch the package leaves no room for
* the conductor beside it.
*/
public enum Density {
_30("30 LED/m", 30, 10.0d),
_60("60 LED/m", 60, 10.0d),
_144("144 LED/m", 144, 12.0d);
private final String label;
private final int ledsPerMetre;
private final double widthMm;
Density(String label, int ledsPerMetre, double widthMm) {
this.label = label;
this.ledsPerMetre = ledsPerMetre;
this.widthMm = widthMm;
}
@Override public String toString() { return label; }
public int getLedsPerMetre() { return ledsPerMetre; }
public double getWidthMm() { return widthMm; }
/** Centre-to-centre LED spacing: one metre shared out between that many LEDs. */
public double getPitchMm() { return 1000.0d / ledsPerMetre; }
}
}
@@ -76,58 +76,6 @@ public class ArduinoNano extends AbstractMakerBoard {
public static final int PINS_PER_ROW = 15;
// Ordered by label so the Version drop-down reads alphabetically. XStream serializes enum
// constants by name, so the order here is free to change without affecting existing .diy files.
public enum NanoVersion {
CLASSIC("Nano (ATmega328)", "NANO", "m328P", null),
NANO_33_BLE("Nano 33 BLE", "33 BLE", null, "NINA-B306"),
NANO_33_BLE_SENSE("Nano 33 BLE Sense", "33 BLE SENSE", null, "NINA-B306"),
NANO_33_IOT("Nano 33 IoT", "33 IOT", "SAMD21", "NINA-W102"),
NANO_ESP32("Nano ESP32", "NANO ESP32", null, "NORA-W106"),
EVERY("Nano Every", "EVERY", "m4809", "SAMD11"),
NANO_R4("Nano R4", "NANO R4", "RA4M1", "QWIIC"),
// Silkscreened "CONNECT" rather than the full name, which does not fit between the two rows of
// pad names; the RP2040 package right below it carries the part number
NANO_RP2040_CONNECT("Nano RP2040 Connect", "CONNECT", "RP2040", "NINA-W102");
private final String label;
private final String silkLabel;
private final String mcuLabel;
private final String moduleLabel;
NanoVersion(String label, String silkLabel, String mcuLabel, String moduleLabel) {
this.label = label;
this.silkLabel = silkLabel;
this.mcuLabel = mcuLabel;
this.moduleLabel = moduleLabel;
}
public String getSilkLabel() {
return silkLabel;
}
/**
* Marking of the separately visible MCU package, or null when the MCU is a die inside the
* module rather than its own part on the board.
*/
public String getMcuLabel() {
return mcuLabel;
}
/**
* Marking of the part occupying the end of the board opposite the USB jack, where the classic
* Nano has its ICSP header. Null on the classic Nano, which has the header instead.
*/
public String getModuleLabel() {
return moduleLabel;
}
@Override
public String toString() {
return label;
}
}
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",
@@ -667,4 +615,56 @@ public class ArduinoNano extends AbstractMakerBoard {
g2d.fillRect(width - 9, y, 2, 2);
}
}
// Ordered by label so the Version drop-down reads alphabetically. XStream serializes enum
// constants by name, so the order here is free to change without affecting existing .diy files.
public enum NanoVersion {
CLASSIC("Nano (ATmega328)", "NANO", "m328P", null),
NANO_33_BLE("Nano 33 BLE", "33 BLE", null, "NINA-B306"),
NANO_33_BLE_SENSE("Nano 33 BLE Sense", "33 BLE SENSE", null, "NINA-B306"),
NANO_33_IOT("Nano 33 IoT", "33 IOT", "SAMD21", "NINA-W102"),
NANO_ESP32("Nano ESP32", "NANO ESP32", null, "NORA-W106"),
EVERY("Nano Every", "EVERY", "m4809", "SAMD11"),
NANO_R4("Nano R4", "NANO R4", "RA4M1", "QWIIC"),
// Silkscreened "CONNECT" rather than the full name, which does not fit between the two rows of
// pad names; the RP2040 package right below it carries the part number
NANO_RP2040_CONNECT("Nano RP2040 Connect", "CONNECT", "RP2040", "NINA-W102");
private final String label;
private final String silkLabel;
private final String mcuLabel;
private final String moduleLabel;
NanoVersion(String label, String silkLabel, String mcuLabel, String moduleLabel) {
this.label = label;
this.silkLabel = silkLabel;
this.mcuLabel = mcuLabel;
this.moduleLabel = moduleLabel;
}
public String getSilkLabel() {
return silkLabel;
}
/**
* Marking of the separately visible MCU package, or null when the MCU is a die inside the
* module rather than its own part on the board.
*/
public String getMcuLabel() {
return mcuLabel;
}
/**
* Marking of the part occupying the end of the board opposite the USB jack, where the classic
* Nano has its ICSP header. Null on the classic Nano, which has the header instead.
*/
public String getModuleLabel() {
return moduleLabel;
}
@Override
public String toString() {
return label;
}
}
}
@@ -61,24 +61,6 @@ public class ArduinoUno extends AbstractMakerBoard {
private static final long serialVersionUID = 1L;
public enum ArduinoUnoVersion {
LEONARDO("Leonardo"),
REV3("UNO R3"),
R4_MINIMA("UNO R4 Minima"),
R4_WIFI("UNO R4 WiFi");
private String label;
private ArduinoUnoVersion(String label) {
this.label = label;
}
@Override
public String toString() {
return label;
}
}
private ArduinoUnoVersion version = ArduinoUnoVersion.REV3;
public static Size BOARD_WIDTH = new Size(68.6d, SizeUnit.mm);
@@ -675,4 +657,22 @@ public class ArduinoUno extends AbstractMakerBoard {
double textX = boardX + (boardW / 2.0) + 0.5;
StringUtils.drawCenteredText(g2d, iconText, textX, textY, HorizontalAlignment.CENTER, VerticalAlignment.CENTER);
}
public enum ArduinoUnoVersion {
LEONARDO("Leonardo"),
REV3("UNO R3"),
R4_MINIMA("UNO R4 Minima"),
R4_WIFI("UNO R4 WiFi");
private String label;
private ArduinoUnoVersion(String label) {
this.label = label;
}
@Override
public String toString() {
return label;
}
}
}
@@ -62,25 +62,6 @@ public class ESP32DevKit extends AbstractMakerBoard {
private static final long serialVersionUID = 1L;
public enum DevKitVersion {
DevKit_V1_30Pin("ESP32 DevKit V1 (30-Pin)"),
DevKitC_V4_38Pin("ESP32 DevKitC V4 (38-Pin)"),
ESP32_S3_DevKitC_44Pin("ESP32-S3 DevKitC-1 (44-Pin)"),
ESP32_C3_DevKitM_1("ESP32-C3 DevKitM-1 (30-Pin)"),
ESP32_C6_DevKitC_1("ESP32-C6 DevKitC-1 (32-Pin)");
private final String label;
DevKitVersion(String label) {
this.label = label;
}
@Override
public String toString() {
return label;
}
}
public static Size BOARD_WIDTH_30 = new Size(28.33d, SizeUnit.mm);
public static Size BOARD_LENGTH_30 = new Size(51.45d, SizeUnit.mm);
public static Size TOP_MARGIN_30 = new Size(6.7d, SizeUnit.mm);
@@ -859,4 +840,23 @@ public class ESP32DevKit extends AbstractMakerBoard {
g2d.setFont(ICON_FONT);
StringUtils.drawCenteredText(g2d, "ESP32", width / 2, height / 2 + 8, HorizontalAlignment.CENTER, VerticalAlignment.CENTER);
}
public enum DevKitVersion {
DevKit_V1_30Pin("ESP32 DevKit V1 (30-Pin)"),
DevKitC_V4_38Pin("ESP32 DevKitC V4 (38-Pin)"),
ESP32_S3_DevKitC_44Pin("ESP32-S3 DevKitC-1 (44-Pin)"),
ESP32_C3_DevKitM_1("ESP32-C3 DevKitM-1 (30-Pin)"),
ESP32_C6_DevKitC_1("ESP32-C6 DevKitC-1 (32-Pin)");
private final String label;
DevKitVersion(String label) {
this.label = label;
}
@Override
public String toString() {
return label;
}
}
}
@@ -83,23 +83,6 @@ public class RaspberryPi extends AbstractMakerBoard {
"PCIe", "MIPI 1", "MIPI 0", "UART"
};
public enum RaspberryPiVersion {
PI_3_B("Pi 3 (Model B+)"),
PI_4_B("Pi 4 (Model B)"),
PI_5("Pi 5");
private String label;
private RaspberryPiVersion(String label) {
this.label = label;
}
@Override
public String toString() {
return label;
}
}
private RaspberryPiVersion version = RaspberryPiVersion.PI_5;
@org.diylc.core.annotations.EditableProperty(name = "Version")
@@ -581,4 +564,21 @@ public class RaspberryPi extends AbstractMakerBoard {
g2d.setFont(ICON_FONT_LARGE);
StringUtils.drawCenteredText(g2d, "RPi", 14, 25, HorizontalAlignment.CENTER, VerticalAlignment.CENTER);
}
public enum RaspberryPiVersion {
PI_3_B("Pi 3 (Model B+)"),
PI_4_B("Pi 4 (Model B)"),
PI_5("Pi 5");
private String label;
private RaspberryPiVersion(String label) {
this.label = label;
}
@Override
public String toString() {
return label;
}
}
}
@@ -61,24 +61,6 @@ public class RaspberryPiPico extends AbstractMakerBoard {
private static final long serialVersionUID = 1L;
public enum PicoVersion {
PICO("Pi Pico"),
PICO_W("Pi Pico W"),
PICO_2("Pi Pico 2"),
PICO_2_W("Pi Pico 2 W");
private final String label;
PicoVersion(String label) {
this.label = label;
}
@Override
public String toString() {
return label;
}
}
public static Size BOARD_WIDTH = new Size(21.0d, SizeUnit.mm);
public static Size BOARD_LENGTH = new Size(51.0d, SizeUnit.mm);
@@ -509,4 +491,22 @@ public class RaspberryPiPico extends AbstractMakerBoard {
g2d.setFont(ICON_FONT);
StringUtils.drawCenteredText(g2d, "PICO", width / 2, height / 2 + 10, HorizontalAlignment.CENTER, VerticalAlignment.CENTER);
}
public enum PicoVersion {
PICO("Pi Pico"),
PICO_W("Pi Pico W"),
PICO_2("Pi Pico 2"),
PICO_2_W("Pi Pico 2 W");
private final String label;
PicoVersion(String label) {
this.label = label;
}
@Override
public String toString() {
return label;
}
}
}
@@ -75,23 +75,6 @@ public class RaspberryPiZero extends AbstractMakerBoard {
PIN_NAMES[44] = "TV 2";
}
public enum ZeroVersion {
PI_ZERO("Pi Zero"),
PI_ZERO_W("Pi Zero W"),
PI_ZERO_2_W("Pi Zero 2 W");
private final String label;
ZeroVersion(String label) {
this.label = label;
}
@Override
public String toString() {
return label;
}
}
protected ZeroVersion version = ZeroVersion.PI_ZERO;
protected boolean headers = false;
@@ -399,4 +382,21 @@ public class RaspberryPiZero extends AbstractMakerBoard {
g2d.setFont(ICON_FONT);
StringUtils.drawCenteredText(g2d, "ZERO", width / 2 + 4, 21, HorizontalAlignment.CENTER, VerticalAlignment.CENTER);
}
public enum ZeroVersion {
PI_ZERO("Pi Zero"),
PI_ZERO_W("Pi Zero W"),
PI_ZERO_2_W("Pi Zero 2 W");
private final String label;
ZeroVersion(String label) {
this.label = label;
}
@Override
public String toString() {
return label;
}
}
}
@@ -58,23 +58,6 @@ public class Teensy extends AbstractMakerBoard {
private static final long serialVersionUID = 1L;
public enum TeensyVersion {
Teensy_3_2("Teensy 3.2"),
Teensy_4_0("Teensy 4.0"),
Teensy_4_1("Teensy 4.1");
private final String label;
TeensyVersion(String label) {
this.label = label;
}
@Override
public String toString() {
return label;
}
}
// gap between the Ethernet header pads and the silkscreen box printed around them
public static Size ETH_BOX_MARGIN = new Size(1.2d, SizeUnit.mm);
@@ -565,4 +548,21 @@ public class Teensy extends AbstractMakerBoard {
StringUtils.drawCenteredText(g2d, "TEENSY", width / 2, height / 2 + 8,
HorizontalAlignment.CENTER, VerticalAlignment.CENTER);
}
public enum TeensyVersion {
Teensy_3_2("Teensy 3.2"),
Teensy_4_0("Teensy 4.0"),
Teensy_4_1("Teensy 4.1");
private final String label;
TeensyVersion(String label) {
this.label = label;
}
@Override
public String toString() {
return label;
}
}
}