[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:
Jørgen Austvik 2022-11-12 23:00:08 +01:00 committed by GitHub
parent 160e0c2548
commit fbd06ec709
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 1278 additions and 29 deletions

View File

@ -28,9 +28,13 @@ You can set the **color** for each panel and in the case of a Nanoleaf Canvas or
| Nanoleaf Name | Type | Description | supported | touch support | | Nanoleaf Name | Type | Description | supported | touch support |
| ---------------------- | ---- | ---------------------------------------------------------- | --------- | ------------- | | ---------------------- | ---- | ---------------------------------------------------------- | --------- | ------------- |
| Light Panels | NL22 | Triangles 1st Generation | X | - | | Light Panels | NL22 | Triangles 1st Generation | X | - |
| Shapes Triangle | NL42 | Triangles 2nd Generation (rounded edges) | X | X |
| Shapes Hexagon | NL42 | Hexagons | X | X | | Shapes Hexagon | NL42 | Hexagons | X | X |
| Shapes Mini Triangles | NL42 | Mini Triangles | x | X | | Shapes Triangles | NL47 | Triangles | X | X |
| Shapes Mini Triangles | NL48 | Mini Triangles | X | X |
| Elements Hexagon | NL52 | Elements Hexagons | X | X |
| Smart Bulb | NL45 | Smart Bulb | - | |
| Lightstrip | NL55 | Lightstrip | - | |
| Lines | NL59 | Lines | - | |
| Canvas | NL29 | Squares | X | X | | Canvas | NL29 | Squares | X | X |
x = Supported (-) = unknown (no device available to test) x = Supported (-) = unknown (no device available to test)
@ -70,9 +74,15 @@ In this case:
### Panel Layout ### Panel Layout
Unfortunately it is not easy to find out which panel gets which id, and this becomes pretty important if you have lots of them and want to assign rules. If you want to program individual panels, it can be hard to figure out which panel has which ID. To make this easier, there is Layout channel on the Nanoleaf controller thing in openHAB.
The easiest way to visualize the layout of the individual panels is to open the controller thing in the openHAB UI, go to Channels and add a new item to the Layout channel.
Clicking on that image or adding it to a dashboard will show a picture of your canvas with the individual thing ID in the picture.
For canvas that use square panels, you can request the layout through a [console command](https://www.openhab.org/docs/administration/console.html): If your canvas has elements we dont know how to draw a layout for yet, please reach out, and we will ask for some information and will try to add support for your elements.
![Image](doc/Layout.jpg)
There is an alternative method for canvas that use square panels, you can request the layout through a [console command](https://www.openhab.org/docs/administration/console.html):
then issue the following command: then issue the following command:
@ -94,7 +104,7 @@ Compare the following output with the right picture at the beginning of the arti
41451 41451
``` ```
## Thing Configuration ## Thing Configuration
The controller thing has the following parameters: The controller thing has the following parameters:

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

View File

@ -58,6 +58,7 @@ public class NanoleafBindingConstants {
public static final String CHANNEL_SWIPE_EVENT_DOWN = "DOWN"; 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_LEFT = "LEFT";
public static final String CHANNEL_SWIPE_EVENT_RIGHT = "RIGHT"; public static final String CHANNEL_SWIPE_EVENT_RIGHT = "RIGHT";
public static final String CHANNEL_LAYOUT = "layout";
// List of light panel channels // List of light panel channels
public static final String CHANNEL_PANEL_COLOR = "color"; 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 API_MIN_FW_VER_CANVAS = "1.1.0";
public static final String MODEL_ID_LIGHTPANELS = "NL22"; 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_LIGHTPANELS = "lightPanels";
public static final String DEVICE_TYPE_TOUCHSUPPORT = "canvas"; // we need to keep this enum for backward public static final String DEVICE_TYPE_TOUCHSUPPORT = "canvas"; // we need to keep this enum for backward
// compatibility even though not only canvas type // compatibility even though not only canvas type
@ -93,4 +94,8 @@ public class NanoleafBindingConstants {
// Color channels increase/decrease brightness step size // Color channels increase/decrease brightness step size
public static final int BRIGHTNESS_STEP_SIZE = 5; 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;
} }

View File

@ -154,11 +154,7 @@ public class OpenAPIUtils {
for (int i = 0; i < currentVer.length; ++i) { for (int i = 0; i < currentVer.length; ++i) {
if (currentVer[i] != requiredVer[i]) { if (currentVer[i] != requiredVer[i]) {
if (currentVer[i] > requiredVer[i]) { return (currentVer[i] > requiredVer[i]);
return true;
}
return false;
} }
} }

View File

@ -14,6 +14,7 @@ package org.openhab.binding.nanoleaf.internal.handler;
import static org.openhab.binding.nanoleaf.internal.NanoleafBindingConstants.*; import static org.openhab.binding.nanoleaf.internal.NanoleafBindingConstants.*;
import java.io.IOException;
import java.net.URI; import java.net.URI;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.Collection; 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.commanddescription.NanoleafCommandDescriptionProvider;
import org.openhab.binding.nanoleaf.internal.config.NanoleafControllerConfig; import org.openhab.binding.nanoleaf.internal.config.NanoleafControllerConfig;
import org.openhab.binding.nanoleaf.internal.discovery.NanoleafPanelsDiscoveryService; 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.AuthToken;
import org.openhab.binding.nanoleaf.internal.model.BooleanState; import org.openhab.binding.nanoleaf.internal.model.BooleanState;
import org.openhab.binding.nanoleaf.internal.model.Brightness; 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.IncreaseDecreaseType;
import org.openhab.core.library.types.OnOffType; import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.PercentType; 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.library.types.StringType;
import org.openhab.core.thing.Bridge; import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ChannelUID; 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.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail; import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.binding.BaseBridgeHandler; 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.thing.binding.ThingHandlerService;
import org.openhab.core.types.Command; import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType; import org.openhab.core.types.RefreshType;
@ -103,6 +107,7 @@ public class NanoleafControllerHandler extends BaseBridgeHandler {
private @Nullable HttpClient httpClientSSETouchEvent; private @Nullable HttpClient httpClientSSETouchEvent;
private @Nullable Request sseTouchjobRequest; private @Nullable Request sseTouchjobRequest;
private List<NanoleafControllerListener> controllerListeners = new CopyOnWriteArrayList<NanoleafControllerListener>(); private List<NanoleafControllerListener> controllerListeners = new CopyOnWriteArrayList<NanoleafControllerListener>();
private PanelLayout previousPanelLayout = new PanelLayout();
private @NonNullByDefault({}) ScheduledFuture<?> pairingJob; private @NonNullByDefault({}) ScheduledFuture<?> pairingJob;
private @NonNullByDefault({}) ScheduledFuture<?> updateJob; private @NonNullByDefault({}) ScheduledFuture<?> updateJob;
@ -664,6 +669,7 @@ public class NanoleafControllerHandler extends BaseBridgeHandler {
updateProperties(); updateProperties();
updateConfiguration(); updateConfiguration();
updateLayout(controllerInfo.getPanelLayout());
for (NanoleafControllerListener controllerListener : controllerListeners) { for (NanoleafControllerListener controllerListener : controllerListeners) {
controllerListener.onControllerInfoFetched(getThing().getUID(), controllerInfo); 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 { private ControllerInfo receiveControllerInfo() throws NanoleafException, NanoleafUnauthorizedException {
ContentResponse controllerlInfoJSON = OpenAPIUtils.sendOpenAPIRequest(OpenAPIUtils.requestBuilder(httpClient, ContentResponse controllerlInfoJSON = OpenAPIUtils.sendOpenAPIRequest(OpenAPIUtils.requestBuilder(httpClient,
getControllerConfig(), API_GET_CONTROLLER_INFO, HttpMethod.GET)); getControllerConfig(), API_GET_CONTROLLER_INFO, HttpMethod.GET));

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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));
}
}

View File

@ -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);
}
}

View File

@ -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);
}

View File

@ -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());
}
}
}

