Skip to content

Commit

Permalink
[knx] Allow sending items with units to KNX bus. (openhab#12675)
Browse files Browse the repository at this point in the history
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 openhab#10706.

Signed-off-by: Holger Friedrich <mail@holger-friedrich.de>
Signed-off-by: Andras Uhrin <andras.uhrin@gmail.com>
  • Loading branch information
holgerfriedrich authored and andrasU committed Nov 12, 2022
1 parent b2b232a commit 21c7202
Show file tree
Hide file tree
Showing 4 changed files with 422 additions and 3 deletions.
4 changes: 4 additions & 0 deletions bundles/org.openhab.binding.knx/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@
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;
Expand Down Expand Up @@ -171,9 +173,10 @@ protected final Set<String> asSet(String... values) {
}
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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
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;
Expand Down Expand Up @@ -231,7 +232,6 @@ public KNXCoreTypeMapper() {
* 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);
Expand All @@ -250,6 +250,7 @@ public KNXCoreTypeMapper() {
* 8.007: DPT_DeltaTimeHrs
* 8.010: DPT_Percent_V16
* 8.011: DPT_Rotation_Angle
* 8.012: DPT_Length_m
*/
dptMainTypeMap.put(8, DecimalType.class);

Expand All @@ -275,6 +276,8 @@ public KNXCoreTypeMapper() {
* 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 */
Expand All @@ -300,6 +303,11 @@ public KNXCoreTypeMapper() {
* 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 */
Expand All @@ -316,7 +324,10 @@ public KNXCoreTypeMapper() {
* 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 */
Expand Down Expand Up @@ -404,6 +415,7 @@ public KNXCoreTypeMapper() {
* 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 */
Expand Down Expand Up @@ -571,6 +583,156 @@ public KNXCoreTypeMapper() {
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;
Expand Down Expand Up @@ -659,6 +821,9 @@ public String toDPTValue(Type type, String dptID) {
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,
Expand Down
Loading

0 comments on commit 21c7202

Please sign in to comment.