[mqtt.generic] Add UOM to inbound values for MQTT Channels (#10727)

* 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>
This commit is contained in:
James Melville
2022-01-09 09:05:53 +00:00
committed by GitHub
parent f79ce45650
commit 0fcebcde3f
10 changed files with 186 additions and 86 deletions

View 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);

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

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

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

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