mirror of
https://github.com/bancika/diy-layout-creator
synced 2026-11-07 12:24:58 +10:00
displays
This commit is contained in:
@@ -521,6 +521,10 @@
|
||||
<changeType>NEW_FEATURE</changeType>
|
||||
<description>LED Matrix (MAX7219) - single 8x8, compact 8x8, 4-in-1 32x8</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>NEW_FEATURE</changeType>
|
||||
<description>NeoPixel Breakout (WS2812B) - single LED</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>NEW_FEATURE</changeType>
|
||||
<description>NeoPixel Jewel (WS2812B) - 7 LEDs</description>
|
||||
|
||||
@@ -619,14 +619,6 @@ public abstract class AbstractMakerBoard extends AbstractTransparentComponent<Vo
|
||||
drawingObserver.stopTrackingContinuityArea();
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to draw rectangular tinned solder pads with drill holes at given control points.
|
||||
*/
|
||||
/**
|
||||
* Footprint of a solder pad. The defaults are the sizes this method has always drawn, expressed
|
||||
* as the inch measures they work out to exactly, so a board that wants smaller pads overrides
|
||||
* these rather than a second pad-drawing method being written somewhere else.
|
||||
*/
|
||||
/**
|
||||
* Spreads {@link #RGB_LED_GRADIENT} evenly over {@code count} LEDs, interpolating between
|
||||
* neighbouring anchors and treating them as a loop so the last LED runs back into the first.
|
||||
|
||||
@@ -0,0 +1,240 @@
|
||||
/*
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package org.diylc.components.displays;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Composite;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Shape;
|
||||
import java.awt.geom.AffineTransform;
|
||||
import java.awt.geom.Point2D;
|
||||
import java.awt.geom.Rectangle2D;
|
||||
import java.awt.geom.RoundRectangle2D;
|
||||
|
||||
import org.diylc.common.ObjectCache;
|
||||
import org.diylc.common.Orientation;
|
||||
import org.diylc.components.AbstractMakerBoard;
|
||||
import org.diylc.components.MakerBoardPainter;
|
||||
import org.diylc.core.ComponentState;
|
||||
import org.diylc.core.IDIYComponent;
|
||||
import org.diylc.core.IDrawingObserver;
|
||||
import org.diylc.core.Project;
|
||||
import org.diylc.core.annotations.BomPolicy;
|
||||
import org.diylc.core.annotations.ComponentDescriptor;
|
||||
import org.diylc.core.annotations.KeywordPolicy;
|
||||
import org.diylc.core.measures.Size;
|
||||
import org.diylc.core.measures.SizeUnit;
|
||||
import org.diylc.utils.Constants;
|
||||
|
||||
@ComponentDescriptor(name = "NeoPixel Breakout", category = "Displays & Outputs",
|
||||
author = "Branislav Stojkovic",
|
||||
description = "Single-LED Addressable RGB WS2812B NeoPixel Breakout",
|
||||
instanceNamePrefix = "LED", zOrder = IDIYComponent.COMPONENT,
|
||||
bomPolicy = BomPolicy.SHOW_ONLY_TYPE_NAME, keywordPolicy = KeywordPolicy.SHOW_TYPE_NAME,
|
||||
enableCache = true)
|
||||
public class WS2812BBreakout extends AbstractMakerBoard {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public static Color NEO_BLACK = Color.decode("#111111");
|
||||
|
||||
// The board lies on its side: a three-pad column up each of the two short edges, and the two
|
||||
// mounting holes on the centre line between them, one towards the top edge and one the bottom.
|
||||
public static Size BOARD_WIDTH = new Size(0.5d, SizeUnit.in);
|
||||
public static Size BOARD_HEIGHT = new Size(0.4d, SizeUnit.in);
|
||||
|
||||
// The real board is wired through a three-way connector at each end. Those are drawn as plain
|
||||
// pads instead: at this size a connector body would cover most of the board and hide the LED,
|
||||
// and what a layout needs from it is where the three wires land.
|
||||
//
|
||||
// As on the stick the pads are flush with their own edge rather than inset from it, so how far
|
||||
// in their centres sit is half their reach and not a figure of its own.
|
||||
public static Size PAD_WIDTH = new Size(1.5d, SizeUnit.mm);
|
||||
public static Size PAD_LENGTH = new Size(1.5d, SizeUnit.mm);
|
||||
|
||||
public static Size MOUNTING_HOLE_SIZE = new Size(2.0d, SizeUnit.mm);
|
||||
public static Size MOUNTING_HOLE_SPACING = new Size(0.3d, SizeUnit.in);
|
||||
|
||||
// Drawn lit off the same colour wheel as the rest of the family, so a breakout on a layout reads
|
||||
// as the same part as one pixel of a stick.
|
||||
public static Color LED_COLOR = buildLedGradient(1)[0];
|
||||
|
||||
// Each edge carries ground, power and one data line. The two grounds are the same net on the
|
||||
// board, as are the two supplies, and are numbered only to keep the node names distinct.
|
||||
private static final String[] PIN_NAMES = {
|
||||
"GND_1", "VIN_1", "IN",
|
||||
"GND_2", "VIN_2", "OUT"
|
||||
};
|
||||
|
||||
public WS2812BBreakout() {
|
||||
super();
|
||||
this.bodyColor = NEO_BLACK;
|
||||
updateControlPoints();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getControlPointNodeName(int index) {
|
||||
if (index >= 0 && index < PIN_NAMES.length) {
|
||||
return PIN_NAMES[index];
|
||||
}
|
||||
return Integer.toString(index + 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateControlPoints() {
|
||||
Point2D firstPoint = controlPoints[0];
|
||||
double spacing = PIN_SPACING.convertToPixels();
|
||||
// the two pad columns stand flush against their own edges, so what separates them is the
|
||||
// board less one whole pad
|
||||
double columnSpacing = BOARD_WIDTH.convertToPixels() - PAD_WIDTH.convertToPixels();
|
||||
|
||||
double[][] relativeOffsets = new double[PIN_NAMES.length][2];
|
||||
for (int i = 0; i < 3; i++) {
|
||||
relativeOffsets[i][0] = 0;
|
||||
relativeOffsets[i][1] = i * spacing;
|
||||
relativeOffsets[3 + i][0] = columnSpacing;
|
||||
relativeOffsets[3 + i][1] = i * spacing;
|
||||
}
|
||||
|
||||
rotatePoints(firstPoint, relativeOffsets);
|
||||
}
|
||||
|
||||
/**
|
||||
* Left edge of the board; the first pad column is flush with it, so its centres sit half a pad
|
||||
* inboard.
|
||||
*/
|
||||
private double getBoardX(double x) {
|
||||
return x - PAD_WIDTH.convertToPixels() / 2.0;
|
||||
}
|
||||
|
||||
/** Top edge of the board, derived so that the three-pad column is centred across its height. */
|
||||
private double getBoardY(double y) {
|
||||
double padSpan = 2 * PIN_SPACING.convertToPixels();
|
||||
return y - (BOARD_HEIGHT.convertToPixels() - padSpan) / 2.0;
|
||||
}
|
||||
|
||||
/** The one package, in the middle of the board with the holes and pads placed around it. */
|
||||
Point2D getLedCentre() {
|
||||
Point2D p0 = controlPoints[0];
|
||||
return new Point2D.Double(getBoardX(p0.getX()) + BOARD_WIDTH.convertToPixels() / 2.0,
|
||||
getBoardY(p0.getY()) + BOARD_HEIGHT.convertToPixels() / 2.0);
|
||||
}
|
||||
|
||||
/**
|
||||
* One of the two mounting holes. They sit on the centre line between the pad columns, a fixed
|
||||
* span apart astride the LED, and they are what the board is tightest against: 0.27 mm of board
|
||||
* outside each rim and 0.31 mm between the rim and the package.
|
||||
*/
|
||||
Point2D getHoleCentre(boolean bottom) {
|
||||
Point2D centre = getLedCentre();
|
||||
double offset = MOUNTING_HOLE_SPACING.convertToPixels() / 2.0;
|
||||
return new Point2D.Double(centre.getX(), centre.getY() + (bottom ? offset : -offset));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Shape getBodyShape() {
|
||||
Point2D p0 = controlPoints[0];
|
||||
return new RoundRectangle2D.Double(getBoardX(p0.getX()), getBoardY(p0.getY()),
|
||||
BOARD_WIDTH.convertToPixels(), BOARD_HEIGHT.convertToPixels(), 4, 4);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(Graphics2D g2d, ComponentState componentState, boolean outlineMode,
|
||||
Project project, IDrawingObserver drawingObserver) {
|
||||
if (checkPointsClipped(g2d.getClip())) {
|
||||
return;
|
||||
}
|
||||
|
||||
Point2D p0 = controlPoints[0];
|
||||
double x = p0.getX();
|
||||
double y = p0.getY();
|
||||
|
||||
AffineTransform oldTx = g2d.getTransform();
|
||||
if (orientation != Orientation.DEFAULT) {
|
||||
g2d.rotate(orientation.toRadians(), x, y);
|
||||
}
|
||||
|
||||
Shape boardShape = getBodyShape();
|
||||
|
||||
Composite oldComposite = applyAlpha(g2d, componentState);
|
||||
|
||||
drawingObserver.startTracking();
|
||||
g2d.setColor(outlineMode ? Constants.TRANSPARENT_COLOR : bodyColor);
|
||||
g2d.fill(boardShape);
|
||||
drawingObserver.stopTracking();
|
||||
|
||||
g2d.setColor(getFinalBorderColor(componentState, outlineMode));
|
||||
g2d.setStroke(ObjectCache.getInstance().fetchBasicStroke(1.5f));
|
||||
g2d.draw(boardShape);
|
||||
|
||||
if (!outlineMode) {
|
||||
double holeSize = MOUNTING_HOLE_SIZE.convertToPixels();
|
||||
for (boolean bottom : new boolean[] {false, true}) {
|
||||
Point2D hole = getHoleCentre(bottom);
|
||||
MakerBoardPainter.drawMountingHole(g2d, hole.getX(), hole.getY(), holeSize);
|
||||
}
|
||||
|
||||
// The pad columns clear the package by 2.35 mm, but that room is between a pad and the
|
||||
// package rather than beside a pad, so there is nowhere a pin name would go: this face
|
||||
// carries no silkscreen, as the stick's does not.
|
||||
Point2D led = getLedCentre();
|
||||
MakerBoardPainter.drawAddressableLed(g2d, led.getX(), led.getY(),
|
||||
RGB_LED_SIZE.convertToPixels(), LED_COLOR);
|
||||
}
|
||||
|
||||
g2d.setTransform(oldTx);
|
||||
|
||||
drawSurfacePads(g2d, 0, controlPoints.length, PAD_WIDTH, PAD_LENGTH, outlineMode,
|
||||
drawingObserver);
|
||||
|
||||
g2d.setComposite(oldComposite);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawIcon(Graphics2D g2d, int width, int height) {
|
||||
// landscape in the same 5:4 as the board, so the icon reads as the part and not as a sibling
|
||||
// of the portrait modules further down the category
|
||||
double rectW = width - 4;
|
||||
double rectH = rectW * 0.8;
|
||||
double rectX = 2;
|
||||
double rectY = (height - rectH) / 2.0;
|
||||
|
||||
g2d.setColor(NEO_BLACK);
|
||||
g2d.fill(new RoundRectangle2D.Double(rectX, rectY, rectW, rectH, 3, 3));
|
||||
g2d.setColor(Color.DARK_GRAY);
|
||||
g2d.draw(new RoundRectangle2D.Double(rectX, rectY, rectW, rectH, 3, 3));
|
||||
|
||||
double padW = 3;
|
||||
double padH = 2;
|
||||
double padStep = 5;
|
||||
g2d.setColor(PAD_COLOR);
|
||||
for (int i = 0; i < 3; i++) {
|
||||
double padY = height / 2.0 - padStep + i * padStep - padH / 2.0;
|
||||
g2d.fill(new Rectangle2D.Double(rectX, padY, padW, padH));
|
||||
g2d.fill(new Rectangle2D.Double(rectX + rectW - padW, padY, padW, padH));
|
||||
}
|
||||
|
||||
double ledSize = Math.min(rectW - 6, 12);
|
||||
MakerBoardPainter.drawAddressableLed(g2d, width / 2.0, height / 2.0, ledSize,
|
||||
Color.decode("#FF3333"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
/*
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package org.diylc.components.displays;
|
||||
|
||||
import java.awt.geom.Rectangle2D;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.diylc.appframework.miscutils.ConfigurationManager;
|
||||
import org.diylc.components.displays.LEDMatrix.Modules;
|
||||
import org.diylc.components.maker.MakerBoardTestSupport;
|
||||
import org.junit.Assert;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Basic checks for the MAX7219 matrix. The three variants differ in outline and in which edge
|
||||
* their headers leave by, so each one places the same ten pins differently.
|
||||
*/
|
||||
public class LEDMatrixTest {
|
||||
|
||||
private static final double PX_PER_MM = 200.0d / 25.4d;
|
||||
|
||||
@BeforeClass
|
||||
public static void setUp() {
|
||||
try {
|
||||
ConfigurationManager.getInstance().initialize("diylc");
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
private static double mm(double px) {
|
||||
return px / PX_PER_MM;
|
||||
}
|
||||
|
||||
private static LEDMatrix of(Modules modules) {
|
||||
LEDMatrix matrix = new LEDMatrix();
|
||||
matrix.setModules(modules);
|
||||
return matrix;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void boardSizeFollowsTheModules() {
|
||||
for (Modules modules : Modules.values()) {
|
||||
Rectangle2D bounds = of(modules).getBodyShape().getBounds2D();
|
||||
Assert.assertEquals(modules + " width", modules.getBoardWidthMm(), mm(bounds.getWidth()),
|
||||
0.01d);
|
||||
Assert.assertEquals(modules + " length", modules.getBoardHeightMm(), mm(bounds.getHeight()),
|
||||
0.01d);
|
||||
}
|
||||
}
|
||||
|
||||
/** The modules are flush with each other and with the board, so the outline has to bound them. */
|
||||
@Test
|
||||
public void theModulesFitAcrossTheBoard() {
|
||||
for (Modules modules : Modules.values()) {
|
||||
double matrixSize = mm(LEDMatrix.MATRIX_SIZE.convertToPixels());
|
||||
Assert.assertTrue(modules + " is narrower than its modules",
|
||||
modules.getBoardWidthMm() >= modules.getCount() * matrixSize);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void controlPointsAreTheTwoHeadersInOrder() {
|
||||
String[] expected = new String[] {"VCC_IN", "GND_IN", "DIN", "CS_IN", "CLK_IN", "VCC_OUT",
|
||||
"GND_OUT", "DOUT", "CS_OUT", "CLK_OUT"};
|
||||
|
||||
for (Modules modules : Modules.values()) {
|
||||
LEDMatrix matrix = of(modules);
|
||||
Assert.assertEquals(modules.toString(), expected.length, matrix.getControlPointCount());
|
||||
|
||||
Set<String> names = new HashSet<String>();
|
||||
for (int i = 0; i < expected.length; i++) {
|
||||
Assert.assertEquals(modules + " pin " + i, expected[i], matrix.getControlPointNodeName(i));
|
||||
Assert.assertTrue(modules + " duplicate node name " + expected[i], names.add(expected[i]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Both headers are five-pin rows on 0.1" pitch wherever they leave the board. The tall single
|
||||
* runs them across the width; the two tileable variants stand them on end at the short edges.
|
||||
*/
|
||||
@Test
|
||||
public void eachHeaderIsAFivePinRow() {
|
||||
for (Modules modules : Modules.values()) {
|
||||
LEDMatrix matrix = of(modules);
|
||||
MakerBoardTestSupport.assertRow(matrix, 0, 4);
|
||||
MakerBoardTestSupport.assertRow(matrix, 5, 9);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void everyPinSitsOnTheBoard() {
|
||||
for (Modules modules : Modules.values()) {
|
||||
LEDMatrix matrix = of(modules);
|
||||
Rectangle2D board = matrix.getBodyShape().getBounds2D();
|
||||
for (int i = 0; i < matrix.getControlPointCount(); i++) {
|
||||
Assert.assertTrue(modules + " pin " + i + " is off the board",
|
||||
board.contains(matrix.getControlPoint(i)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void modulesRoundTripThroughTheSetter() {
|
||||
Assert.assertEquals(Modules.Single_8x8, new LEDMatrix().getModules());
|
||||
for (Modules modules : Modules.values()) {
|
||||
LEDMatrix matrix = of(modules);
|
||||
Assert.assertEquals(modules, matrix.getModules());
|
||||
Assert.assertEquals(modules.toString(), matrix.getValueForDisplay());
|
||||
}
|
||||
}
|
||||
|
||||
/** A project saved before the variant property existed deserialises with no modules set. */
|
||||
@Test
|
||||
public void aMissingModulesValueFallsBackToTheSingle() {
|
||||
LEDMatrix matrix = of(null);
|
||||
Assert.assertEquals(Modules.Single_8x8, matrix.getModules());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void drawsCleanlyInEveryState() {
|
||||
for (Modules modules : Modules.values()) {
|
||||
MakerBoardTestSupport.assertDrawsCleanly(of(modules));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -167,4 +167,9 @@ public class Nokia5110LCDTest {
|
||||
Assert.assertEquals(Display.VALUE, printed.getScreen());
|
||||
MakerBoardTestSupport.assertScreenTextIsDrawn(printed, blank);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void drawsCleanlyInEveryState() {
|
||||
MakerBoardTestSupport.assertDrawsCleanly(new Nokia5110LCD());
|
||||
}
|
||||
}
|
||||
|
||||
+201
@@ -0,0 +1,201 @@
|
||||
/*
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package org.diylc.components.displays;
|
||||
|
||||
import java.awt.geom.Rectangle2D;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.diylc.appframework.miscutils.ConfigurationManager;
|
||||
import org.diylc.components.displays.SevenSegmentDisplay.Common;
|
||||
import org.diylc.components.displays.SevenSegmentDisplay.DisplayType;
|
||||
import org.diylc.components.displays.SevenSegmentDisplay.Punctuation;
|
||||
import org.diylc.components.maker.MakerBoardTestSupport;
|
||||
import org.junit.Assert;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Basic checks for the seven-segment displays. Three of the four variants are DIP packages that
|
||||
* straddle their pins with a row either side, and the fourth is a module carrying a single header
|
||||
* down one edge, so the pin layout is what differs most between them.
|
||||
*/
|
||||
public class SevenSegmentDisplayTest {
|
||||
|
||||
private static final double PX_PER_MM = 200.0d / 25.4d;
|
||||
|
||||
@BeforeClass
|
||||
public static void setUp() {
|
||||
try {
|
||||
ConfigurationManager.getInstance().initialize("diylc");
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
private static double mm(double px) {
|
||||
return px / PX_PER_MM;
|
||||
}
|
||||
|
||||
private static SevenSegmentDisplay of(DisplayType displayType) {
|
||||
SevenSegmentDisplay display = new SevenSegmentDisplay();
|
||||
display.setDisplayType(displayType);
|
||||
return display;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bodySizeFollowsThePackage() {
|
||||
for (DisplayType displayType : DisplayType.values()) {
|
||||
Rectangle2D bounds = of(displayType).getBodyShape().getBounds2D();
|
||||
Assert.assertEquals(displayType + " width", displayType.getBodyWidthMm(),
|
||||
mm(bounds.getWidth()), 0.01d);
|
||||
Assert.assertEquals(displayType + " length", displayType.getBodyLengthMm(),
|
||||
mm(bounds.getHeight()), 0.01d);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void pinCountFollowsThePackage() {
|
||||
Assert.assertEquals(10, of(DisplayType.SingleDigit_10Pin).getControlPointCount());
|
||||
Assert.assertEquals(12, of(DisplayType.FourDigit_0_36_12Pin).getControlPointCount());
|
||||
Assert.assertEquals(12, of(DisplayType.FourDigit_0_56_12Pin).getControlPointCount());
|
||||
Assert.assertEquals(4, of(DisplayType.TM1637_Module_4Pin).getControlPointCount());
|
||||
}
|
||||
|
||||
/**
|
||||
* Pin arrays are in DIP order: pin 1 bottom left, along the bottom row, then back along the top.
|
||||
*/
|
||||
@Test
|
||||
public void pinNamesAreInDipOrder() {
|
||||
Assert.assertArrayEquals(new String[] {"E", "D", "COM1", "C", "DP", "B", "A", "COM2", "F", "G"},
|
||||
SevenSegmentDisplay.PIN_NAMES_1DIGIT);
|
||||
Assert.assertArrayEquals(
|
||||
new String[] {"E", "D", "DP", "C", "G", "D4", "B", "D3", "D2", "F", "A", "D1"},
|
||||
SevenSegmentDisplay.PIN_NAMES_4DIGIT);
|
||||
|
||||
for (DisplayType displayType : DisplayType.values()) {
|
||||
SevenSegmentDisplay display = of(displayType);
|
||||
Set<String> names = new HashSet<String>();
|
||||
for (int i = 0; i < display.getControlPointCount(); i++) {
|
||||
String name = display.getControlPointNodeName(i);
|
||||
Assert.assertFalse(displayType + " pin " + i + " is unnamed", name.isEmpty());
|
||||
Assert.assertTrue(displayType + " duplicate node name " + name, names.add(name));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** The 3641AS and the 5641AS differ in size but not in pin function, so one array serves both. */
|
||||
@Test
|
||||
public void bothFourDigitPackagesSharePinNames() {
|
||||
SevenSegmentDisplay small = of(DisplayType.FourDigit_0_36_12Pin);
|
||||
SevenSegmentDisplay large = of(DisplayType.FourDigit_0_56_12Pin);
|
||||
|
||||
for (int i = 0; i < small.getControlPointCount(); i++) {
|
||||
Assert.assertEquals("pin " + i, small.getControlPointNodeName(i),
|
||||
large.getControlPointNodeName(i));
|
||||
}
|
||||
Assert.assertNotEquals(small.getBodyShape().getBounds2D(), large.getBodyShape().getBounds2D());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void dipPackagesCarryTwoRowsAndTheModuleOne() {
|
||||
for (DisplayType displayType : DisplayType.values()) {
|
||||
SevenSegmentDisplay display = of(displayType);
|
||||
int pinCount = display.getControlPointCount();
|
||||
|
||||
if (displayType.isDualRow()) {
|
||||
int perRow = pinCount / 2;
|
||||
MakerBoardTestSupport.assertRow(display, 0, perRow - 1);
|
||||
MakerBoardTestSupport.assertRow(display, perRow, pinCount - 1);
|
||||
Assert.assertEquals(displayType + " row spacing", displayType.getRowSpacingMm(),
|
||||
mm(Math.abs(display.getControlPoint(0).getY()
|
||||
- display.getControlPoint(pinCount - 1).getY())),
|
||||
0.01d);
|
||||
} else {
|
||||
MakerBoardTestSupport.assertRow(display, 0, pinCount - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** A package only works if its pin rows sit within its body. */
|
||||
@Test
|
||||
public void everyPinSitsOnTheBody() {
|
||||
for (DisplayType displayType : DisplayType.values()) {
|
||||
SevenSegmentDisplay display = of(displayType);
|
||||
Rectangle2D body = display.getBodyShape().getBounds2D();
|
||||
for (int i = 0; i < display.getControlPointCount(); i++) {
|
||||
Assert.assertTrue(displayType + " pin " + i + " is off the body",
|
||||
body.contains(display.getControlPoint(i)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void propertiesRoundTripThroughTheirSetters() {
|
||||
SevenSegmentDisplay display = new SevenSegmentDisplay();
|
||||
Assert.assertEquals(DisplayType.SingleDigit_10Pin, display.getDisplayType());
|
||||
Assert.assertEquals(Common.Cathode, display.getCommon());
|
||||
Assert.assertEquals(Punctuation.DecimalPoints, display.getPunctuation());
|
||||
|
||||
for (DisplayType displayType : DisplayType.values()) {
|
||||
Assert.assertEquals(displayType, of(displayType).getDisplayType());
|
||||
}
|
||||
for (Common common : Common.values()) {
|
||||
display.setCommon(common);
|
||||
Assert.assertEquals(common, display.getCommon());
|
||||
}
|
||||
for (Punctuation punctuation : Punctuation.values()) {
|
||||
display.setPunctuation(punctuation);
|
||||
Assert.assertEquals(punctuation, display.getPunctuation());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The module drives the digits itself and brings out no common pins, so polarity is not part of
|
||||
* what you order and is left out of its BOM value; the bare packages carry it.
|
||||
*/
|
||||
@Test
|
||||
public void bomValueNamesThePackageAndWhatVariesWithIt() {
|
||||
SevenSegmentDisplay module = of(DisplayType.TM1637_Module_4Pin);
|
||||
module.setCommon(Common.Anode);
|
||||
module.setPunctuation(Punctuation.Colon);
|
||||
Assert.assertEquals(DisplayType.TM1637_Module_4Pin + ", " + Punctuation.Colon,
|
||||
module.getValueForDisplay());
|
||||
|
||||
SevenSegmentDisplay bare = of(DisplayType.SingleDigit_10Pin);
|
||||
bare.setCommon(Common.Anode);
|
||||
bare.setPunctuation(Punctuation.None);
|
||||
Assert.assertEquals(
|
||||
DisplayType.SingleDigit_10Pin + ", " + Common.Anode + ", " + Punctuation.None,
|
||||
bare.getValueForDisplay());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void drawsCleanlyInEveryState() {
|
||||
for (DisplayType displayType : DisplayType.values()) {
|
||||
for (Punctuation punctuation : Punctuation.values()) {
|
||||
SevenSegmentDisplay display = of(displayType);
|
||||
display.setPunctuation(punctuation);
|
||||
MakerBoardTestSupport.assertDrawsCleanly(display);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+218
@@ -0,0 +1,218 @@
|
||||
/*
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package org.diylc.components.displays;
|
||||
|
||||
import java.awt.geom.Point2D;
|
||||
import java.awt.geom.Rectangle2D;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.diylc.appframework.miscutils.ConfigurationManager;
|
||||
import org.diylc.components.AbstractMakerBoard;
|
||||
import org.diylc.components.maker.MakerBoardTestSupport;
|
||||
import org.junit.Assert;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Geometry tests for the single-LED breakout. Three things compete for a board half an inch by four
|
||||
* tenths: a three-pad column up each short edge, the two mounting holes on the centre line between
|
||||
* them, and the package in the middle. The holes are what everything else is tight against -- 0.3
|
||||
* inch apart across a 0.4 inch height leaves 0.27 mm of board outside each rim and 0.31 mm between
|
||||
* a rim and the package -- so what is asserted here is that none of the three has taken another's
|
||||
* room.
|
||||
*/
|
||||
public class WS2812BBreakoutTest {
|
||||
|
||||
private static final double PX_PER_MM = 200.0d / 25.4d;
|
||||
|
||||
@BeforeClass
|
||||
public static void setUp() {
|
||||
try {
|
||||
ConfigurationManager.getInstance().initialize("diylc");
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
private static double mm(double px) {
|
||||
return px / PX_PER_MM;
|
||||
}
|
||||
|
||||
private static Rectangle2D padRect(WS2812BBreakout breakout, int index) {
|
||||
Point2D pad = breakout.getControlPoint(index);
|
||||
double w = WS2812BBreakout.PAD_WIDTH.convertToPixels();
|
||||
double h = WS2812BBreakout.PAD_LENGTH.convertToPixels();
|
||||
return new Rectangle2D.Double(pad.getX() - w / 2.0, pad.getY() - h / 2.0, w, h);
|
||||
}
|
||||
|
||||
private static Rectangle2D holeRect(WS2812BBreakout breakout, boolean bottom) {
|
||||
Point2D hole = breakout.getHoleCentre(bottom);
|
||||
double r = WS2812BBreakout.MOUNTING_HOLE_SIZE.convertToPixels() / 2.0;
|
||||
return new Rectangle2D.Double(hole.getX() - r, hole.getY() - r, r * 2, r * 2);
|
||||
}
|
||||
|
||||
private static Rectangle2D ledPackage(WS2812BBreakout breakout) {
|
||||
Point2D led = breakout.getLedCentre();
|
||||
double half = AbstractMakerBoard.RGB_LED_SIZE.convertToPixels() / 2.0;
|
||||
return new Rectangle2D.Double(led.getX() - half, led.getY() - half, half * 2, half * 2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void boardIsHalfAnInchByFourTenths() {
|
||||
MakerBoardTestSupport.assertBoardSize(new WS2812BBreakout(), WS2812BBreakout.BOARD_WIDTH,
|
||||
WS2812BBreakout.BOARD_HEIGHT);
|
||||
}
|
||||
|
||||
/** Ground, power and a data line up each long edge, and no two pads sharing a name. */
|
||||
@Test
|
||||
public void eachEdgeCarriesThreeDistinctlyNamedPads() {
|
||||
WS2812BBreakout breakout = new WS2812BBreakout();
|
||||
Assert.assertEquals(6, breakout.getControlPointCount());
|
||||
|
||||
Set<String> names = new HashSet<String>();
|
||||
for (int i = 0; i < breakout.getControlPointCount(); i++) {
|
||||
Assert.assertTrue("duplicate node name " + breakout.getControlPointNodeName(i),
|
||||
names.add(breakout.getControlPointNodeName(i)));
|
||||
}
|
||||
Assert.assertEquals("IN", breakout.getControlPointNodeName(2));
|
||||
Assert.assertEquals("OUT", breakout.getControlPointNodeName(5));
|
||||
}
|
||||
|
||||
/**
|
||||
* The two grounds are one net on the real board, as are the two supplies, so their names differ
|
||||
* only by a disambiguator that comes off again wherever a pad is named to the user.
|
||||
*/
|
||||
@Test
|
||||
public void theNumberedPadsReadAsOneName() {
|
||||
WS2812BBreakout breakout = new WS2812BBreakout();
|
||||
for (int i : new int[] {0, 3}) {
|
||||
Assert.assertEquals("GND",
|
||||
AbstractMakerBoard.getDisplayPinLabel(breakout.getControlPointNodeName(i)));
|
||||
}
|
||||
for (int i : new int[] {1, 4}) {
|
||||
Assert.assertEquals("VIN",
|
||||
AbstractMakerBoard.getDisplayPinLabel(breakout.getControlPointNodeName(i)));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void padColumnsRunUpTheShortEdgesAtHeaderPitch() {
|
||||
WS2812BBreakout breakout = new WS2812BBreakout();
|
||||
MakerBoardTestSupport.assertRow(breakout, 0, 2);
|
||||
MakerBoardTestSupport.assertRow(breakout, 3, 5);
|
||||
|
||||
Rectangle2D board = breakout.getBodyShape().getBounds2D();
|
||||
for (int i = 0; i < 3; i++) {
|
||||
Assert.assertEquals("left pad " + i, board.getX(), padRect(breakout, i).getX(), 0.01d);
|
||||
Assert.assertEquals("right pad " + i, board.getMaxX(), padRect(breakout, 3 + i).getMaxX(),
|
||||
0.01d);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void holesSitOnTheCentreLineASpanApartAstrideTheLed() {
|
||||
WS2812BBreakout breakout = new WS2812BBreakout();
|
||||
Rectangle2D board = breakout.getBodyShape().getBounds2D();
|
||||
Point2D top = breakout.getHoleCentre(false);
|
||||
Point2D bottom = breakout.getHoleCentre(true);
|
||||
|
||||
Assert.assertEquals("span", WS2812BBreakout.MOUNTING_HOLE_SPACING.convertToPixels(),
|
||||
bottom.getY() - top.getY(), 0.01d);
|
||||
Assert.assertEquals("not centred across the board", board.getCenterX(), top.getX(), 0.01d);
|
||||
Assert.assertEquals("not centred along the board", board.getCenterY(),
|
||||
(top.getY() + bottom.getY()) / 2.0, 0.01d);
|
||||
}
|
||||
|
||||
/**
|
||||
* The margin the 0.3 inch span leaves outside each rim. Asserted as the figure it works out to
|
||||
* rather than as mere containment, because a quarter of a millimetre is the whole of it: any
|
||||
* revision to the board height or the hole spacing eats it entirely.
|
||||
*/
|
||||
@Test
|
||||
public void aQuarterMillimetreOfBoardSurvivesOutsideEachHole() {
|
||||
WS2812BBreakout breakout = new WS2812BBreakout();
|
||||
Rectangle2D board = breakout.getBodyShape().getBounds2D();
|
||||
|
||||
Assert.assertEquals("above the top hole", 0.27d,
|
||||
mm(holeRect(breakout, false).getY() - board.getY()), 0.01d);
|
||||
Assert.assertEquals("below the bottom hole", 0.27d,
|
||||
mm(board.getMaxY() - holeRect(breakout, true).getMaxY()), 0.01d);
|
||||
}
|
||||
|
||||
/**
|
||||
* The package has the middle, and the holes and pads are placed to leave it there. The holes are
|
||||
* the pair worth watching: 0.31 mm of board separates a rim from the package, so this fails on
|
||||
* any revision that brings the two together rather than letting them overlap unnoticed.
|
||||
*/
|
||||
@Test
|
||||
public void thePackageClearsTheHolesAndThePads() {
|
||||
WS2812BBreakout breakout = new WS2812BBreakout();
|
||||
Rectangle2D pack = ledPackage(breakout);
|
||||
|
||||
for (boolean bottom : new boolean[] {false, true}) {
|
||||
Assert.assertFalse("the package sits over a mounting hole",
|
||||
pack.intersects(holeRect(breakout, bottom)));
|
||||
}
|
||||
for (int i = 0; i < breakout.getControlPointCount(); i++) {
|
||||
Assert.assertFalse("the package runs under pad " + i,
|
||||
pack.intersects(padRect(breakout, i)));
|
||||
}
|
||||
}
|
||||
|
||||
/** The pads keep well clear of the holes at these figures; this is what says they still do. */
|
||||
@Test
|
||||
public void theHolesClearThePads() {
|
||||
WS2812BBreakout breakout = new WS2812BBreakout();
|
||||
for (boolean bottom : new boolean[] {false, true}) {
|
||||
Rectangle2D hole = holeRect(breakout, bottom);
|
||||
for (int i = 0; i < breakout.getControlPointCount(); i++) {
|
||||
Assert.assertFalse("a mounting hole runs under pad " + i,
|
||||
hole.intersects(padRect(breakout, i)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void everythingStaysOnTheBoard() {
|
||||
WS2812BBreakout breakout = new WS2812BBreakout();
|
||||
Rectangle2D board = breakout.getBodyShape().getBounds2D();
|
||||
|
||||
for (boolean bottom : new boolean[] {false, true}) {
|
||||
Rectangle2D hole = holeRect(breakout, bottom);
|
||||
Assert.assertTrue("a mounting hole breaches the board edge",
|
||||
board.contains(hole.getX(), hole.getY(), hole.getWidth(), hole.getHeight()));
|
||||
}
|
||||
Rectangle2D pack = ledPackage(breakout);
|
||||
Assert.assertTrue("the package hangs off the board",
|
||||
board.contains(pack.getX(), pack.getY(), pack.getWidth(), pack.getHeight()));
|
||||
for (int i = 0; i < breakout.getControlPointCount(); i++) {
|
||||
Rectangle2D pad = padRect(breakout, i);
|
||||
Assert.assertTrue("pad " + i + " runs off the board",
|
||||
board.contains(pad.getX(), pad.getY(), pad.getWidth(), pad.getHeight()));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void drawsCleanlyInEveryState() {
|
||||
MakerBoardTestSupport.assertDrawsCleanly(new WS2812BBreakout());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
/*
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package org.diylc.components.displays;
|
||||
|
||||
import java.awt.geom.Point2D;
|
||||
import java.awt.geom.Rectangle2D;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.diylc.appframework.miscutils.ConfigurationManager;
|
||||
import org.diylc.components.displays.WS2812BRing.RingSize;
|
||||
import org.diylc.components.maker.MakerBoardTestSupport;
|
||||
import org.junit.Assert;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Basic checks for the NeoPixel ring. The pad arrangement itself is guarded by {@link RingSize}'s
|
||||
* constructor, which throws unless each ring's LED gaps sum to its LED count, so what is left to
|
||||
* assert here is that the three sizes carry the pads and diameters they were measured with and
|
||||
* that the body is drawn concentric with them.
|
||||
*/
|
||||
public class WS2812BRingTest {
|
||||
|
||||
private static final double PX_PER_MM = 200.0d / 25.4d;
|
||||
|
||||
@BeforeClass
|
||||
public static void setUp() {
|
||||
try {
|
||||
ConfigurationManager.getInstance().initialize("diylc");
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
private static double mm(double px) {
|
||||
return px / PX_PER_MM;
|
||||
}
|
||||
|
||||
private static WS2812BRing of(RingSize ringSize) {
|
||||
WS2812BRing ring = new WS2812BRing();
|
||||
ring.setRingSize(ringSize);
|
||||
return ring;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bodyIsTheOuterDiameterOfEachRing() {
|
||||
for (RingSize ringSize : RingSize.values()) {
|
||||
Rectangle2D bounds = of(ringSize).getBodyShape().getBounds2D();
|
||||
Assert.assertEquals(ringSize + " width", ringSize.getOuterDiameterMm(), mm(bounds.getWidth()),
|
||||
0.01d);
|
||||
Assert.assertEquals(ringSize + " height", ringSize.getOuterDiameterMm(),
|
||||
mm(bounds.getHeight()), 0.01d);
|
||||
}
|
||||
}
|
||||
|
||||
/** Four pads on the 12-LED ring, six on the two larger ones, and no two sharing a name. */
|
||||
@Test
|
||||
public void padCountFollowsTheRingSize() {
|
||||
Assert.assertEquals(4, of(RingSize._12_LED).getControlPointCount());
|
||||
Assert.assertEquals(6, of(RingSize._16_LED).getControlPointCount());
|
||||
Assert.assertEquals(6, of(RingSize._24_LED).getControlPointCount());
|
||||
|
||||
for (RingSize ringSize : RingSize.values()) {
|
||||
WS2812BRing ring = of(ringSize);
|
||||
Set<String> names = new HashSet<String>();
|
||||
for (int i = 0; i < ring.getControlPointCount(); i++) {
|
||||
String name = ring.getControlPointNodeName(i);
|
||||
Assert.assertNotNull(ringSize + " pad " + i, name);
|
||||
Assert.assertFalse(ringSize + " pad " + i + " is unnamed", name.isEmpty());
|
||||
Assert.assertTrue(ringSize + " duplicate node name " + name, names.add(name));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Every pad sits one inset in from the rim, on a circle of its own rather than on the one the
|
||||
* LEDs occupy. This also says the body is drawn concentric with the pads: the outline is placed
|
||||
* by working backwards from the first pad, so a disagreement there would leave the ring drawn
|
||||
* away from its own control points and show up as an off-centre radius here.
|
||||
*/
|
||||
@Test
|
||||
public void everyPadSitsOnItsOwnCircleInsideTheRim() {
|
||||
for (RingSize ringSize : RingSize.values()) {
|
||||
WS2812BRing ring = of(ringSize);
|
||||
Rectangle2D bounds = ring.getBodyShape().getBounds2D();
|
||||
Point2D centre = new Point2D.Double(bounds.getCenterX(), bounds.getCenterY());
|
||||
double expected = ringSize.getOuterDiameterMm() / 2.0
|
||||
- mm(WS2812BRing.PAD_EDGE_INSET.convertToPixels());
|
||||
|
||||
for (int i = 0; i < ring.getControlPointCount(); i++) {
|
||||
Assert.assertEquals(ringSize + " pad " + i + " radius", expected,
|
||||
mm(centre.distance(ring.getControlPoint(i))), 0.01d);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ringSizeRoundTripsThroughTheSetter() {
|
||||
Assert.assertEquals(RingSize._16_LED, new WS2812BRing().getRingSize());
|
||||
for (RingSize ringSize : RingSize.values()) {
|
||||
WS2812BRing ring = of(ringSize);
|
||||
Assert.assertEquals(ringSize, ring.getRingSize());
|
||||
Assert.assertEquals(ringSize.toString(), ring.getValueForDisplay());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void drawsCleanlyInEveryState() {
|
||||
for (RingSize ringSize : RingSize.values()) {
|
||||
MakerBoardTestSupport.assertDrawsCleanly(of(ringSize));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -84,6 +84,7 @@ public class MakerComponentsTest {
|
||||
WS2812BStrip.class,
|
||||
WS2812BJewel.class,
|
||||
WS2812BPanel.class,
|
||||
WS2812BBreakout.class,
|
||||
Nokia5110LCD.class,
|
||||
|
||||
// Electro-Mechanical
|
||||
|
||||
@@ -979,6 +979,40 @@ for the lit colour. Category `Displays & Outputs` even though the neighbours in
|
||||
hold its own dimensions. Should a second panel ever be sourced, re-introducing the enum is the
|
||||
same work either way.
|
||||
|
||||
- **NeoPixel Breakout** as its own class — **done**, as `WS2812BBreakout`, on the maintainer's
|
||||
figures: a **0.5 inch wide by 0.4 inch high** board, one 5050 package in the middle, two 2 mm
|
||||
mounting holes **0.3 inch apart** on the centre line, and three connections at each end —
|
||||
`GND`, `VIN`, `IN` on one and `GND`, `VIN`, `OUT` on the other. The real board brings those out
|
||||
through a three-way connector at each end, and they are drawn as plain pads instead, which is the
|
||||
same call §3.2 recorded for fine-pitch connectors generally: at this size a connector body would
|
||||
cover most of the board and hide the LED, and what a layout needs from it is where the three
|
||||
wires land.
|
||||
|
||||
So the board lies on its side — a three-pad column up each short edge, the holes spaced across
|
||||
the height between them. **The figures were corrected once during the work and the first set did
|
||||
not fit**, which is worth recording because the failure was not a rounding: the board was first
|
||||
given as 0.5 inch long and 0.4 inch tall with the holes 0.4 inch apart, and 0.4 inch of spacing
|
||||
does not fit a 0.4 inch height — the two centres land exactly on the edges. Read the other way,
|
||||
with the spacing along the 0.5 inch axis, the holes then wanted the same place as the pad columns.
|
||||
The 0.3 inch spacing removes the conflict entirely and puts the part back the way round the brief
|
||||
described it. The lesson is only that a hole pattern and an outline have to be checked against
|
||||
each other before either is drawn, since a spacing equal to the dimension it runs along reads
|
||||
perfectly well in prose.
|
||||
|
||||
What is left is tight in two places and comfortable elsewhere. Each hole keeps **0.27 mm of board
|
||||
outside its rim** and **0.31 mm between its rim and the package** — about 2 px each at 1:1, and
|
||||
the tightest clearances anywhere in the package, against the ring's 0.5745 mm from pad to rim and
|
||||
the full millimetre the stick's holes keep from its top edge.
|
||||
`aQuarterMillimetreOfBoardSurvivesOutsideEachHole` asserts the first as the figure rather than as
|
||||
mere containment, and `thePackageClearsTheHolesAndThePads` guards the second: any revision to the
|
||||
height or the spacing consumes both. The pad columns are the comfortable ones, 2.35 mm clear of
|
||||
the package and 4.88 mm from the nearest hole. The face carries no silkscreen — the room there is
|
||||
between a pad and the package rather than beside a pad, so a pin name has nowhere to sit, as on
|
||||
the stick.
|
||||
|
||||
Like the Jewel, the Nokia and the panel it is a single part with no variant enum, so its figures
|
||||
are plain constants on the class.
|
||||
|
||||
### 6.8 On-screen identification — the glass prints its own spec — **done**
|
||||
|
||||
**Done.** The four displays that have a lit area — `OLEDDisplay`, `TFTDisplay`,
|
||||
@@ -1158,8 +1192,8 @@ part name, then the variant list).
|
||||
6. **§7** tests alongside each of the above, not after.
|
||||
7. **§8** the `update.xml` block, last.
|
||||
|
||||
Twelve palette entries: the seven existing, plus the strip, the bar graph, the Jewel, the Nokia and
|
||||
now the panel. The `update.xml` 6.7.0 block carries all twelve,
|
||||
Thirteen palette entries: the seven existing, plus the strip, the bar graph, the Jewel, the Nokia,
|
||||
the panel and the breakout. The `update.xml` 6.7.0 block carries all thirteen,
|
||||
alphabetically as the 6.5.0 block lists its boards. Two of the entries it already had were stale
|
||||
rather than missing, and both were stale because a class had outgrown them: the matrix was still
|
||||
described as an 8x8 and the TFT as a touch screen with an SD slot, which is one variant of four
|
||||
@@ -1410,19 +1444,25 @@ gate the displays.
|
||||
`_` so the silkscreen still reads `G`.
|
||||
|
||||
This also closes §3.2 item 8. The pads no longer share the LED arc: they sit on their own radius,
|
||||
`outerR` less a 1.4 mm `PAD_EDGE_INSET`, drawn 1.6 × 1.3 mm with a 0.7 mm hole. A ring pad is far
|
||||
smaller than a 0.1" header pad, so `AbstractMakerBoard` grew three
|
||||
`getSolderPad{Width,Length,HoleSize}` hooks. That is a deliberate exception to the usual
|
||||
preference for tuning the shared appearance constants globally, taken because only the Ring wants
|
||||
these values: the defaults reproduce the previous hardcoded 22 / 16 / 7 px exactly, confirmed by
|
||||
byte-comparing every maker render before and after and finding **only `WS2812BRing` changed**.
|
||||
`outerR` less a 1.4 mm `PAD_EDGE_INSET`. They are **round**, drawn by the shared
|
||||
`drawPcbSolderPads` off `AbstractMakerBoard`'s own `PAD_SIZE` and `HOLE_SIZE` — 1.651 mm
|
||||
(0.065", exactly 13 px) across with a 0.7 mm drill — so no per-board pad footprint exists and
|
||||
the standing preference for tuning the shared appearance constants globally is left intact.
|
||||
|
||||
Because the pad is an axis-aligned rectangle, what has to clear the rim is its corner diagonal
|
||||
(1.031 mm), not its half-length — leaving 0.37 mm of board outside the furthest corner, about
|
||||
3 px at 1:1. Both the full-size renders and a pixel scan appeared to show pads breaching the rim;
|
||||
both were misreading the board's own anti-aliased edge, which registers at every angle rather
|
||||
than only at the pads. Magnifying the tightest pad on each variant 12× showed unbroken board
|
||||
between every pad and the rim.
|
||||
What has to clear the rim is therefore the pad radius, and since both it and the inset are
|
||||
constants the clearance is the same on every ring: **0.5745 mm of board outside each pad**, about
|
||||
4.5 px at 1:1. It does not vary with ring size, which is worth knowing before either figure is
|
||||
revised — one change moves all three variants at once.
|
||||
|
||||
*This paragraph was corrected after the fact.* It previously described rectangular 1.6 × 1.3 mm
|
||||
pads reached through three `getSolderPad{Width,Length,HoleSize}` hooks on `AbstractMakerBoard`,
|
||||
with a 0.37 mm corner-diagonal clearance arrived at by magnifying renders. None of that is in the
|
||||
tree: the hooks do not exist and the pads are round. §3.3 records why — `drawSolderPads` and its
|
||||
three size hooks were removed and their six callers moved onto `drawPcbSolderPads` — so it is
|
||||
this paragraph that was left behind rather than the decision that is in doubt. The same removal
|
||||
left two orphaned Javadoc blocks above `buildLedGradient`, since deleted. The figures above are read out of the
|
||||
code rather than recomputed by hand; no render was re-checked for this correction, so the visual
|
||||
question the old text claimed to settle is open again, if only trivially at 4.5 px of margin.
|
||||
10. **`SevenSegmentDisplay`'s pin arrays** — **decided: the maintainer confirms they are correct.**
|
||||
Three questions closed together. The arrays are right as they stand; the two bare four-digit
|
||||
packages **share a pinout**; and each variant is now pinned to a named part — **5161AS** for the
|
||||
|
||||
Reference in New Issue
Block a user