[Nanoleaf] Visualize layout (#13552)
* Visualize Nanoleaf layout * Only calculate image if channel is linked * White background image * Render more shapes Signed-off-by: Jørgen Austvik <jaustvik@acm.org>
This commit is contained in:
@@ -58,6 +58,7 @@ public class NanoleafBindingConstants {
|
||||
public static final String CHANNEL_SWIPE_EVENT_DOWN = "DOWN";
|
||||
public static final String CHANNEL_SWIPE_EVENT_LEFT = "LEFT";
|
||||
public static final String CHANNEL_SWIPE_EVENT_RIGHT = "RIGHT";
|
||||
public static final String CHANNEL_LAYOUT = "layout";
|
||||
|
||||
// List of light panel channels
|
||||
public static final String CHANNEL_PANEL_COLOR = "color";
|
||||
@@ -78,7 +79,7 @@ public class NanoleafBindingConstants {
|
||||
public static final String API_MIN_FW_VER_CANVAS = "1.1.0";
|
||||
public static final String MODEL_ID_LIGHTPANELS = "NL22";
|
||||
|
||||
public static final List<String> MODELS_WITH_TOUCHSUPPORT = Arrays.asList("NL29", "NL42");
|
||||
public static final List<String> MODELS_WITH_TOUCHSUPPORT = Arrays.asList("NL29", "NL42", "NL47", "NL48", "NL52");
|
||||
public static final String DEVICE_TYPE_LIGHTPANELS = "lightPanels";
|
||||
public static final String DEVICE_TYPE_TOUCHSUPPORT = "canvas"; // we need to keep this enum for backward
|
||||
// compatibility even though not only canvas type
|
||||
@@ -93,4 +94,8 @@ public class NanoleafBindingConstants {
|
||||
|
||||
// Color channels increase/decrease brightness step size
|
||||
public static final int BRIGHTNESS_STEP_SIZE = 5;
|
||||
|
||||
// Layout rendering
|
||||
public static final int LAYOUT_LIGHT_RADIUS = 8;
|
||||
public static final int LAYOUT_BORDER_WIDTH = 30;
|
||||
}
|
||||
|
||||
@@ -154,11 +154,7 @@ public class OpenAPIUtils {
|
||||
|
||||
for (int i = 0; i < currentVer.length; ++i) {
|
||||
if (currentVer[i] != requiredVer[i]) {
|
||||
if (currentVer[i] > requiredVer[i]) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
return (currentVer[i] > requiredVer[i]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ package org.openhab.binding.nanoleaf.internal.handler;
|
||||
|
||||
import static org.openhab.binding.nanoleaf.internal.NanoleafBindingConstants.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Collection;
|
||||
@@ -43,6 +44,7 @@ import org.openhab.binding.nanoleaf.internal.OpenAPIUtils;
|
||||
import org.openhab.binding.nanoleaf.internal.commanddescription.NanoleafCommandDescriptionProvider;
|
||||
import org.openhab.binding.nanoleaf.internal.config.NanoleafControllerConfig;
|
||||
import org.openhab.binding.nanoleaf.internal.discovery.NanoleafPanelsDiscoveryService;
|
||||
import org.openhab.binding.nanoleaf.internal.layout.NanoleafLayout;
|
||||
import org.openhab.binding.nanoleaf.internal.model.AuthToken;
|
||||
import org.openhab.binding.nanoleaf.internal.model.BooleanState;
|
||||
import org.openhab.binding.nanoleaf.internal.model.Brightness;
|
||||
@@ -65,6 +67,7 @@ import org.openhab.core.library.types.HSBType;
|
||||
import org.openhab.core.library.types.IncreaseDecreaseType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.PercentType;
|
||||
import org.openhab.core.library.types.RawType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
@@ -72,6 +75,7 @@ import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
import org.openhab.core.thing.binding.BaseBridgeHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandlerCallback;
|
||||
import org.openhab.core.thing.binding.ThingHandlerService;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
@@ -103,6 +107,7 @@ public class NanoleafControllerHandler extends BaseBridgeHandler {
|
||||
private @Nullable HttpClient httpClientSSETouchEvent;
|
||||
private @Nullable Request sseTouchjobRequest;
|
||||
private List<NanoleafControllerListener> controllerListeners = new CopyOnWriteArrayList<NanoleafControllerListener>();
|
||||
private PanelLayout previousPanelLayout = new PanelLayout();
|
||||
|
||||
private @NonNullByDefault({}) ScheduledFuture<?> pairingJob;
|
||||
private @NonNullByDefault({}) ScheduledFuture<?> updateJob;
|
||||
@@ -664,6 +669,7 @@ public class NanoleafControllerHandler extends BaseBridgeHandler {
|
||||
|
||||
updateProperties();
|
||||
updateConfiguration();
|
||||
updateLayout(controllerInfo.getPanelLayout());
|
||||
|
||||
for (NanoleafControllerListener controllerListener : controllerListeners) {
|
||||
controllerListener.onControllerInfoFetched(getThing().getUID(), controllerInfo);
|
||||
@@ -705,6 +711,33 @@ public class NanoleafControllerHandler extends BaseBridgeHandler {
|
||||
}
|
||||
}
|
||||
|
||||
private void updateLayout(PanelLayout panelLayout) {
|
||||
ChannelUID layoutChannel = new ChannelUID(getThing().getUID(), CHANNEL_LAYOUT);
|
||||
ThingHandlerCallback callback = getCallback();
|
||||
if (callback != null) {
|
||||
if (!callback.isChannelLinked(layoutChannel)) {
|
||||
// Don't generate image unless it is used
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (previousPanelLayout.equals(panelLayout)) {
|
||||
logger.trace("Not rendering panel layout as it is the same as previous rendered panel layout");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
byte[] bytes = NanoleafLayout.render(panelLayout);
|
||||
if (bytes.length > 0) {
|
||||
updateState(CHANNEL_LAYOUT, new RawType(bytes, "image/png"));
|
||||
}
|
||||
|
||||
previousPanelLayout = panelLayout;
|
||||
} catch (IOException ioex) {
|
||||
logger.warn("Failed to create layout image", ioex);
|
||||
}
|
||||
}
|
||||
|
||||
private ControllerInfo receiveControllerInfo() throws NanoleafException, NanoleafUnauthorizedException {
|
||||
ContentResponse controllerlInfoJSON = OpenAPIUtils.sendOpenAPIRequest(OpenAPIUtils.requestBuilder(httpClient,
|
||||
getControllerConfig(), API_GET_CONTROLLER_INFO, HttpMethod.GET));
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.nanoleaf.internal.layout;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Differentiates how shapes must be drawn
|
||||
*
|
||||
* @author Jørgen Austvik - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public enum DrawingAlgorithm {
|
||||
NONE,
|
||||
SQUARE,
|
||||
TRIANGLE,
|
||||
HEXAGON,
|
||||
CORNER,
|
||||
LINE;
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
|
||||
package org.openhab.binding.nanoleaf.internal.layout;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.nanoleaf.internal.NanoleafBindingConstants;
|
||||
import org.openhab.binding.nanoleaf.internal.layout.shape.Shape;
|
||||
import org.openhab.binding.nanoleaf.internal.layout.shape.ShapeFactory;
|
||||
import org.openhab.binding.nanoleaf.internal.model.GlobalOrientation;
|
||||
import org.openhab.binding.nanoleaf.internal.model.Layout;
|
||||
import org.openhab.binding.nanoleaf.internal.model.PanelLayout;
|
||||
import org.openhab.binding.nanoleaf.internal.model.PositionDatum;
|
||||
|
||||
/**
|
||||
* Renders the Nanoleaf layout to an image.
|
||||
*
|
||||
* @author Jørgen Austvik - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class NanoleafLayout {
|
||||
|
||||
private static final Color COLOR_BACKGROUND = Color.WHITE;
|
||||
private static final Color COLOR_PANEL = Color.BLACK;
|
||||
private static final Color COLOR_SIDE = Color.GRAY;
|
||||
private static final Color COLOR_TEXT = Color.BLACK;
|
||||
|
||||
public static byte[] render(PanelLayout panelLayout) throws IOException {
|
||||
double rotationRadians = 0;
|
||||
GlobalOrientation globalOrientation = panelLayout.getGlobalOrientation();
|
||||
if (globalOrientation != null) {
|
||||
rotationRadians = calculateRotationRadians(globalOrientation);
|
||||
}
|
||||
|
||||
Layout layout = panelLayout.getLayout();
|
||||
if (layout == null) {
|
||||
return new byte[] {};
|
||||
}
|
||||
|
||||
List<PositionDatum> panels = layout.getPositionData();
|
||||
if (panels == null) {
|
||||
return new byte[] {};
|
||||
}
|
||||
|
||||
Point2D size[] = findSize(panels, rotationRadians);
|
||||
final Point2D min = size[0];
|
||||
final Point2D max = size[1];
|
||||
Point2D prev = null;
|
||||
Point2D first = null;
|
||||
|
||||
int sideCounter = 0;
|
||||
BufferedImage image = new BufferedImage(
|
||||
(max.getX() - min.getX()) + 2 * NanoleafBindingConstants.LAYOUT_BORDER_WIDTH,
|
||||
(max.getY() - min.getY()) + 2 * NanoleafBindingConstants.LAYOUT_BORDER_WIDTH,
|
||||
BufferedImage.TYPE_INT_RGB);
|
||||
Graphics2D g2 = image.createGraphics();
|
||||
|
||||
g2.setBackground(COLOR_BACKGROUND);
|
||||
g2.clearRect(0, 0, image.getWidth(), image.getHeight());
|
||||
|
||||
for (PositionDatum panel : panels) {
|
||||
final ShapeType shapeType = ShapeType.valueOf(panel.getShapeType());
|
||||
|
||||
Shape shape = ShapeFactory.CreateShape(shapeType, panel);
|
||||
List<Point2D> outline = toPictureLayout(shape.generateOutline(), image.getHeight(), min, rotationRadians);
|
||||
for (int i = 0; i < outline.size(); i++) {
|
||||
g2.setColor(COLOR_SIDE);
|
||||
Point2D pos = outline.get(i);
|
||||
Point2D nextPos = outline.get((i + 1) % outline.size());
|
||||
g2.drawLine(pos.getX(), pos.getY(), nextPos.getX(), nextPos.getY());
|
||||
}
|
||||
|
||||
for (int i = 0; i < outline.size(); i++) {
|
||||
Point2D pos = outline.get(i);
|
||||
g2.setColor(COLOR_PANEL);
|
||||
g2.fillOval(pos.getX() - NanoleafBindingConstants.LAYOUT_LIGHT_RADIUS / 2,
|
||||
pos.getY() - NanoleafBindingConstants.LAYOUT_LIGHT_RADIUS / 2,
|
||||
NanoleafBindingConstants.LAYOUT_LIGHT_RADIUS, NanoleafBindingConstants.LAYOUT_LIGHT_RADIUS);
|
||||
}
|
||||
|
||||
Point2D current = toPictureLayout(new Point2D(panel.getPosX(), panel.getPosY()), image.getHeight(), min,
|
||||
rotationRadians);
|
||||
if (sideCounter == 0) {
|
||||
first = current;
|
||||
}
|
||||
|
||||
g2.setColor(COLOR_SIDE);
|
||||
final int expectedSides = shapeType.getNumSides();
|
||||
if (shapeType.getDrawingAlgorithm() == DrawingAlgorithm.CORNER) {
|
||||
// Special handling of Elements Hexagon Corners, where we get 6 corners instead of 1 shape. They seem to
|
||||
// come after each other in the JSON, so this algorithm connects them based on the number of sides the
|
||||
// shape is expected to have.
|
||||
if (sideCounter > 0 && sideCounter != expectedSides && prev != null) {
|
||||
g2.drawLine(prev.getX(), prev.getY(), current.getX(), current.getY());
|
||||
}
|
||||
|
||||
sideCounter++;
|
||||
|
||||
if (sideCounter == expectedSides && first != null) {
|
||||
g2.drawLine(current.getX(), current.getY(), first.getX(), first.getY());
|
||||
sideCounter = 0;
|
||||
}
|
||||
} else {
|
||||
sideCounter = 0;
|
||||
}
|
||||
|
||||
prev = current;
|
||||
|
||||
g2.setColor(COLOR_TEXT);
|
||||
Point2D textPos = shape.labelPosition(g2, outline);
|
||||
g2.drawString(Integer.toString(panel.getPanelId()), textPos.getX(), textPos.getY());
|
||||
}
|
||||
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
ImageIO.write(image, "png", out);
|
||||
return out.toByteArray();
|
||||
}
|
||||
|
||||
private static double calculateRotationRadians(GlobalOrientation globalOrientation) {
|
||||
Integer maxObj = globalOrientation.getMax();
|
||||
int maxValue = maxObj == null ? 360 : (int) maxObj;
|
||||
int value = globalOrientation.getValue(); // 0 - 360 measured counter clockwise.
|
||||
return ((double) (maxValue - value)) * (Math.PI / 180);
|
||||
}
|
||||
|
||||
private static Point2D[] findSize(Collection<PositionDatum> panels, double rotationRadians) {
|
||||
int maxX = 0;
|
||||
int maxY = 0;
|
||||
int minX = 0;
|
||||
int minY = 0;
|
||||
|
||||
for (PositionDatum panel : panels) {
|
||||
ShapeType shapeType = ShapeType.valueOf(panel.getShapeType());
|
||||
Shape shape = ShapeFactory.CreateShape(shapeType, panel);
|
||||
for (Point2D point : shape.generateOutline()) {
|
||||
var rotated = point.rotate(rotationRadians);
|
||||
maxX = Math.max(rotated.getX(), maxX);
|
||||
maxY = Math.max(rotated.getY(), maxY);
|
||||
minX = Math.min(rotated.getX(), minX);
|
||||
minY = Math.min(rotated.getY(), minY);
|
||||
}
|
||||
}
|
||||
|
||||
return new Point2D[] { new Point2D(minX, minY), new Point2D(maxX, maxY) };
|
||||
}
|
||||
|
||||
private static Point2D toPictureLayout(Point2D original, int imageHeight, Point2D min, double rotationRadians) {
|
||||
Point2D rotated = original.rotate(rotationRadians);
|
||||
Point2D translated = new Point2D(NanoleafBindingConstants.LAYOUT_BORDER_WIDTH + rotated.getX() - min.getX(),
|
||||
imageHeight - NanoleafBindingConstants.LAYOUT_BORDER_WIDTH - rotated.getY() + min.getY());
|
||||
return translated;
|
||||
}
|
||||
|
||||
private static List<Point2D> toPictureLayout(List<Point2D> originals, int imageHeight, Point2D min,
|
||||
double rotationRadians) {
|
||||
List<Point2D> result = new ArrayList<Point2D>(originals.size());
|
||||
for (Point2D original : originals) {
|
||||
result.add(toPictureLayout(original, imageHeight, min, rotationRadians));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
|
||||
package org.openhab.binding.nanoleaf.internal.layout;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Coordinate in 2D space.
|
||||
*
|
||||
* @author Jørgen Austvik - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class Point2D {
|
||||
private final int x;
|
||||
private final int y;
|
||||
|
||||
public Point2D(int x, int y) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
public int getX() {
|
||||
return x;
|
||||
}
|
||||
|
||||
public int getY() {
|
||||
return y;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rotates the point a given amount of radians.
|
||||
*
|
||||
* @param radians The amount to rotate the point
|
||||
* @return A new point which is rotated
|
||||
*/
|
||||
public Point2D rotate(double radians) {
|
||||
double sinAngle = Math.sin(radians);
|
||||
double cosAngle = Math.cos(radians);
|
||||
|
||||
int newX = (int) (cosAngle * x - sinAngle * y);
|
||||
int newY = (int) (sinAngle * x + cosAngle * y);
|
||||
return new Point2D(newX, newY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the point in x and y direction.
|
||||
*
|
||||
* @param moveX Amount to move in x direction
|
||||
* @param moveY Amount to move in y direction
|
||||
* @return
|
||||
*/
|
||||
public Point2D move(int moveX, int moveY) {
|
||||
return new Point2D(getX() + moveX, getY() + moveY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the point in x and y direction,.
|
||||
*
|
||||
* @param offset Offset to move
|
||||
* @return
|
||||
*/
|
||||
public Point2D move(Point2D offset) {
|
||||
return move(offset.getX(), offset.getY());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("x:%d, y:%d", x, y);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
|
||||
package org.openhab.binding.nanoleaf.internal.layout;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Information about the different Nanoleaf shapes.
|
||||
*
|
||||
* @author Jørgen Austvik - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public enum ShapeType {
|
||||
// side lengths are taken from https://forum.nanoleaf.me/docs chapter 3.3
|
||||
UNKNOWN("Unknown", -1, 0, 0, DrawingAlgorithm.NONE),
|
||||
TRIANGLE("Triangle", 0, 150, 3, DrawingAlgorithm.TRIANGLE),
|
||||
RHYTHM("Rhythm", 1, 0, 1, DrawingAlgorithm.NONE),
|
||||
SQUARE("Square", 2, 100, 0, DrawingAlgorithm.SQUARE),
|
||||
CONTROL_SQUARE_MASTER("Control Square Master", 3, 100, 0, DrawingAlgorithm.SQUARE),
|
||||
CONTROL_SQUARE_PASSIVE("Control Square Passive", 4, 100, 0, DrawingAlgorithm.SQUARE),
|
||||
SHAPES_HEXAGON("Hexagon (Shapes)", 7, 67, 6, DrawingAlgorithm.HEXAGON),
|
||||
SHAPES_TRIANGLE("Triangle (Shapes)", 8, 134, 3, DrawingAlgorithm.TRIANGLE),
|
||||
SHAPES_MINI_TRIANGLE("Mini Triangle (Shapes)", 9, 67, 3, DrawingAlgorithm.TRIANGLE),
|
||||
SHAPES_CONTROLLER("Controller (Shapes)", 12, 0, 0, DrawingAlgorithm.NONE),
|
||||
ELEMENTS_HEXAGON("Elements Hexagon", 14, 134, 6, DrawingAlgorithm.HEXAGON),
|
||||
ELEMENTS_HEXAGON_CORNER("Elements Hexagon - Corner", 15, 33.5 / 58, 6, DrawingAlgorithm.CORNER),
|
||||
LINES_CONNECTOR("Lines Connector", 16, 11, 1, DrawingAlgorithm.LINE),
|
||||
LIGHT_LINES("Light Lines", 17, 154, 1, DrawingAlgorithm.LINE),
|
||||
LINES_LINES_SINGLE("Light Lines - Single Sone", 18, 77, 1, DrawingAlgorithm.LINE),
|
||||
CONTROLLER_CAP("Controller Cap", 19, 11, 0, DrawingAlgorithm.NONE),
|
||||
POWER_CONNECTOR("Power Connector", 20, 11, 0, DrawingAlgorithm.NONE);
|
||||
|
||||
private final String name;
|
||||
private final int id;
|
||||
private final double sideLength;
|
||||
private final int numSides;
|
||||
private final DrawingAlgorithm drawingAlgorithm;
|
||||
|
||||
ShapeType(String name, int id, double sideLenght, int numSides, DrawingAlgorithm drawingAlgorithm) {
|
||||
this.name = name;
|
||||
this.id = id;
|
||||
this.sideLength = sideLenght;
|
||||
this.numSides = numSides;
|
||||
this.drawingAlgorithm = drawingAlgorithm;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public double getSideLength() {
|
||||
return sideLength;
|
||||
}
|
||||
|
||||
public int getNumSides() {
|
||||
return numSides;
|
||||
}
|
||||
|
||||
public DrawingAlgorithm getDrawingAlgorithm() {
|
||||
return drawingAlgorithm;
|
||||
}
|
||||
|
||||
public static ShapeType valueOf(int id) {
|
||||
for (ShapeType shapeType : values()) {
|
||||
if (shapeType.getId() == id) {
|
||||
return shapeType;
|
||||
}
|
||||
}
|
||||
|
||||
return ShapeType.UNKNOWN;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.nanoleaf.internal.layout.shape;
|
||||
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.geom.Rectangle2D;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.nanoleaf.internal.layout.Point2D;
|
||||
import org.openhab.binding.nanoleaf.internal.layout.ShapeType;
|
||||
|
||||
/**
|
||||
* A hexagon shape.
|
||||
*
|
||||
* @author Jørgen Austvik - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class Hexagon extends Shape {
|
||||
public Hexagon(ShapeType shapeType, int panelId, Point2D position, int orientation) {
|
||||
super(shapeType, panelId, position, orientation);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Point2D> generateOutline() {
|
||||
Point2D v1 = new Point2D((int) getShapeType().getSideLength(), 0);
|
||||
Point2D v2 = v1.rotate((1.0 / 3.0) * Math.PI);
|
||||
Point2D v3 = v1.rotate((2.0 / 3.0) * Math.PI);
|
||||
Point2D v4 = v1.rotate((3.0 / 3.0) * Math.PI);
|
||||
Point2D v5 = v1.rotate((4.0 / 3.0) * Math.PI);
|
||||
Point2D v6 = v1.rotate((5.0 / 3.0) * Math.PI);
|
||||
return Arrays.asList(v1.move(getPosition()), v2.move(getPosition()), v3.move(getPosition()),
|
||||
v4.move(getPosition()), v5.move(getPosition()), v6.move(getPosition()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Point2D labelPosition(Graphics2D graphics, List<Point2D> outline) {
|
||||
Point2D[] bounds = findBounds(outline);
|
||||
int midX = bounds[0].getX() + (bounds[1].getX() - bounds[0].getX()) / 2;
|
||||
int midY = bounds[0].getY() + (bounds[1].getY() - bounds[0].getY()) / 2;
|
||||
|
||||
Rectangle2D rect = graphics.getFontMetrics().getStringBounds(Integer.toString(getPanelId()), graphics);
|
||||
return new Point2D(midX - (int) (rect.getWidth() / 2), midY - (int) (rect.getHeight() / 2));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.nanoleaf.internal.layout.shape;
|
||||
|
||||
import java.awt.Graphics2D;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.nanoleaf.internal.layout.Point2D;
|
||||
import org.openhab.binding.nanoleaf.internal.layout.ShapeType;
|
||||
|
||||
/**
|
||||
* A shape without any area.
|
||||
*
|
||||
* @author Jørgen Austvik - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class Point extends Shape {
|
||||
public Point(ShapeType shapeType, int panelId, Point2D position, int orientation) {
|
||||
super(shapeType, panelId, position, orientation);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Point2D> generateOutline() {
|
||||
return Arrays.asList(getPosition());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Point2D labelPosition(Graphics2D graphics, List<Point2D> outline) {
|
||||
return outline.get(0);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.nanoleaf.internal.layout.shape;
|
||||
|
||||
import java.awt.Graphics2D;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.nanoleaf.internal.layout.Point2D;
|
||||
import org.openhab.binding.nanoleaf.internal.layout.ShapeType;
|
||||
|
||||
/**
|
||||
* Shape that can be drawn.
|
||||
*
|
||||
* @author Jørgen Austvik - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public abstract class Shape {
|
||||
private final ShapeType shapeType;
|
||||
private final int panelId;
|
||||
private final Point2D position;
|
||||
private final int orientation;
|
||||
|
||||
public Shape(ShapeType shapeType, int panelId, Point2D position, int orientation) {
|
||||
this.shapeType = shapeType;
|
||||
this.panelId = panelId;
|
||||
this.position = position;
|
||||
this.orientation = orientation;
|
||||
}
|
||||
|
||||
public int getPanelId() {
|
||||
return panelId;
|
||||
};
|
||||
|
||||
public Point2D getPosition() {
|
||||
return position;
|
||||
}
|
||||
|
||||
public int getOrientation() {
|
||||
return orientation;
|
||||
};
|
||||
|
||||
public ShapeType getShapeType() {
|
||||
return shapeType;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The opposite points of the minimum bounding rectangle around this shape.
|
||||
*/
|
||||
public Point2D[] findBounds(List<Point2D> outline) {
|
||||
int minX = Integer.MAX_VALUE;
|
||||
int minY = Integer.MAX_VALUE;
|
||||
int maxX = Integer.MIN_VALUE;
|
||||
int maxY = Integer.MIN_VALUE;
|
||||
|
||||
for (Point2D point : outline) {
|
||||
maxX = Math.max(point.getX(), maxX);
|
||||
maxY = Math.max(point.getY(), maxY);
|
||||
minX = Math.min(point.getX(), minX);
|
||||
minY = Math.min(point.getY(), minY);
|
||||
}
|
||||
|
||||
return new Point2D[] { new Point2D(minX, minY), new Point2D(maxX, maxY) };
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The points that make up this shape.
|
||||
*/
|
||||
public abstract List<Point2D> generateOutline();
|
||||
|
||||
/**
|
||||
* @return The position where the label of the shape should be placed
|
||||
*/
|
||||
public abstract Point2D labelPosition(Graphics2D graphics, List<Point2D> outline);
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.nanoleaf.internal.layout.shape;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.nanoleaf.internal.layout.Point2D;
|
||||
import org.openhab.binding.nanoleaf.internal.layout.ShapeType;
|
||||
import org.openhab.binding.nanoleaf.internal.model.PositionDatum;
|
||||
|
||||
/**
|
||||
* Create the correct chape for a given shape type.
|
||||
*
|
||||
* @author Jørgen Austvik - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ShapeFactory {
|
||||
|
||||
public static Shape CreateShape(ShapeType shapeType, PositionDatum positionDatum) {
|
||||
Point2D pos = new Point2D(positionDatum.getPosX(), positionDatum.getPosY());
|
||||
switch (shapeType.getDrawingAlgorithm()) {
|
||||
case SQUARE:
|
||||
return new Square(shapeType, positionDatum.getPanelId(), pos, positionDatum.getOrientation());
|
||||
|
||||
case TRIANGLE:
|
||||
return new Triangle(shapeType, positionDatum.getPanelId(), pos, positionDatum.getOrientation());
|
||||
|
||||
case HEXAGON:
|
||||
return new Hexagon(shapeType, positionDatum.getPanelId(), pos, positionDatum.getOrientation());
|
||||
|
||||
default:
|
||||
return new Point(shapeType, positionDatum.getPanelId(), pos, positionDatum.getOrientation());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.nanoleaf.internal.layout.shape;
|
||||
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.geom.Rectangle2D;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.nanoleaf.internal.layout.Point2D;
|
||||
import org.openhab.binding.nanoleaf.internal.layout.ShapeType;
|
||||
|
||||
/**
|
||||
* A square shape.
|
||||
*
|
||||
* @author Jørgen Austvik - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class Square extends Shape {
|
||||
public Square(ShapeType shapeType, int panelId, Point2D position, int orientation) {
|
||||
super(shapeType, panelId, position, orientation);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Point2D> generateOutline() {
|
||||
int sideLength = (int) getShapeType().getSideLength();
|
||||
|
||||
Point2D current = getPosition();
|
||||
Point2D corner2 = new Point2D(current.getX() + sideLength, current.getY());
|
||||
Point2D corner3 = new Point2D(current.getX() + sideLength, current.getY() + sideLength);
|
||||
Point2D corner4 = new Point2D(current.getX(), current.getY() + sideLength);
|
||||
return Arrays.asList(getPosition(), corner2, corner3, corner4);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Point2D labelPosition(Graphics2D graphics, List<Point2D> outline) {
|
||||
// Center of square is average of oposite corners
|
||||
Point2D p0 = outline.get(0);
|
||||
Point2D p2 = outline.get(2);
|
||||
|
||||
Rectangle2D rect = graphics.getFontMetrics().getStringBounds(Integer.toString(getPanelId()), graphics);
|
||||
|
||||
return new Point2D((p0.getX() + p2.getX()) / 2 - (int) (rect.getWidth() / 2),
|
||||
(p0.getY() + p2.getY()) / 2 - (int) (rect.getHeight() / 2));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.nanoleaf.internal.layout.shape;
|
||||
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.geom.Rectangle2D;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.nanoleaf.internal.layout.Point2D;
|
||||
import org.openhab.binding.nanoleaf.internal.layout.ShapeType;
|
||||
|
||||
/**
|
||||
* A triangular shape.
|
||||
*
|
||||
* @author Jørgen Austvik - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class Triangle extends Shape {
|
||||
public Triangle(ShapeType shapeType, int panelId, Point2D position, int orientation) {
|
||||
super(shapeType, panelId, position, orientation);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Point2D> generateOutline() {
|
||||
int height = (int) (getShapeType().getSideLength() * Math.sqrt(3) / 2);
|
||||
Point2D v1;
|
||||
if (pointsUp()) {
|
||||
v1 = new Point2D(0, height * 2 / 3);
|
||||
} else {
|
||||
v1 = new Point2D(0, -height * 2 / 3);
|
||||
}
|
||||
|
||||
Point2D v2 = v1.rotate((2.0 / 3.0) * Math.PI);
|
||||
Point2D v3 = v1.rotate((-2.0 / 3.0) * Math.PI);
|
||||
return Arrays.asList(v1.move(getPosition()), v2.move(getPosition()), v3.move(getPosition()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Point2D labelPosition(Graphics2D graphics, List<Point2D> outline) {
|
||||
Point2D[] bounds = findBounds(outline);
|
||||
int midX = bounds[0].getX() + (bounds[1].getX() - bounds[0].getX()) / 2;
|
||||
int midY = bounds[0].getY() + (bounds[1].getY() - bounds[0].getY()) / 2;
|
||||
|
||||
Rectangle2D rect = graphics.getFontMetrics().getStringBounds(Integer.toString(getPanelId()), graphics);
|
||||
return new Point2D(midX - (int) (rect.getWidth() / 2), midY - (int) (rect.getHeight() / 2));
|
||||
}
|
||||
|
||||
private boolean pointsUp() {
|
||||
// Upward: even multiple of 60 degrees rotation
|
||||
return ((getOrientation() / 60) % 2) == 0;
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,8 @@
|
||||
*/
|
||||
package org.openhab.binding.nanoleaf.internal.model;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
@@ -27,6 +29,15 @@ public class GlobalOrientation {
|
||||
private @Nullable Integer max;
|
||||
private @Nullable Integer min;
|
||||
|
||||
public GlobalOrientation() {
|
||||
}
|
||||
|
||||
public GlobalOrientation(Integer min, Integer max, int value) {
|
||||
this.min = min;
|
||||
this.max = max;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
@@ -50,4 +61,30 @@ public class GlobalOrientation {
|
||||
public void setMin(Integer min) {
|
||||
this.min = min;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
GlobalOrientation go = (GlobalOrientation) o;
|
||||
return (value == go.getValue()) && (Objects.equals(min, go.getMin())) && (Objects.equals(max, go.getMax()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
Integer x = max;
|
||||
Integer i = min;
|
||||
result = prime * result + value;
|
||||
result = prime * result + ((x == null) ? 0 : x.hashCode());
|
||||
result = prime * result + ((i == null) ? 0 : i.hashCode());
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
*/
|
||||
package org.openhab.binding.nanoleaf.internal.model;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
@@ -36,6 +37,14 @@ public class Layout {
|
||||
|
||||
private @Nullable List<PositionDatum> positionData = null;
|
||||
|
||||
public Layout() {
|
||||
}
|
||||
|
||||
public Layout(List<PositionDatum> positionData) {
|
||||
this.positionData = new ArrayList<>(positionData);
|
||||
this.numPanels = positionData.size();
|
||||
}
|
||||
|
||||
public int getNumPanels() {
|
||||
return numPanels;
|
||||
}
|
||||
@@ -143,4 +152,48 @@ public class Layout {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Layout l = (Layout) o;
|
||||
|
||||
if (numPanels != l.getNumPanels()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
List<PositionDatum> pd = getPositionData();
|
||||
List<PositionDatum> otherPd = l.getPositionData();
|
||||
if (pd == null && otherPd == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (pd == null || otherPd == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return pd.equals(otherPd);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + getNumPanels();
|
||||
List<PositionDatum> pd = getPositionData();
|
||||
if (pd != null) {
|
||||
for (PositionDatum p : pd) {
|
||||
result = prime * result + p.hashCode();
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,14 @@ public class PanelLayout {
|
||||
private @Nullable Layout layout;
|
||||
private @Nullable GlobalOrientation globalOrientation;
|
||||
|
||||
public PanelLayout() {
|
||||
}
|
||||
|
||||
public PanelLayout(GlobalOrientation globalOrientation, Layout layout) {
|
||||
this.globalOrientation = globalOrientation;
|
||||
this.layout = layout;
|
||||
}
|
||||
|
||||
public @Nullable Layout getLayout() {
|
||||
return layout;
|
||||
}
|
||||
@@ -41,4 +49,69 @@ public class PanelLayout {
|
||||
public void setGlobalOrientation(GlobalOrientation globalOrientation) {
|
||||
this.globalOrientation = globalOrientation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
PanelLayout pl = (PanelLayout) o;
|
||||
|
||||
// For a panel layout to be equal to another panel layouit, all inner data structures must
|
||||
// be equal, or they must be null both in this object or the object it is compared with.
|
||||
|
||||
GlobalOrientation go = globalOrientation;
|
||||
GlobalOrientation otherGo = pl.getGlobalOrientation();
|
||||
boolean goEquals = false;
|
||||
if (go == null || otherGo == null) {
|
||||
if (go == null && otherGo == null) {
|
||||
// If one of the global oriantations are null, the other must also be null
|
||||
// for them to be equal
|
||||
goEquals = true;
|
||||
}
|
||||
} else {
|
||||
goEquals = go.equals(otherGo);
|
||||
}
|
||||
|
||||
if (goEquals == false) {
|
||||
// No reason to compare layout if global oriantation is different
|
||||
return false;
|
||||
}
|
||||
|
||||
Layout l = layout;
|
||||
Layout otherL = pl.getLayout();
|
||||
|
||||
if (l == null && otherL == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (l == null || otherL == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return l.equals(otherL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
|
||||
GlobalOrientation go = globalOrientation;
|
||||
if (go != null) {
|
||||
result = prime * result + go.hashCode();
|
||||
}
|
||||
|
||||
Layout l = layout;
|
||||
if (l != null) {
|
||||
result = prime * result + l.hashCode();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,10 +12,9 @@
|
||||
*/
|
||||
package org.openhab.binding.nanoleaf.internal.model;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.nanoleaf.internal.layout.ShapeType;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
@@ -37,21 +36,15 @@ public class PositionDatum {
|
||||
@SerializedName("shapeType")
|
||||
private int shapeType;
|
||||
|
||||
private static Map<Integer, Integer> panelSizes = new HashMap<Integer, Integer>();
|
||||
|
||||
public PositionDatum() {
|
||||
// initialize constant sidelengths for panels. See https://forum.nanoleaf.me/docs chapter 3.3
|
||||
if (panelSizes.isEmpty()) {
|
||||
panelSizes.put(0, 150); // Triangle
|
||||
panelSizes.put(1, 0); // Rhythm N/A
|
||||
panelSizes.put(2, 100); // Square
|
||||
panelSizes.put(3, 100); // Control Square Master
|
||||
panelSizes.put(4, 100); // Control Square Passive
|
||||
panelSizes.put(7, 67); // Hexagon
|
||||
panelSizes.put(8, 134); // Triangle Shapes
|
||||
panelSizes.put(9, 67); // Mini Triangle Shapes
|
||||
panelSizes.put(12, 0); // Shapes Controller (N/A)
|
||||
}
|
||||
}
|
||||
|
||||
public PositionDatum(int panelId, int posX, int posY, int orientation, int shapeType) {
|
||||
this.panelId = panelId;
|
||||
this.posX = posX;
|
||||
this.posY = posY;
|
||||
this.orientation = orientation;
|
||||
this.shapeType = shapeType;
|
||||
}
|
||||
|
||||
public int getPanelId() {
|
||||
@@ -105,6 +98,33 @@ public class PositionDatum {
|
||||
}
|
||||
|
||||
public Integer getPanelSize() {
|
||||
return panelSizes.getOrDefault(shapeType, 0);
|
||||
return (int) ShapeType.valueOf(shapeType).getSideLength();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
PositionDatum pd = (PositionDatum) o;
|
||||
return (posX == pd.getPosX()) && (posY == pd.getPosY()) && (orientation == pd.getOrientation())
|
||||
&& (shapeType == pd.getShapeType()) && (panelId == pd.getPanelId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + posX;
|
||||
result = prime * result + posY;
|
||||
result = prime * result + orientation;
|
||||
result = prime * result + shapeType;
|
||||
result = prime * result + panelId;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,6 +38,8 @@ channel-type.nanoleaf.tap.label = Button
|
||||
channel-type.nanoleaf.tap.description = Button events of the panel
|
||||
channel-type.nanoleaf.swipe.label = Swipe
|
||||
channel-type.nanoleaf.swipe.description = Swipe over the panels
|
||||
channel-type.nanoleaf.layout.label = Layout
|
||||
channel-type.nanoleaf.layout.description = Layout of the panels
|
||||
|
||||
# error messages
|
||||
error.nanoleaf.controller.noIp = IP/host address and/or port are not configured for the controller.
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
<channel id="rhythmActive" typeId="rhythmActive"/>
|
||||
<channel id="rhythmMode" typeId="rhythmMode"/>
|
||||
<channel id="swipe" typeId="swipe"/>
|
||||
<channel id="layout" typeId="layout"/>
|
||||
</channels>
|
||||
|
||||
<properties>
|
||||
@@ -107,4 +108,10 @@
|
||||
</event>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="layout">
|
||||
<item-type>Image</item-type>
|
||||
<label>@text/channel-type.nanoleaf.layout.label</label>
|
||||
<description>@text/channel-type.nanoleaf.layout.description</description>
|
||||
</channel-type>
|
||||
|
||||
</thing:thing-descriptions>
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.nanoleaf.internal.model;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* Test for global orientation
|
||||
*
|
||||
* @author Jørgen Austvik - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class GlobalOrientationTest {
|
||||
|
||||
@Nullable
|
||||
GlobalOrientation go1;
|
||||
|
||||
@Nullable
|
||||
GlobalOrientation go2; // Different from go1
|
||||
|
||||
@Nullable
|
||||
GlobalOrientation go3; // Same as go1
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
go1 = new GlobalOrientation(0, 360, 180);
|
||||
go2 = new GlobalOrientation(0, 360, 267);
|
||||
go3 = new GlobalOrientation(0, 360, 180);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHashCode() {
|
||||
GlobalOrientation g1 = go1;
|
||||
GlobalOrientation g2 = go2;
|
||||
GlobalOrientation g3 = go3;
|
||||
if (g1 != null && g2 != null && g3 != null) {
|
||||
assertThat(g1.hashCode(), is(equalTo(g3.hashCode())));
|
||||
assertThat(g2.hashCode(), is(not(equalTo(g3.hashCode()))));
|
||||
} else {
|
||||
assertThat("Should be initialized", false);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEquals() {
|
||||
assertThat(go1, is(equalTo(go3)));
|
||||
assertThat(go2, is(not(equalTo(go3))));
|
||||
assertThat(go3, is(not(equalTo(null))));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.nanoleaf.internal.model;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* Test for global orientation
|
||||
*
|
||||
* @author Jørgen Austvik - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class LayoutTest {
|
||||
|
||||
@Nullable
|
||||
private Layout lo1;
|
||||
|
||||
@Nullable
|
||||
private Layout lo2; // Different from l1
|
||||
|
||||
@Nullable
|
||||
private Layout lo3; // Same as l1
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
PositionDatum pd1 = new PositionDatum(100, 200, 270, 123, 12);
|
||||
PositionDatum pd2 = new PositionDatum(100, 220, 240, 123, 2);
|
||||
PositionDatum pd3 = new PositionDatum(100, 200, 270, 123, 12);
|
||||
|
||||
lo1 = new Layout(Arrays.asList(pd1, pd3));
|
||||
lo2 = new Layout(Arrays.asList(pd1, pd2));
|
||||
lo3 = new Layout(Arrays.asList(pd1, pd3));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHashCode() {
|
||||
Layout l1 = lo1;
|
||||
Layout l2 = lo2;
|
||||
Layout l3 = lo3;
|
||||
if (l1 != null && l2 != null && l3 != null) {
|
||||
assertThat(l1.hashCode(), is(equalTo(l3.hashCode())));
|
||||
assertThat(l2.hashCode(), is(not(equalTo(l3.hashCode()))));
|
||||
} else {
|
||||
assertThat("Should be initialized", false);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEquals() {
|
||||
assertThat(lo1, is(equalTo(lo3)));
|
||||
assertThat(lo2, is(not(equalTo(lo3))));
|
||||
assertThat(lo3, is(not(equalTo(null))));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.nanoleaf.internal.model;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* Test for global orientation
|
||||
*
|
||||
* @author Jørgen Austvik - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class PanelLayoutTest {
|
||||
|
||||
@Nullable
|
||||
private PanelLayout pl1;
|
||||
|
||||
@Nullable
|
||||
private PanelLayout pl2; // Different from pl1
|
||||
|
||||
@Nullable
|
||||
private PanelLayout pl3; // Equal to pl1
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
PositionDatum pd1 = new PositionDatum(100, 200, 270, 123, 12);
|
||||
PositionDatum pd2 = new PositionDatum(100, 220, 240, 123, 2);
|
||||
PositionDatum pd3 = new PositionDatum(100, 200, 270, 123, 12);
|
||||
|
||||
Layout l1 = new Layout(Arrays.asList(pd1, pd3));
|
||||
Layout l2 = new Layout(Arrays.asList(pd1, pd2));
|
||||
Layout l3 = new Layout(Arrays.asList(pd1, pd3));
|
||||
|
||||
GlobalOrientation go1 = new GlobalOrientation(0, 360, 180);
|
||||
|
||||
pl1 = new PanelLayout(go1, l1);
|
||||
pl2 = new PanelLayout(go1, l2);
|
||||
pl3 = new PanelLayout(go1, l3);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHashCode() {
|
||||
PanelLayout p1 = pl1;
|
||||
PanelLayout p2 = pl2;
|
||||
PanelLayout p3 = pl3;
|
||||
if (p1 != null && p2 != null && p3 != null) {
|
||||
assertThat(p1.hashCode(), is(equalTo(p3.hashCode())));
|
||||
assertThat(p2.hashCode(), is(not(equalTo(p3.hashCode()))));
|
||||
} else {
|
||||
assertThat("Should be initialized", false);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEquals() {
|
||||
assertThat(pl1, is(equalTo(pl3)));
|
||||
assertThat(pl2, is(not(equalTo(pl3))));
|
||||
assertThat(pl3, is(not(equalTo(null))));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.nanoleaf.internal.model;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* Test for global orientation
|
||||
*
|
||||
* @author Jørgen Austvik - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class PositionDatumTest {
|
||||
|
||||
@Nullable
|
||||
private PositionDatum pd1;
|
||||
|
||||
@Nullable
|
||||
private PositionDatum pd2; // different from pd1
|
||||
|
||||
@Nullable
|
||||
private PositionDatum pd3; // same as pd1
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
pd1 = new PositionDatum(100, 200, 270, 123, 12);
|
||||
pd2 = new PositionDatum(100, 220, 240, 123, 2);
|
||||
pd3 = new PositionDatum(100, 200, 270, 123, 12);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHashCode() {
|
||||
PositionDatum p1 = pd1;
|
||||
PositionDatum p2 = pd2;
|
||||
PositionDatum p3 = pd3;
|
||||
if (p1 != null && p2 != null && p3 != null) {
|
||||
assertThat(p1.hashCode(), is(equalTo(p3.hashCode())));
|
||||
assertThat(p2.hashCode(), is(not(equalTo(p3.hashCode()))));
|
||||
} else {
|
||||
assertThat("Should be initialized", false);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEquals() {
|
||||
assertThat(pd1, is(equalTo(pd3)));
|
||||
assertThat(pd2, is(not(equalTo(pd3))));
|
||||
assertThat(pd3, is(not(equalTo(null))));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user