]> git.basschouten.com Git - openhab-addons.git/commitdiff
[mqtt.generic] Add UOM to inbound values for MQTT Channels (#10727)
authorJames Melville <jamesmelville@gmail.com>
Sun, 9 Jan 2022 09:05:53 +0000 (09:05 +0000)
committerGitHub <noreply@github.com>
Sun, 9 Jan 2022 09:05:53 +0000 (10:05 +0100)
* Add UOM for MQTT Channels

Signed-off-by: James Melville <jamesmelville@gmail.com>
* Fix dependencies

Signed-off-by: James Melville <jamesmelville@gmail.com>
* Simplify units parsing, remove channelUID from NumberValue constructor

Signed-off-by: James Melville <jamesmelville@gmail.com>
* Simplify pattern

Signed-off-by: James Melville <jamesmelville@gmail.com>
* Fix tests

Signed-off-by: James Melville <jamesmelville@gmail.com>
* Correct Units reference

Signed-off-by: James Melville <jamesmelville@gmail.com>
* Correct homeassistant binding changes

Signed-off-by: James Melville <jamesmelville@gmail.com>
* Wrap precision in temperature unit definition

Signed-off-by: James Melville <jamesmelville@gmail.com>
* Use BigDecimal for precision

Signed-off-by: James Melville <jamesmelville@gmail.com>
* Use BigDecimal throughout

Signed-off-by: James Melville <jamesmelville@gmail.com>
* Fix SAT

Signed-off-by: James Melville <jamesmelville@gmail.com>
* Inverty equals check

Signed-off-by: James Melville <jamesmelville@gmail.com>
bundles/org.openhab.binding.mqtt.generic/src/main/java/org/openhab/binding/mqtt/generic/values/NumberValue.java
bundles/org.openhab.binding.mqtt.generic/src/main/java/org/openhab/binding/mqtt/generic/values/ValueFactory.java
bundles/org.openhab.binding.mqtt.generic/src/test/java/org/openhab/binding/mqtt/generic/ChannelStateTests.java
bundles/org.openhab.binding.mqtt.generic/src/test/java/org/openhab/binding/mqtt/generic/values/ValueTests.java
bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Climate.java
bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Sensor.java
bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/ClimateTests.java
bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/HAConfigurationTests.java
bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/SensorTests.java
bundles/org.openhab.binding.mqtt.homie/src/main/java/org/openhab/binding/mqtt/homie/internal/homie300/Property.java

index e7afc3e6f8d5b9fc13bd3e83cd13a82a4984820a..de6f5c6f28ca1d2099713e0750b94fab44acb94f 100644 (file)
@@ -16,6 +16,8 @@ import java.math.BigDecimal;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
+import javax.measure.Unit;
+
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.eclipse.jdt.annotation.Nullable;
 import org.openhab.core.library.CoreItemFactory;
@@ -47,19 +49,19 @@ public class NumberValue extends Value {
     private final @Nullable BigDecimal min;
     private final @Nullable BigDecimal max;
     private final BigDecimal step;
-    private final String unit;
+    private final Unit<?> unit;
 
     public NumberValue(@Nullable BigDecimal min, @Nullable BigDecimal max, @Nullable BigDecimal step,
-            @Nullable String unit) {
+            @Nullable Unit<?> unit) {
         super(CoreItemFactory.NUMBER, Stream.of(QuantityType.class, IncreaseDecreaseType.class, UpDownType.class)
                 .collect(Collectors.toList()));
         this.min = min;
         this.max = max;
         this.step = step == null ? BigDecimal.ONE : step;
-        this.unit = unit == null ? "" : unit;
+        this.unit = unit != null ? unit : Units.ONE;
     }
 
-    protected boolean checkConditions(BigDecimal newValue, DecimalType oldvalue) {
+    protected boolean checkConditions(BigDecimal newValue) {
         BigDecimal min = this.min;
         if (min != null && newValue.compareTo(min) == -1) {
             logger.trace("Number not accepted as it is below the configured minimum");
@@ -90,49 +92,54 @@ public class NumberValue extends Value {
 
     @Override
     public void update(Command command) throws IllegalArgumentException {
-        DecimalType oldvalue = (state == UnDefType.UNDEF) ? new DecimalType() : (DecimalType) state;
         BigDecimal newValue = null;
         if (command instanceof DecimalType) {
-            if (!checkConditions(((DecimalType) command).toBigDecimal(), oldvalue)) {
-                return;
-            }
-            state = (DecimalType) command;
+            newValue = ((DecimalType) command).toBigDecimal();
         } else if (command instanceof IncreaseDecreaseType || command instanceof UpDownType) {
+            BigDecimal oldValue = getOldValue();
             if (command == IncreaseDecreaseType.INCREASE || command == UpDownType.UP) {
-                newValue = oldvalue.toBigDecimal().add(step);
+                newValue = oldValue.add(step);
             } else {
-                newValue = oldvalue.toBigDecimal().subtract(step);
-            }
-            if (!checkConditions(newValue, oldvalue)) {
-                return;
+                newValue = oldValue.subtract(step);
             }
-            state = new DecimalType(newValue);
         } else if (command instanceof QuantityType<?>) {
-            QuantityType<?> qType = (QuantityType<?>) command;
-
-            if (qType.getUnit().isCompatible(Units.ONE)) {
-                newValue = qType.toBigDecimal();
-            } else {
-                qType = qType.toUnit(unit);
-                if (qType != null) {
-                    newValue = qType.toBigDecimal();
-                }
-            }
-            if (newValue != null) {
-                if (!checkConditions(newValue, oldvalue)) {
-                    return;
-                }
-                state = new DecimalType(newValue);
-            }
+            newValue = getQuantityTypeAsDecimal((QuantityType<?>) command);
         } else {
             newValue = new BigDecimal(command.toString());
-            if (!checkConditions(newValue, oldvalue)) {
-                return;
-            }
+        }
+        if (!checkConditions(newValue)) {
+            return;
+        }
+        // items with units specified in the label in the UI but no unit on mqtt are stored as
+        // DecimalType to avoid conversions (e.g. % expects 0-1 rather than 0-100)
+        if (!Units.ONE.equals(unit)) {
+            state = new QuantityType<>(newValue, unit);
+        } else {
             state = new DecimalType(newValue);
         }
     }
 
+    private BigDecimal getOldValue() {
+        BigDecimal val = BigDecimal.ZERO;
+        if (state instanceof DecimalType) {
+            val = ((DecimalType) state).toBigDecimal();
+        } else if (state instanceof QuantityType<?>) {
+            val = ((QuantityType<?>) state).toBigDecimal();
+        }
+        return val;
+    }
+
+    private BigDecimal getQuantityTypeAsDecimal(QuantityType<?> qType) {
+        BigDecimal val = qType.toBigDecimal();
+        if (!qType.getUnit().isCompatible(Units.ONE)) {
+            QuantityType<?> convertedType = qType.toUnit(unit);
+            if (convertedType != null) {
+                val = convertedType.toBigDecimal();
+            }
+        }
+        return val;
+    }
+
     @Override
     public StateDescriptionFragmentBuilder createStateDescription(boolean readOnly) {
         StateDescriptionFragmentBuilder builder = super.createStateDescription(readOnly);
@@ -144,10 +151,6 @@ public class NumberValue extends Value {
         if (min != null) {
             builder = builder.withMinimum(min);
         }
-        builder = builder.withStep(step);
-        if (this.unit.length() > 0) {
-            builder = builder.withPattern("%s " + this.unit.replace("%", "%%"));
-        }
-        return builder;
+        return builder.withStep(step).withPattern("%s %unit%");
     }
 }
index 12c20895f49231d197e8c17d7edee02f743a142c..70bc0ebebfc3db191f2007ea2e34d37d913831a4 100644 (file)
@@ -16,6 +16,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.openhab.binding.mqtt.generic.ChannelConfig;
 import org.openhab.binding.mqtt.generic.internal.MqttBindingConstants;
 import org.openhab.binding.mqtt.generic.mapping.ColorMode;
+import org.openhab.core.types.util.UnitUtils;
 
 /**
  * A factory t
@@ -24,6 +25,7 @@ import org.openhab.binding.mqtt.generic.mapping.ColorMode;
  */
 @NonNullByDefault
 public class ValueFactory {
+
     /**
      * Creates a new channel state value.
      *
@@ -47,7 +49,7 @@ public class ValueFactory {
                 value = new LocationValue();
                 break;
             case MqttBindingConstants.NUMBER:
-                value = new NumberValue(config.min, config.max, config.step, config.unit);
+                value = new NumberValue(config.min, config.max, config.step, UnitUtils.parseUnit(config.unit));
                 break;
             case MqttBindingConstants.DIMMER:
                 value = new PercentageValue(config.min, config.max, config.step, config.on, config.off);
index 5753c93727a8a8bc0dce4a06bc52444a7c50f2b2..75a582261e9acef2fb073a6aba32fa0f824408d2 100644 (file)
@@ -52,6 +52,7 @@ import org.openhab.core.io.transport.mqtt.MqttBrokerConnection;
 import org.openhab.core.library.types.HSBType;
 import org.openhab.core.library.types.RawType;
 import org.openhab.core.library.types.StringType;
+import org.openhab.core.library.unit.Units;
 import org.openhab.core.thing.ChannelUID;
 
 /**
@@ -185,6 +186,36 @@ public class ChannelStateTests {
         assertThat(value.getChannelState().toString(), is("16.0"));
     }
 
+    @Test
+    public void receiveDecimalUnitTest() {
+        NumberValue value = new NumberValue(null, null, new BigDecimal(10), Units.WATT);
+        ChannelState c = spy(new ChannelState(config, channelUID, value, channelStateUpdateListener));
+        c.start(connection, mock(ScheduledExecutorService.class), 100);
+
+        c.processMessage("state", "15".getBytes());
+        assertThat(value.getChannelState().toString(), is("15 W"));
+
+        c.processMessage("state", "INCREASE".getBytes());
+        assertThat(value.getChannelState().toString(), is("25 W"));
+
+        c.processMessage("state", "DECREASE".getBytes());
+        assertThat(value.getChannelState().toString(), is("15 W"));
+
+        verify(channelStateUpdateListener, times(3)).updateChannelState(eq(channelUID), any());
+    }
+
+    @Test
+    public void receiveDecimalAsPercentageUnitTest() {
+        NumberValue value = new NumberValue(null, null, new BigDecimal(10), Units.PERCENT);
+        ChannelState c = spy(new ChannelState(config, channelUID, value, channelStateUpdateListener));
+        c.start(connection, mock(ScheduledExecutorService.class), 100);
+
+        c.processMessage("state", "63.7".getBytes());
+        assertThat(value.getChannelState().toString(), is("63.7 %"));
+
+        verify(channelStateUpdateListener, times(1)).updateChannelState(eq(channelUID), any());
+    }
+
     @Test
     public void receivePercentageTest() {
         PercentageValue value = new PercentageValue(new BigDecimal(-100), new BigDecimal(100), new BigDecimal(10), null,
index 6d9d02f170edf25d7d320715fe728c14eeebe9ef..eb39ed24351aaac92b949202600b3062dd39b277 100644 (file)
@@ -26,8 +26,11 @@ 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.StringType;
 import org.openhab.core.library.types.UpDownType;
+import org.openhab.core.library.unit.MetricPrefix;
+import org.openhab.core.library.unit.Units;
 import org.openhab.core.types.Command;
 import org.openhab.core.types.TypeParser;
 
@@ -160,6 +163,39 @@ public class ValueTests {
         assertThat(v.getChannelState(), is(OpenClosedType.OPEN));
     }
 
+    @Test
+    public void numberUpdate() {
+        NumberValue v = new NumberValue(null, null, new BigDecimal(10), Units.WATT);
+
+        // Test with command with units
+        v.update(new QuantityType<>(20, Units.WATT));
+        assertThat(v.getMQTTpublishValue(null), is("20"));
+        assertThat(v.getChannelState(), is(new QuantityType<>(20, Units.WATT)));
+        v.update(new QuantityType<>(20, MetricPrefix.KILO(Units.WATT)));
+        assertThat(v.getMQTTpublishValue(null), is("20000"));
+        assertThat(v.getChannelState(), is(new QuantityType<>(20, MetricPrefix.KILO(Units.WATT))));
+
+        // Test with command without units
+        v.update(new QuantityType<>("20"));
+        assertThat(v.getMQTTpublishValue(null), is("20"));
+        assertThat(v.getChannelState(), is(new QuantityType<>(20, Units.WATT)));
+    }
+
+    @Test
+    public void numberPercentageUpdate() {
+        NumberValue v = new NumberValue(null, null, new BigDecimal(10), Units.PERCENT);
+
+        // Test with command with units
+        v.update(new QuantityType<>(20, Units.PERCENT));
+        assertThat(v.getMQTTpublishValue(null), is("20"));
+        assertThat(v.getChannelState(), is(new QuantityType<>(20, Units.PERCENT)));
+
+        // Test with command without units
+        v.update(new QuantityType<>("20"));
+        assertThat(v.getMQTTpublishValue(null), is("20"));
+        assertThat(v.getChannelState(), is(new QuantityType<>(20, Units.PERCENT)));
+    }
+
     @Test
     public void rollershutterUpdateWithStrings() {
         RollershutterValue v = new RollershutterValue("fancyON", "fancyOff", "fancyStop");
index 09021686dfcf9b90b821d8ffdacd98d167c4d76f..41531396a6122e4849ed6fa0fb2ec1c8f269c6f2 100644 (file)
@@ -17,6 +17,9 @@ import java.util.Arrays;
 import java.util.List;
 import java.util.function.Predicate;
 
+import javax.measure.Unit;
+import javax.measure.quantity.Temperature;
+
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.eclipse.jdt.annotation.Nullable;
 import org.openhab.binding.mqtt.generic.ChannelStateUpdateListener;
@@ -27,6 +30,8 @@ import org.openhab.binding.mqtt.generic.values.Value;
 import org.openhab.binding.mqtt.homeassistant.internal.ComponentChannel;
 import org.openhab.binding.mqtt.homeassistant.internal.config.dto.AbstractChannelConfiguration;
 import org.openhab.core.library.types.StringType;
+import org.openhab.core.library.unit.ImperialUnits;
+import org.openhab.core.library.unit.SIUnits;
 import org.openhab.core.types.Command;
 import org.openhab.core.types.State;
 
@@ -53,10 +58,28 @@ public class Climate extends AbstractComponent<Climate.ChannelConfiguration> {
     public static final String TEMPERATURE_LOW_CH_ID = "temperatureLow";
     public static final String POWER_CH_ID = "power";
 
-    private static final String CELSIUM = "C";
-    private static final String FAHRENHEIT = "F";
-    private static final float DEFAULT_CELSIUM_PRECISION = 0.1f;
-    private static final float DEFAULT_FAHRENHEIT_PRECISION = 1f;
+    public static enum TemperatureUnit {
+        @SerializedName("C")
+        CELSIUS(SIUnits.CELSIUS, new BigDecimal("0.1")),
+        @SerializedName("F")
+        FAHRENHEIT(ImperialUnits.FAHRENHEIT, BigDecimal.ONE);
+
+        private final Unit<Temperature> unit;
+        private final BigDecimal defaultPrecision;
+
+        TemperatureUnit(Unit<Temperature> unit, BigDecimal defaultPrecision) {
+            this.unit = unit;
+            this.defaultPrecision = defaultPrecision;
+        }
+
+        public Unit<Temperature> getUnit() {
+            return unit;
+        }
+
+        public BigDecimal getDefaultPrecision() {
+            return defaultPrecision;
+        }
+    }
 
     private static final String ACTION_OFF = "off";
     private static final State ACTION_OFF_STATE = new StringType(ACTION_OFF);
@@ -175,14 +198,14 @@ public class Climate extends AbstractComponent<Climate.ChannelConfiguration> {
 
         protected Integer initial = 21;
         @SerializedName("max_temp")
-        protected @Nullable Float maxTemp;
+        protected @Nullable BigDecimal maxTemp;
         @SerializedName("min_temp")
-        protected @Nullable Float minTemp;
+        protected @Nullable BigDecimal minTemp;
         @SerializedName("temperature_unit")
-        protected String temperatureUnit = CELSIUM; // System unit by default
+        protected TemperatureUnit temperatureUnit = TemperatureUnit.CELSIUS; // System unit by default
         @SerializedName("temp_step")
-        protected Float tempStep = 1f;
-        protected @Nullable Float precision;
+        protected BigDecimal tempStep = BigDecimal.ONE;
+        protected @Nullable BigDecimal precision;
         @SerializedName("send_if_off")
         protected Boolean sendIfOff = true;
     }
@@ -190,13 +213,8 @@ public class Climate extends AbstractComponent<Climate.ChannelConfiguration> {
     public Climate(ComponentFactory.ComponentConfiguration componentConfiguration) {
         super(componentConfiguration, ChannelConfiguration.class);
 
-        BigDecimal minTemp = channelConfiguration.minTemp != null ? BigDecimal.valueOf(channelConfiguration.minTemp)
-                : null;
-        BigDecimal maxTemp = channelConfiguration.maxTemp != null ? BigDecimal.valueOf(channelConfiguration.maxTemp)
-                : null;
-        float precision = channelConfiguration.precision != null ? channelConfiguration.precision
-                : (FAHRENHEIT.equals(channelConfiguration.temperatureUnit) ? DEFAULT_FAHRENHEIT_PRECISION
-                        : DEFAULT_CELSIUM_PRECISION);
+        BigDecimal precision = channelConfiguration.precision != null ? channelConfiguration.precision
+                : channelConfiguration.temperatureUnit.getDefaultPrecision();
         final ChannelStateUpdateListener updateListener = componentConfiguration.getUpdateListener();
 
         ComponentChannel actionChannel = buildOptionalChannel(ACTION_CH_ID,
@@ -214,7 +232,8 @@ public class Climate extends AbstractComponent<Climate.ChannelConfiguration> {
                 channelConfiguration.awayModeStateTopic, commandFilter);
 
         buildOptionalChannel(CURRENT_TEMPERATURE_CH_ID,
-                new NumberValue(minTemp, maxTemp, BigDecimal.valueOf(precision), channelConfiguration.temperatureUnit),
+                new NumberValue(channelConfiguration.minTemp, channelConfiguration.maxTemp, precision,
+                        channelConfiguration.temperatureUnit.getUnit()),
                 updateListener, null, null, channelConfiguration.currentTemperatureTemplate,
                 channelConfiguration.currentTemperatureTopic, commandFilter);
 
@@ -237,22 +256,22 @@ public class Climate extends AbstractComponent<Climate.ChannelConfiguration> {
                 channelConfiguration.swingStateTemplate, channelConfiguration.swingStateTopic, commandFilter);
 
         buildOptionalChannel(TEMPERATURE_CH_ID,
-                new NumberValue(minTemp, maxTemp, BigDecimal.valueOf(channelConfiguration.tempStep),
-                        channelConfiguration.temperatureUnit),
+                new NumberValue(channelConfiguration.minTemp, channelConfiguration.maxTemp,
+                        channelConfiguration.tempStep, channelConfiguration.temperatureUnit.getUnit()),
                 updateListener, channelConfiguration.temperatureCommandTemplate,
                 channelConfiguration.temperatureCommandTopic, channelConfiguration.temperatureStateTemplate,
                 channelConfiguration.temperatureStateTopic, commandFilter);
 
         buildOptionalChannel(TEMPERATURE_HIGH_CH_ID,
-                new NumberValue(minTemp, maxTemp, BigDecimal.valueOf(channelConfiguration.tempStep),
-                        channelConfiguration.temperatureUnit),
+                new NumberValue(channelConfiguration.minTemp, channelConfiguration.maxTemp,
+                        channelConfiguration.tempStep, channelConfiguration.temperatureUnit.getUnit()),
                 updateListener, channelConfiguration.temperatureHighCommandTemplate,
                 channelConfiguration.temperatureHighCommandTopic, channelConfiguration.temperatureHighStateTemplate,
                 channelConfiguration.temperatureHighStateTopic, commandFilter);
 
         buildOptionalChannel(TEMPERATURE_LOW_CH_ID,
-                new NumberValue(minTemp, maxTemp, BigDecimal.valueOf(channelConfiguration.tempStep),
-                        channelConfiguration.temperatureUnit),
+                new NumberValue(channelConfiguration.minTemp, channelConfiguration.maxTemp,
+                        channelConfiguration.tempStep, channelConfiguration.temperatureUnit.getUnit()),
                 updateListener, channelConfiguration.temperatureLowCommandTemplate,
                 channelConfiguration.temperatureLowCommandTopic, channelConfiguration.temperatureLowStateTemplate,
                 channelConfiguration.temperatureLowStateTopic, commandFilter);
index 0451c276fa7c17e31682f591daa6ca2bd8d1f2dc..4b4bdcccba86846338a0538f3834bbcacf04bc5a 100644 (file)
@@ -23,6 +23,7 @@ import org.openhab.binding.mqtt.generic.values.TextValue;
 import org.openhab.binding.mqtt.generic.values.Value;
 import org.openhab.binding.mqtt.homeassistant.internal.config.dto.AbstractChannelConfiguration;
 import org.openhab.binding.mqtt.homeassistant.internal.listener.ExpireUpdateStateListener;
+import org.openhab.core.types.util.UnitUtils;
 
 import com.google.gson.annotations.SerializedName;
 
@@ -71,7 +72,7 @@ public class Sensor extends AbstractComponent<Sensor.ChannelConfiguration> {
         String uom = channelConfiguration.unitOfMeasurement;
 
         if (uom != null && !uom.isBlank()) {
-            value = new NumberValue(null, null, null, uom);
+            value = new NumberValue(null, null, null, UnitUtils.parseUnit(uom));
         } else {
             value = new TextValue();
         }
index ccb50ea55135f4548bf9207b160d6c266cccb04a..9234b0810c8c9fd3fef355500085f57a1a549404 100644 (file)
@@ -23,7 +23,10 @@ import org.openhab.binding.mqtt.generic.values.OnOffValue;
 import org.openhab.binding.mqtt.generic.values.TextValue;
 import org.openhab.core.library.types.DecimalType;
 import org.openhab.core.library.types.OnOffType;
+import org.openhab.core.library.types.QuantityType;
 import org.openhab.core.library.types.StringType;
+import org.openhab.core.library.unit.ImperialUnits;
+import org.openhab.core.library.unit.SIUnits;
 
 /**
  * Tests for {@link Climate}
@@ -82,10 +85,10 @@ public class ClimateTests extends AbstractComponentTests {
                         + "\"current_heating_setpoint\": \"24\"}");
         assertState(component, Climate.ACTION_CH_ID, new StringType("off"));
         assertState(component, Climate.AWAY_MODE_CH_ID, OnOffType.ON);
-        assertState(component, Climate.CURRENT_TEMPERATURE_CH_ID, new DecimalType(22.2));
+        assertState(component, Climate.CURRENT_TEMPERATURE_CH_ID, new QuantityType<>(22.2, SIUnits.CELSIUS));
         assertState(component, Climate.HOLD_CH_ID, new StringType("schedule"));
         assertState(component, Climate.MODE_CH_ID, new StringType("heat"));
-        assertState(component, Climate.TEMPERATURE_CH_ID, new DecimalType(24));
+        assertState(component, Climate.TEMPERATURE_CH_ID, new QuantityType<>(24, SIUnits.CELSIUS));
 
         component.getChannel(Climate.AWAY_MODE_CH_ID).getState().publishValue(OnOffType.OFF);
         assertPublished("zigbee2mqtt/th1/set/away_mode", "OFF");
@@ -146,10 +149,10 @@ public class ClimateTests extends AbstractComponentTests {
                         + "\"current_heating_setpoint\": \"24\"}");
         assertState(component, Climate.ACTION_CH_ID, new StringType("off"));
         assertState(component, Climate.AWAY_MODE_CH_ID, OnOffType.ON);
-        assertState(component, Climate.CURRENT_TEMPERATURE_CH_ID, new DecimalType(22.2));
+        assertState(component, Climate.CURRENT_TEMPERATURE_CH_ID, new QuantityType<>(22.2, SIUnits.CELSIUS));
         assertState(component, Climate.HOLD_CH_ID, new StringType("schedule"));
         assertState(component, Climate.MODE_CH_ID, new StringType("heat"));
-        assertState(component, Climate.TEMPERATURE_CH_ID, new DecimalType(24));
+        assertState(component, Climate.TEMPERATURE_CH_ID, new QuantityType<>(24, SIUnits.CELSIUS));
 
         // Climate is in OFF state
         component.getChannel(Climate.AWAY_MODE_CH_ID).getState().publishValue(OnOffType.OFF);
@@ -260,14 +263,14 @@ public class ClimateTests extends AbstractComponentTests {
         assertState(component, Climate.ACTION_CH_ID, new StringType("fan"));
         assertState(component, Climate.AUX_CH_ID, OnOffType.ON);
         assertState(component, Climate.AWAY_MODE_CH_ID, OnOffType.OFF);
-        assertState(component, Climate.CURRENT_TEMPERATURE_CH_ID, new DecimalType(35.5));
+        assertState(component, Climate.CURRENT_TEMPERATURE_CH_ID, new QuantityType<>(35.5, ImperialUnits.FAHRENHEIT));
         assertState(component, Climate.FAN_MODE_CH_ID, new StringType("p2"));
         assertState(component, Climate.HOLD_CH_ID, new StringType("u2"));
         assertState(component, Climate.MODE_CH_ID, new StringType("B1"));
         assertState(component, Climate.SWING_CH_ID, new StringType("G1"));
-        assertState(component, Climate.TEMPERATURE_CH_ID, new DecimalType(30));
-        assertState(component, Climate.TEMPERATURE_HIGH_CH_ID, new DecimalType(37));
-        assertState(component, Climate.TEMPERATURE_LOW_CH_ID, new DecimalType(20));
+        assertState(component, Climate.TEMPERATURE_CH_ID, new QuantityType<>(30, ImperialUnits.FAHRENHEIT));
+        assertState(component, Climate.TEMPERATURE_HIGH_CH_ID, new QuantityType<>(37, ImperialUnits.FAHRENHEIT));
+        assertState(component, Climate.TEMPERATURE_LOW_CH_ID, new QuantityType<>(20, ImperialUnits.FAHRENHEIT));
 
         component.getChannel(Climate.AUX_CH_ID).getState().publishValue(OnOffType.OFF);
         assertPublished("zigbee2mqtt/th1/aux", "OFF");
@@ -291,6 +294,7 @@ public class ClimateTests extends AbstractComponentTests {
         assertPublished("zigbee2mqtt/th1/power", "OFF");
     }
 
+    @Override
     protected Set<String> getConfigTopics() {
         return Set.of(CONFIG_TOPIC);
     }
index 384babbc2f42083e7f1e23a16f379a0e771d7179..9d3a405d2001d0a4d16699e55bd5c443ee18d9ab 100644 (file)
@@ -19,6 +19,7 @@ import static org.hamcrest.collection.IsIterableContainingInOrder.contains;
 import java.io.BufferedReader;
 import java.io.IOException;
 import java.io.InputStreamReader;
+import java.math.BigDecimal;
 import java.util.Arrays;
 import java.util.List;
 
@@ -171,18 +172,18 @@ public class HAConfigurationTests {
         assertThat(config.holdStateTemplate, is("{{ value_json.preset }}"));
         assertThat(config.holdStateTopic, is("zigbee2mqtt/th1"));
         assertThat(config.jsonAttributesTopic, is("zigbee2mqtt/th1"));
-        assertThat(config.maxTemp, is(35f));
-        assertThat(config.minTemp, is(5f));
+        assertThat(config.maxTemp, is(new BigDecimal(35)));
+        assertThat(config.minTemp, is(new BigDecimal(5)));
         assertThat(config.modeCommandTopic, is("zigbee2mqtt/th1/set/system_mode"));
         assertThat(config.modeStateTemplate, is("{{ value_json.system_mode }}"));
         assertThat(config.modeStateTopic, is("zigbee2mqtt/th1"));
         assertThat(config.modes, is(List.of("heat", "auto", "off")));
         assertThat(config.getName(), is("th1"));
-        assertThat(config.tempStep, is(0.5f));
+        assertThat(config.tempStep, is(new BigDecimal("0.5")));
         assertThat(config.temperatureCommandTopic, is("zigbee2mqtt/th1/set/current_heating_setpoint"));
         assertThat(config.temperatureStateTemplate, is("{{ value_json.current_heating_setpoint }}"));
         assertThat(config.temperatureStateTopic, is("zigbee2mqtt/th1"));
-        assertThat(config.temperatureUnit, is("C"));
+        assertThat(config.temperatureUnit, is(Climate.TemperatureUnit.CELSIUS));
         assertThat(config.getUniqueId(), is("0x847127fffe11dd6a_climate_zigbee2mqtt"));
 
         assertThat(config.initial, is(21));
@@ -240,11 +241,11 @@ public class HAConfigurationTests {
         assertThat(config.temperatureLowStateTopic, is("T"));
         assertThat(config.powerCommandTopic, is("U"));
         assertThat(config.initial, is(10));
-        assertThat(config.maxTemp, is(40f));
-        assertThat(config.minTemp, is(0f));
-        assertThat(config.temperatureUnit, is("F"));
-        assertThat(config.tempStep, is(1f));
-        assertThat(config.precision, is(0.5f));
+        assertThat(config.maxTemp, is(new BigDecimal(40)));
+        assertThat(config.minTemp, is(BigDecimal.ZERO));
+        assertThat(config.temperatureUnit, is(Climate.TemperatureUnit.FAHRENHEIT));
+        assertThat(config.tempStep, is(BigDecimal.ONE));
+        assertThat(config.precision, is(new BigDecimal("0.5")));
         assertThat(config.sendIfOff, is(false));
     }
 }
index d630073efac5aa469375b380950030dd74a09f70..eba2a730c399812b264f0f36083bacddd7e34495 100644 (file)
@@ -19,7 +19,8 @@ import java.util.Set;
 
 import org.junit.jupiter.api.Test;
 import org.openhab.binding.mqtt.generic.values.NumberValue;
-import org.openhab.core.library.types.DecimalType;
+import org.openhab.core.library.types.QuantityType;
+import org.openhab.core.library.unit.Units;
 import org.openhab.core.types.UnDefType;
 
 /**
@@ -67,15 +68,16 @@ public class SensorTests extends AbstractComponentTests {
                 NumberValue.class);
 
         publishMessage("zigbee2mqtt/sensor/state", "10");
-        assertState(component, Sensor.SENSOR_CHANNEL_ID, DecimalType.valueOf("10"));
+        assertState(component, Sensor.SENSOR_CHANNEL_ID, new QuantityType<>(10, Units.WATT));
         publishMessage("zigbee2mqtt/sensor/state", "20");
-        assertState(component, Sensor.SENSOR_CHANNEL_ID, DecimalType.valueOf("20"));
+        assertState(component, Sensor.SENSOR_CHANNEL_ID, new QuantityType<>(20, Units.WATT));
         assertThat(component.getChannel(Sensor.SENSOR_CHANNEL_ID).getState().getCache().createStateDescription(true)
-                .build().getPattern(), is("%s W"));
+                .build().getPattern(), is("%s %unit%"));
 
         waitForAssert(() -> assertState(component, Sensor.SENSOR_CHANNEL_ID, UnDefType.UNDEF), 10000, 200);
     }
 
+    @Override
     protected Set<String> getConfigTopics() {
         return Set.of(CONFIG_TOPIC);
     }
index b87e525c2ec4064391a8f4d615d3d57e9a69891f..016ecfc227d09e96b722cbb6b58de52f2efca80c 100644 (file)
@@ -48,6 +48,7 @@ import org.openhab.core.thing.type.AutoUpdatePolicy;
 import org.openhab.core.thing.type.ChannelType;
 import org.openhab.core.thing.type.ChannelTypeBuilder;
 import org.openhab.core.thing.type.ChannelTypeUID;
+import org.openhab.core.types.util.UnitUtils;
 import org.openhab.core.util.UIDUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -216,7 +217,7 @@ public class Property implements AttributeChanged {
                 if (attributes.unit.contains("%") && attributes.settable) {
                     value = new PercentageValue(min, max, step, null, null);
                 } else {
-                    value = new NumberValue(min, max, step, attributes.unit);
+                    value = new NumberValue(min, max, step, UnitUtils.parseUnit(attributes.unit));
                 }
                 break;
             case datetime_: