diff --git a/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/client/SerialTransportAdapter.java b/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/client/SerialTransportAdapter.java index e20afc15f..7028d4b66 100644 --- a/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/client/SerialTransportAdapter.java +++ b/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/client/SerialTransportAdapter.java @@ -39,12 +39,12 @@ import tuwien.auto.calimero.serial.spi.SerialCom; * The {@link SerialTransportAdapter} provides org.openhab.core.io.transport.serial * services to the Calimero library. * - * @ServiceProvider annotation (biz.aQute.bnd.annotation) automatically creates the file - * /META-INF/services/tuwien.auto.calimero.serial.spi.SerialCom - * to register SerialTransportAdapter to the service loader. - * Additional attributes for SerialTansportAdapter can be specified as well, e.g. - * attribute = { "position=1" } - * and will be part of MANIFEST.MF + * {@literal @}ServiceProvider annotation (biz.aQute.bnd.annotation) automatically creates the file + * /META-INF/services/tuwien.auto.calimero.serial.spi.SerialCom + * to register SerialTransportAdapter to the service loader. + * Additional attributes for SerialTansportAdapter can be specified as well, e.g. + * attribute = { "position=1" } + * and will be part of MANIFEST.MF * * @author Holger Friedrich - Initial contribution */ diff --git a/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/dpt/ValueDecoder.java b/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/dpt/ValueDecoder.java index 28d3ecae9..b94f0ac16 100644 --- a/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/dpt/ValueDecoder.java +++ b/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/dpt/ValueDecoder.java @@ -73,9 +73,10 @@ public class ValueDecoder { // RGBW: "100 27 25 12 %", value range: 0-100, invalid values: "-" private static final Pattern RGBW_PATTERN = Pattern .compile("(?:(?[\\d,.]+)|-)\\s(?:(?[\\d,.]+)|-)\\s(?:(?[\\d,.]+)|-)\\s(?:(?[\\d,.]+)|-)\\s%"); - // xyY: "(0,123 0,123) 56 %", value range 0-1 for xy (comma as decimal point), 0-100 for Y, invalid values omitted - private static final Pattern XYY_PATTERN = Pattern - .compile("(?:\\((?\\d+(?:,\\d+)?) (?\\d+(?:,\\d+)?)\\))?\\s*(?:(?\\d+(?:,\\d+)?)\\s%)?"); + // xyY: "(0,123 0,123) 56 %", value range 0-1 for xy (comma or point as decimal point), 0-100 for Y, invalid values + // omitted + public static final Pattern XYY_PATTERN = Pattern + .compile("(?:\\((?\\d+(?:[,.]\\d+)?) (?\\d+(?:[,.]\\d+)?)\\))?\\s*(?:(?\\d+(?:[,.]\\d+)?)\\s%)?"); /** * convert the raw value received to the corresponding openHAB value @@ -311,7 +312,7 @@ public class ValueDecoder { return ColorUtil.xyToHsb(new double[] { x, y }); } else { double pY = Double.parseDouble(stringY.replace(",", ".")); - return ColorUtil.xyToHsb(new double[] { x, y, pY }); + return ColorUtil.xyToHsb(new double[] { x, y, pY / 100.0 }); } } } diff --git a/bundles/org.openhab.binding.knx/src/test/java/org/openhab/binding/knx/internal/dpt/DPTTest.java b/bundles/org.openhab.binding.knx/src/test/java/org/openhab/binding/knx/internal/dpt/DPTTest.java index 049192499..71c3b7ab5 100644 --- a/bundles/org.openhab.binding.knx/src/test/java/org/openhab/binding/knx/internal/dpt/DPTTest.java +++ b/bundles/org.openhab.binding.knx/src/test/java/org/openhab/binding/knx/internal/dpt/DPTTest.java @@ -14,8 +14,9 @@ package org.openhab.binding.knx.internal.dpt; import static org.junit.jupiter.api.Assertions.*; -import java.nio.charset.StandardCharsets; import java.util.Objects; +import java.util.regex.Matcher; +import java.util.stream.IntStream; import java.util.stream.Stream; import org.eclipse.jdt.annotation.NonNullByDefault; @@ -402,13 +403,79 @@ class DPTTest { Assertions.assertNotNull(value); } - private static Stream rgbValueProvider() { - return Stream.of("r:0 g:0 b:0", "r:255 g:255 b:255"); + private static Stream rgbValueProvider() { + // Returning all combinations is too much. Implementation tries to catch rounding errors + // but is still deterministic to get reproducible test results. + return IntStream.range(0, 3 * 256) + .mapToObj(i -> new byte[] { (byte) (i & 0xff), (byte) ((i / 2) & 0xff), (byte) ((i / 3) & 0xff) }); } @ParameterizedTest @MethodSource("rgbValueProvider") - public void rgbTest(String value) { - Assertions.assertNotNull(ValueDecoder.decode("232.600", value.getBytes(StandardCharsets.UTF_8), HSBType.class)); + public void dpt232BackToBackTest(byte[] value) { + backToBackTest232(value, "232.600"); + backToBackTest232(value, "232.60000"); + } + + private void backToBackTest232(byte[] value, String dpt) { + // decode will apply transformation from raw bytes to String internally + HSBType hsb = (HSBType) ValueDecoder.decode(dpt, value, HSBType.class); + Assertions.assertNotNull(hsb); + + // encoding will return a String in notation defined by Calimero: "r:xxx g:xxx b:xxx" + String result = ValueEncoder.encode(hsb, dpt); + + // for back to back test, compare String representation + Assertions.assertEquals("r:" + (value[0] & 0xff) + " g:" + (value[1] & 0xff) + " b:" + (value[2] & 0xff), + result); + } + + private static Stream xyYValueProvider() { + // Returning all combinations is too much. Implementation tries to catch rounding errors + // but is still deterministic to get reproducible test results. + // + // Cannot run make use of full numeric range, as colors cannot be represented in CIE space and + // back conversion would return different results. + // Use x: 0.1 .. 0.3, y: 0.3 .. 0.5 to be inside the triangle which can be converted without loss + return IntStream.range(77, 115).mapToObj(i -> new byte[] { (byte) ((i / 2) & 0xff), (byte) ((i) & 0xff), + (byte) (i & 0xff), (byte) ((i / 3) & 0xff), (byte) (((i - 50) * 3) & 0xff), 3 }); + // last byte has value 3: c+b valid + } + + @ParameterizedTest + @MethodSource("xyYValueProvider") + public void dpt242BackToBackTest(byte[] value) { + final String dpt = "242.600"; + + // decode will apply transformation from raw bytes to String internally + HSBType hsb = (HSBType) ValueDecoder.decode(dpt, value, HSBType.class); + Assertions.assertNotNull(hsb); + + // encoding will return a String in notation defined by Calimero: "(x,xxxx y,yyyy) YY,Y %" + String result = ValueEncoder.encode(hsb, dpt); + + // for back to back test, compare numerical values to allow tolerances + double dx = (((value[0] & 0xff) << 8) | (value[1] & 0xff)) / 65535.0; + double dy = (((value[2] & 0xff) << 8) | (value[3] & 0xff)) / 65535.0; + double dY = ((double) (value[4] & 0xff)) * 100.0 / 255.0; + + Matcher matcher = ValueDecoder.XYY_PATTERN.matcher(result); + Assertions.assertTrue(matcher.matches()); + String stringx = matcher.group("x"); + String stringy = matcher.group("y"); + String stringY = matcher.group("Y"); + Assertions.assertNotNull(stringx); + Assertions.assertNotNull(stringy); + Assertions.assertNotNull(stringY); + double rx = Double.parseDouble(stringx.replace(',', '.')); + double ry = Double.parseDouble(stringy.replace(',', '.')); + double rY = Double.parseDouble(stringY.replace(',', '.')); + + final double tolerance = 0.001; + if ((Math.abs(dx - rx) > tolerance) || (Math.abs(dy - ry) > tolerance) + || (Math.abs(dY - rY) > tolerance * 100)) { + // print failures in a useful format + Assertions.assertEquals(String.format("(%.4f %.4f) %.1f %%", dx, dy, dY), result); + } } }