View File

@ -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));
}
}

View File

@ -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;
}
}

View File

@ -12,6 +12,8 @@
*/ */
package org.openhab.binding.nanoleaf.internal.model; package org.openhab.binding.nanoleaf.internal.model;
import java.util.Objects;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jdt.annotation.Nullable;
@ -27,6 +29,15 @@ public class GlobalOrientation {
private @Nullable Integer max; private @Nullable Integer max;
private @Nullable Integer min; 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() { public int getValue() {
return value; return value;
} }
@ -50,4 +61,30 @@ public class GlobalOrientation {
public void setMin(Integer min) { public void setMin(Integer min) {
this.min = 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;
}
} }

View File

@ -12,6 +12,7 @@
*/ */
package org.openhab.binding.nanoleaf.internal.model; package org.openhab.binding.nanoleaf.internal.model;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.TreeMap; import java.util.TreeMap;
@ -36,6 +37,14 @@ public class Layout {
private @Nullable List<PositionDatum> positionData = null; 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() { public int getNumPanels() {
return numPanels; return numPanels;
} }
@ -143,4 +152,48 @@ public class Layout {
return ""; 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;
}
} }

View File

@ -26,6 +26,14 @@ public class PanelLayout {
private @Nullable Layout layout; private @Nullable Layout layout;
private @Nullable GlobalOrientation globalOrientation; private @Nullable GlobalOrientation globalOrientation;
public PanelLayout() {
}
public PanelLayout(GlobalOrientation globalOrientation, Layout layout) {
this.globalOrientation = globalOrientation;
this.layout = layout;
}
public @Nullable Layout getLayout() { public @Nullable Layout getLayout() {
return layout; return layout;
} }
@ -41,4 +49,69 @@ public class PanelLayout {
public void setGlobalOrientation(GlobalOrientation globalOrientation) { public void setGlobalOrientation(GlobalOrientation globalOrientation) {
this.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;
}
} }

View File

@ -12,10 +12,9 @@
*/ */
package org.openhab.binding.nanoleaf.internal.model; 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.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.nanoleaf.internal.layout.ShapeType;
import com.google.gson.annotations.SerializedName; import com.google.gson.annotations.SerializedName;
@ -37,21 +36,15 @@ public class PositionDatum {
@SerializedName("shapeType") @SerializedName("shapeType")
private int shapeType; private int shapeType;
private static Map<Integer, Integer> panelSizes = new HashMap<Integer, Integer>();
public PositionDatum() { public PositionDatum() {
// initialize constant sidelengths for panels. See https://forum.nanoleaf.me/docs chapter 3.3 }
if (panelSizes.isEmpty()) {
panelSizes.put(0, 150); // Triangle public PositionDatum(int panelId, int posX, int posY, int orientation, int shapeType) {
panelSizes.put(1, 0); // Rhythm N/A this.panelId = panelId;
panelSizes.put(2, 100); // Square this.posX = posX;
panelSizes.put(3, 100); // Control Square Master this.posY = posY;
panelSizes.put(4, 100); // Control Square Passive this.orientation = orientation;
panelSizes.put(7, 67); // Hexagon this.shapeType = shapeType;
panelSizes.put(8, 134); // Triangle Shapes
panelSizes.put(9, 67); // Mini Triangle Shapes
panelSizes.put(12, 0); // Shapes Controller (N/A)
}
} }
public int getPanelId() { public int getPanelId() {
@ -105,6 +98,33 @@ public class PositionDatum {
} }
public Integer getPanelSize() { 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;
} }
} }

