]> git.basschouten.com Git - openhab-addons.git/commitdiff
[knx] Allow sending items with units to KNX bus. (#12675)
authorHolger Friedrich <holgerfriedrich@users.noreply.github.com>
Sun, 8 May 2022 09:23:18 +0000 (11:23 +0200)
committerGitHub <noreply@github.com>
Sun, 8 May 2022 09:23:18 +0000 (11:23 +0200)
Items with dimensions (QuantityType) are now translated and can be sent to the
KNX bus. This requires the correct DPT to be specified in the channel
definition. Fixes #10706.

Signed-off-by: Holger Friedrich <mail@holger-friedrich.de>
bundles/org.openhab.binding.knx/README.md
bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/channel/KNXChannelType.java
bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/dpt/KNXCoreTypeMapper.java
bundles/org.openhab.binding.knx/src/test/java/org/openhab/binding/knx/internal/dpt/KNXCoreTypeMapperTest.java

index 078b7f8c3a5986fe9057ed9d99eb36ceff0353e6..f5dd76797c5385d88c4dfe5cf30e8ed2ca221a69 100644 (file)
@@ -117,6 +117,10 @@ Note: After changing the DPT of already existing Channels, openHAB needs to be r
 |-----------|---------------|-------------|
 | ga        | Group address | 9.001       |
 
+
+Note: Using the Units Of Measurement feature of openHAB (Quantitytype) requires that the DPT value is set correctly.
+Automatic type conversion will be applied if required.
+
 ##### Channel Type "string"
 
 | Parameter | Description   | Default DPT |
index a9fcfacf4b75c64d61adba2313b940fc0256025b..4115ee714026a24cc01a36731174f31377c54be0 100644 (file)
@@ -31,6 +31,8 @@ import org.openhab.binding.knx.internal.KNXTypeMapper;
 import org.openhab.binding.knx.internal.client.InboundSpec;
 import org.openhab.binding.knx.internal.client.OutboundSpec;
 import org.openhab.core.config.core.Configuration;
+import org.openhab.core.library.types.DecimalType;
+import org.openhab.core.library.types.QuantityType;
 import org.openhab.core.types.Type;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -171,9 +173,10 @@ public abstract class KNXChannelType {
                 }
                 Class<? extends Type> expectedTypeClass = typeHelper.toTypeClass(dpt);
                 if (expectedTypeClass != null) {
-                    if (expectedTypeClass.isInstance(command)) {
+                    if (expectedTypeClass.isInstance(command)
+                            || ((expectedTypeClass == DecimalType.class) && (command instanceof QuantityType))) {
                         logger.trace(
-                                "getCommandSpec key '{}' uses expectedTypeClass '{}' witch isInstance for command '{}' and dpt '{}'",
+                                "getCommandSpec key '{}' uses expectedTypeClass '{}' which isInstance for command '{}' and dpt '{}'",
                                 key, expectedTypeClass, command, dpt);
                         return new WriteSpecImpl(config, dpt, command);
                     }
index c29a6ddbe5d255de94beeae4422cbaaf4f8ac62b..4c02a4bb4f0ce9880c6c4ad0b8a59d4dbc5cedce 100644 (file)
@@ -34,6 +34,7 @@ import org.openhab.core.library.types.IncreaseDecreaseType;
 import org.openhab.core.library.types.OnOffType;
 import org.openhab.core.library.types.OpenClosedType;
 import org.openhab.core.library.types.PercentType;
+import org.openhab.core.library.types.QuantityType;
 import org.openhab.core.library.types.StopMoveType;
 import org.openhab.core.library.types.StringType;
 import org.openhab.core.library.types.UpDownType;
@@ -231,7 +232,6 @@ public class KNXCoreTypeMapper implements KNXTypeMapper {
          * 7.011: DPT_Length_mm values: 0...65535 mm
          * 7.012: DPT_UElCurrentmA values: 0...65535 mA
          * 7.013: DPT_Brightness values: 0...65535 lx
-         * Calimero does not map: (map/use to 7.000 until then)
          * 7.600: DPT_Colour_Temperature values: 0...65535 K, 2000K 3000K 5000K 8000K
          */
         dptMainTypeMap.put(7, DecimalType.class);
@@ -250,6 +250,7 @@ public class KNXCoreTypeMapper implements KNXTypeMapper {
          * 8.007: DPT_DeltaTimeHrs
          * 8.010: DPT_Percent_V16
          * 8.011: DPT_Rotation_Angle
+         * 8.012: DPT_Length_m
          */
         dptMainTypeMap.put(8, DecimalType.class);
 
@@ -275,6 +276,8 @@ public class KNXCoreTypeMapper implements KNXTypeMapper {
          * 9.026: DPT_Rain_Amount values: -671088.64...670760.96 l/m²
          * 9.027: DPT_Value_Temp_F values: -459.6...670760.96 °F
          * 9.028: DPT_Value_Wsp_kmh values: 0...670760.96 km/h
+         * 9.029: DPT_Value_Absolute_Humidity: 0...670760 g/m³
+         * 9.030: DPT_Concentration_μgm3: 0...670760 µg/m³
          */
         dptMainTypeMap.put(9, DecimalType.class);
         /** Exceptions Datapoint Types "2-Octet Float Value", Main number 9 */
@@ -300,6 +303,11 @@ public class KNXCoreTypeMapper implements KNXTypeMapper {
          * MainType: 12
          * 12.000: General unsigned long
          * 12.001: DPT_Value_4_Ucount values: 0...4294967295 counter pulses
+         * 12.100: DPT_LongTimePeriod_Sec values: 0...4294967295 s
+         * 12.101: DPT_LongTimePeriod_Min values: 0...4294967295 min
+         * 12.102: DPT_LongTimePeriod_Hrs values: 0...4294967295 h
+         * 12.1200: DPT_VolumeLiquid_Litre values: 0..4294967295 l
+         * 12.1201: DPT_Volume_m3 values: 0..4294967295 m3
          */
         dptMainTypeMap.put(12, DecimalType.class);
         /** Exceptions Datapoint Types "4-Octet Unsigned Value", Main number 12 */
@@ -316,7 +324,10 @@ public class KNXCoreTypeMapper implements KNXTypeMapper {
          * 13.013: DPT_ActiveEnergy_kWh values: -2147483648...2147483647 kWh
          * 13.014: DPT_ApparantEnergy_kVAh values: -2147483648...2147483647 kVAh
          * 13.015: DPT_ReactiveEnergy_kVARh values: -2147483648...2147483647 kVAR
+         * 13.016: DPT_ActiveEnergy_MWh4 values: -2147483648...2147483647 MWh
          * 13.100: DPT_LongDeltaTimeSec values: -2147483648...2147483647 s
+         * 13.1200: DPT_DeltaVolumeLiquid_Litre values: -2147483648...2147483647 l
+         * 13.1201: DPT_DeltaVolume_m3 values: -2147483648...2147483647 m³
          */
         dptMainTypeMap.put(13, DecimalType.class);
         /** Exceptions Datapoint Types "4-Octet Signed Value", Main number 13 */
@@ -404,6 +415,7 @@ public class KNXCoreTypeMapper implements KNXTypeMapper {
          * 14.077: Volume flux, values: m³/s
          * 14.078: Weight, values: N
          * 14.079: Work, values: J
+         * 14.080: apparent power: VA
          */
         dptMainTypeMap.put(14, DecimalType.class);
         /** Exceptions Datapoint Types "4-Octet Float Value", Main number 14 */
@@ -571,6 +583,156 @@ public class KNXCoreTypeMapper implements KNXTypeMapper {
         defaultDptMap.put(HSBType.class, DPTXlatorRGB.DPT_RGB.getID());
     }
 
+    /*
+     * This function computes the target unit for type conversion from OH quantity type to DPT types.
+     * Calimero library provides units which can be used for most of the DPTs. There are some deviations
+     * from the OH unit scheme which are handled.
+     */
+    private String quantityTypeToDPTValue(QuantityType<?> qt, int mainNumber, int subNumber, String dpUnit)
+            throws KNXException {
+        String targetOhUnit = dpUnit;
+        double scaleFactor = 1.0;
+        switch (mainNumber) {
+            case 7:
+                switch (subNumber) {
+                    case 3:
+                    case 4:
+                        targetOhUnit = "ms";
+                        break;
+                }
+                break;
+            case 9:
+                switch (subNumber) {
+                    // special case: temperature deltas specified in different units
+                    // ignore the offset, but run a conversion to handle prefixes like mK
+                    // scaleFactor is needed to properly handle °F
+                    case 2: {
+                        final String unit = qt.getUnit().toString();
+                        // find out if the unit is based on °C or K, getSystemUnit() does not help here as it always
+                        // gives "K"
+                        if (unit.contains("°C")) {
+                            targetOhUnit = "°C";
+                        } else if (unit.contains("°F")) {
+                            targetOhUnit = "°F";
+                            scaleFactor = 5.0 / 9.0;
+                        } else if (unit.contains("K")) {
+                            targetOhUnit = "K";
+                        } else {
+                            targetOhUnit = "";
+                        }
+                        break;
+                    }
+                    case 3: {
+                        final String unit = qt.getUnit().toString();
+                        if (unit.contains("°C")) {
+                            targetOhUnit = "°C/h";
+                        } else if (unit.contains("°F")) {
+                            targetOhUnit = "°F/h";
+                            scaleFactor = 5.0 / 9.0;
+                        } else if (unit.contains("K")) {
+                            targetOhUnit = "K/h";
+                        } else {
+                            targetOhUnit = "";
+                        }
+                        break;
+                    }
+                    case 23: {
+                        final String unit = qt.getUnit().toString();
+                        if (unit.contains("°C")) {
+                            targetOhUnit = "°C/%";
+                        } else if (unit.contains("°F")) {
+                            targetOhUnit = "°F/%";
+                            scaleFactor = 5.0 / 9.0;
+                        } else if (unit.contains("K")) {
+                            targetOhUnit = "K/%";
+                        } else {
+                            targetOhUnit = "";
+                        }
+                        break;
+                    }
+                }
+                break;
+            case 12:
+                switch (subNumber) {
+                    case 1200:
+                        // Calimero uses "litre"
+                        targetOhUnit = "l";
+                        break;
+                }
+                break;
+            case 13:
+                switch (subNumber) {
+                    case 12:
+                    case 15:
+                        // Calimero uses VARh, OH uses varh
+                        targetOhUnit = targetOhUnit.replace("VARh", "varh");
+                        break;
+                    case 14:
+                        // OH does not accept kVAh, only VAh
+                        targetOhUnit = targetOhUnit.replace("kVAh", "VAh");
+                        scaleFactor = 1.0 / 1000.0;
+                        break;
+                }
+                break;
+
+            case 14:
+                targetOhUnit = targetOhUnit.replace("Ω\u207B¹", "S");
+                // Calimero uses a special unicode character to specify units like m*s^-2
+                // this needs to be rewritten to m/s²
+                final int posMinus = targetOhUnit.indexOf("\u207B");
+                if (posMinus > 0) {
+                    targetOhUnit = targetOhUnit.substring(0, posMinus - 1) + "/" + targetOhUnit.charAt(posMinus - 1)
+                            + targetOhUnit.substring(posMinus + 1);
+                }
+                switch (subNumber) {
+                    case 8:
+                        // OH does not support unut Js, need to expand
+                        targetOhUnit = "J*s";
+                        break;
+                    case 21:
+                        targetOhUnit = "C*m";
+                        break;
+                    case 24:
+                        targetOhUnit = "C";
+                        break;
+                    case 29:
+                    case 47:
+                        targetOhUnit = "A*m²";
+                        break;
+                    case 40:
+                        if (qt.getUnit().toString().contains("J")) {
+                            targetOhUnit = "J";
+                        } else {
+                            targetOhUnit = "lm*s";
+                        }
+                        break;
+                    case 61:
+                        targetOhUnit = "Ohm*m";
+                        break;
+                    case 75:
+                        targetOhUnit = "N*m";
+                        break;
+                }
+                break;
+            case 29:
+                switch (subNumber) {
+                    case 12:
+                        // Calimero uses VARh, OH uses varh
+                        targetOhUnit = targetOhUnit.replace("VARh", "varh");
+                        break;
+                }
+                break;
+        }
+        // replace e.g. m3 by m³
+        targetOhUnit = targetOhUnit.replace("3", "³").replace("2", "²");
+
+        final QuantityType<?> result = qt.toUnit(targetOhUnit);
+        if (result == null) {
+            throw new KNXException("incompatible types: " + qt.getUnit().toString() + ", " + targetOhUnit);
+        }
+        return String.valueOf(result.doubleValue() * scaleFactor);
+    }
+
     @Override
     public String toDPTValue(Type type, String dptID) {
         DPT dpt;
@@ -659,6 +821,9 @@ public class KNXCoreTypeMapper implements KNXTypeMapper {
                 return type.toString();
             } else if (type instanceof DateTimeType) {
                 return formatDateTime((DateTimeType) type, dptID);
+            } else if (type instanceof QuantityType) {
+                final QuantityType<?> qt = (QuantityType<?>) type;
+                return quantityTypeToDPTValue(qt, mainNumber, subNumber, dpt.getUnit());
             }
         } catch (Exception e) {
             logger.warn("An exception occurred converting type {} to dpt id {}: error message={}", type, dptID,
index fae66e40024f6003b5decc26c100ec0d3df41e05..439998b347218f590407cae827d0ac27129d0bb7 100644 (file)
@@ -16,7 +16,9 @@ import static org.junit.jupiter.api.Assertions.*;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.junit.jupiter.api.Test;
+import org.openhab.core.library.types.DateTimeType;
 import org.openhab.core.library.types.DecimalType;
+import org.openhab.core.library.types.QuantityType;
 
 /**
  *
@@ -31,4 +33,249 @@ public class KNXCoreTypeMapperTest {
         assertEquals("3", new KNXCoreTypeMapper().toDPTValue(new DecimalType("3"), "17.001"));
         assertEquals("3", new KNXCoreTypeMapper().toDPTValue(new DecimalType("3.0"), "17.001"));
     }
+
+    @Test
+    @SuppressWarnings("null")
+    public void testToDPT5ValueFromQuantityType() {
+        assertEquals("80.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("80 %"), "5.001"));
+
+        assertEquals("180.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("180 °"), "5.003"));
+        assertTrue(new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("3.14 rad"), "5.003").startsWith("179."));
+        assertEquals("80.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("80 %"), "5.004"));
+    }
+
+    @Test
+    @SuppressWarnings("null")
+    public void testToDPT7ValueFromQuantityType() {
+        assertEquals("1000.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1000 ms"), "7.002"));
+        assertEquals("1000.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1000 ms"), "7.003"));
+        assertEquals("1000.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1000 ms"), "7.004"));
+        assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1000 ms"), "7.005"));
+        assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("60 s"), "7.006"));
+        assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("60 min"), "7.007"));
+
+        assertEquals("1000.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 m"), "7.011"));
+        assertEquals("1000.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1000 mA"), "7.012"));
+        assertEquals("1000.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1000 lx"), "7.013"));
+
+        assertEquals("3000.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("3000 K"), "7.600"));
+    }
+
+    @Test
+    @SuppressWarnings("null")
+    public void testToDPT8ValueFromQuantityType() {
+        assertEquals("1000.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1000 ms"), "8.002"));
+        assertEquals("1000.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1000 ms"), "8.003"));
+        assertEquals("1000.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1000 ms"), "8.004"));
+        assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1000 ms"), "8.005"));
+        assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("60 s"), "8.006"));
+        assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("60 min"), "8.007"));
+
+        assertEquals("180.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("180 °"), "8.011"));
+        assertEquals("1000.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 km"), "8.012"));
+    }
+
+    @Test
+    @SuppressWarnings("null")
+    public void testToDPT9ValueFromQuantityType() {
+        assertEquals("23.1", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("23.1 °C"), "9.001"));
+        assertEquals("5.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("41 °F"), "9.001"));
+        assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("274.15 K"), "9.001"));
+        assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 K"), "9.002"));
+        assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1000 mK"), "9.002"));
+        assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 °C"), "9.002"));
+        assertTrue(new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 °F"), "9.002").startsWith("0.55"));
+        assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 K/h"), "9.003"));
+        assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 °C/h"), "9.003"));
+        assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1000 mK/h"), "9.003"));
+        assertEquals("600.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("10 K/min"), "9.003"));
+        assertEquals("100.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("100 lx"), "9.004"));
+        assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 m/s"), "9.005"));
+        assertTrue(new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1.94 kn"), "9.005").startsWith("0.99"));
+        assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("3.6 km/h"), "9.005"));
+        assertEquals("456.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("456 Pa"), "9.006"));
+        assertEquals("70.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("70 %"), "9.007"));
+        assertEquals("8.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("8 ppm"), "9.008"));
+        assertEquals("9.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("9 m³/h"), "9.009"));
+        assertEquals("10.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("10 s"), "9.010"));
+        assertEquals("11.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("0.011 s"), "9.011"));
+
+        assertEquals("20.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("20 mV"), "9.020"));
+        assertEquals("20.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("0.02 V"), "9.020"));
+        assertEquals("21.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("21 mA"), "9.021"));
+        assertEquals("21.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("0.021 A"), "9.021"));
+        assertEquals("12.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("12 W/m²"), "9.022"));
+        assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 K/%"), "9.023"));
+        assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 °C/%"), "9.023"));
+        assertTrue(new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 °F/%"), "9.023").startsWith("0.55"));
+        assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 kW"), "9.024"));
+        assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 l/h"), "9.025"));
+        assertEquals("60.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 l/min"), "9.025"));
+        assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 l/m²"), "9.026"));
+        assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 °F"), "9.027"));
+        assertTrue(new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("-12 °C"), "9.027").startsWith("10."));
+        assertEquals("10.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("10 km/h"), "9.028"));
+        assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 g/m³"), "9.029"));
+        assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 µg/m³"), "9.030"));
+    }
+
+    @Test
+    @SuppressWarnings("null")
+    public void testToDPT10ValueFromQuantityType() {
+        // DateTimeTyype, not QuantityType
+        assertEquals("Wed, 17:30:00",
+                new KNXCoreTypeMapper().toDPTValue(new DateTimeType("2019-06-12T17:30:00Z"), "10.001"));
+    }
+
+    @Test
+    @SuppressWarnings("null")
+    public void testToDPT11ValueFromQuantityType() {
+        // DateTimeTyype, not QuantityType
+        assertEquals("2019-06-12",
+                new KNXCoreTypeMapper().toDPTValue(new DateTimeType("2019-06-12T17:30:00Z"), "11.001"));
+    }
+
+    @Test
+    @SuppressWarnings("null")
+    public void testToDPT12ValueFromQuantityType() {
+        // 12.001: dimensionless
+
+        assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 s"), "12.100"));
+        assertEquals("2.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("2 min"), "12.101"));
+        assertEquals("3.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("3 h"), "12.102"));
+
+        assertEquals("1000.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 m^3"), "12.1200"));
+        assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 l"), "12.1200"));
+        assertEquals("2.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("2 m³"), "12.1201"));
+    }
+
+    @Test
+    @SuppressWarnings("null")
+    public void testToDPT13ValueFromQuantityType() {
+        // 13.001 dimensionless
+        assertEquals("24.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("24 m³/h"), "13.002"));
+        assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("24 m³/d"), "13.002"));
+
+        assertEquals("42.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("42 Wh"), "13.010"));
+        assertEquals("42.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("42 VAh"), "13.011"));
+        assertEquals("42.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("42 varh"), "13.012"));
+        assertEquals("42.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("42 kWh"), "13.013"));
+        assertEquals("4.2", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("4200 VAh"), "13.014"));
+        assertEquals("42.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("42 kvarh"), "13.015"));
+        assertEquals("42.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("42 MWh"), "13.016"));
+
+        assertEquals("42.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("42 s"), "13.100"));
+
+        // DPTs 13.1200 and 13.1201 not available in Calimero 2.5, fix once we update
+        assertNotEquals("42.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("42 l"), "13.1200"));
+        assertNotEquals("42.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("42 m³"), "13.1201"));
+    }
+
+    @Test
+    @SuppressWarnings("null")
+    public void testToDPT14ValueFromQuantityType() {
+        assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 m/s²"), "14.000"));
+        assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 rad/s²"), "14.001"));
+        assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 J/mol"), "14.002"));
+        assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 /s"), "14.003"));
+        assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 mol"), "14.004"));
+
+        assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 rad"), "14.006"));
+        assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 °"), "14.007"));
+        assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 J*s"), "14.008"));
+        assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 rad/s"), "14.009"));
+        assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 m²"), "14.010"));
+        assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 F"), "14.011"));
+        assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 C/m²"), "14.012"));
+        assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 C/m³"), "14.013"));
+        assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 m²/N"), "14.014"));
+        assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 S"), "14.015"));
+        assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 S/m"), "14.016"));
+        assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 kg/m³"), "14.017"));
+        assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 C"), "14.018"));
+        assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 A"), "14.019"));
+        assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 A/m²"), "14.020"));
+        assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 C*m"), "14.021"));
+        assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 C/m²"), "14.022"));
+        assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 V/m"), "14.023"));
+        assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 C"), "14.024"));
+        assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 C/m²"), "14.025"));
+        assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 C/m²"), "14.026"));
+        assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 V"), "14.027"));
+        assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 V"), "14.028"));
+        assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 A*m²"), "14.029"));
+        assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 V"), "14.030"));
+        assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 J"), "14.031"));
+        assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 N"), "14.032"));
+        assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 Hz"), "14.033"));
+        assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 rad/s"), "14.034"));
+        assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 J/K"), "14.035"));
+        assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 W"), "14.036"));
+        assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 J"), "14.037"));
+        assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 Ohm"), "14.038"));
+        assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 m"), "14.039"));
+        assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 J"), "14.040"));
+        assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 lm*s"), "14.040"));
+        assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 cd/m²"), "14.041"));
+        assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 lm"), "14.042"));
+        assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 cd"), "14.043"));
+        assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 A/m"), "14.044"));
+        assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 Wb"), "14.045"));
+        assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 T"), "14.046"));
+        assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 A*m²"), "14.047"));
+        assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 T"), "14.048"));
+        assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 A/m"), "14.049"));
+        assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 A"), "14.050"));
+        assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 kg"), "14.051"));
+        assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 kg/s"), "14.052"));
+        assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 N/s"), "14.053"));
+        assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 rad"), "14.054"));
+        assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 °"), "14.055"));
+        assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 W"), "14.056"));
+        // 14.057: dimensionless
+        assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 Pa"), "14.058"));
+        assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 Ohm"), "14.059"));
+        assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 Ohm"), "14.060"));
+        assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 Ohm*m"), "14.061"));
+        assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 H"), "14.062"));
+        assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 sr"), "14.063"));
+        assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 W/m²"), "14.064"));
+        assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 m/s"), "14.065"));
+        assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 Pa"), "14.066"));
+        assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 N/m"), "14.067"));
+        assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 °C"), "14.068"));
+        assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 K"), "14.069"));
+        assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 K"), "14.070"));
+        assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 J/K"), "14.071"));
+        assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 W/m/K"), "14.072"));
+        assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 V/K"), "14.073"));
+        assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 s"), "14.074"));
+        assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 N*m"), "14.075"));
+        assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 J"), "14.075"));
+        assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 m³"), "14.076"));
+        assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 m³/s"), "14.077"));
+        assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 N"), "14.078"));
+        assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 J"), "14.079"));
+        assertEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 VA"), "14.080"));
+
+        // DPTs 14.1200 and 14.1201 not available in Calimero 2.5, fix once we update
+        assertNotEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 m³/h"), "14.1200"));
+        assertNotEquals("1.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("1 l/s"), "14.1201"));
+    }
+
+    @Test
+    @SuppressWarnings("null")
+    public void testToDPT19ValueFromQuantityType() {
+        // DateTimeTyype, not QuantityType
+        assertEquals("2019-06-12 17:30:00",
+                new KNXCoreTypeMapper().toDPTValue(new DateTimeType("2019-06-12T17:30:00Z"), "19.001"));
+    }
+
+    @Test
+    @SuppressWarnings("null")
+    public void testToDPT29ValueFromQuantityType() {
+        assertEquals("42.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("42 Wh"), "29.010"));
+        assertEquals("42.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("42 VAh"), "29.011"));
+        assertEquals("42.0", new KNXCoreTypeMapper().toDPTValue(new QuantityType<>("42 varh"), "29.012"));
+    }
 }