View File

@ -38,6 +38,8 @@ channel-type.nanoleaf.tap.label = Button
channel-type.nanoleaf.tap.description = Button events of the panel channel-type.nanoleaf.tap.description = Button events of the panel
channel-type.nanoleaf.swipe.label = Swipe channel-type.nanoleaf.swipe.label = Swipe
channel-type.nanoleaf.swipe.description = Swipe over the panels 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 messages
error.nanoleaf.controller.noIp = IP/host address and/or port are not configured for the controller. error.nanoleaf.controller.noIp = IP/host address and/or port are not configured for the controller.

View File

@ -18,6 +18,7 @@
<channel id="rhythmActive" typeId="rhythmActive"/> <channel id="rhythmActive" typeId="rhythmActive"/>
<channel id="rhythmMode" typeId="rhythmMode"/> <channel id="rhythmMode" typeId="rhythmMode"/>
<channel id="swipe" typeId="swipe"/> <channel id="swipe" typeId="swipe"/>
<channel id="layout" typeId="layout"/>
</channels> </channels>
<properties> <properties>
@ -107,4 +108,10 @@
</event> </event>
</channel-type> </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> </thing:thing-descriptions>

View File

@ -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))));
}
}

View File

@ -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))));
}
}

View File

@ -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))));
}
}

View File

@ -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))));
}
}