[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:
parent
f79ce45650
commit
0fcebcde3f
@ -16,6 +16,8 @@ import java.math.BigDecimal;
|
|||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import javax.measure.Unit;
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
import org.eclipse.jdt.annotation.Nullable;
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
import org.openhab.core.library.CoreItemFactory;
|
import org.openhab.core.library.CoreItemFactory;
|
||||||
@ -47,19 +49,19 @@ public class NumberValue extends Value {
|
|||||||
private final @Nullable BigDecimal min;
|
private final @Nullable BigDecimal min;
|
||||||
private final @Nullable BigDecimal max;
|
private final @Nullable BigDecimal max;
|
||||||
private final BigDecimal step;
|
private final BigDecimal step;
|
||||||
private final String unit;
|
private final Unit<?> unit;
|
||||||
|
|
||||||
public NumberValue(@Nullable BigDecimal min, @Nullable BigDecimal max, @Nullable BigDecimal step,
|
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)
|
super(CoreItemFactory.NUMBER, Stream.of(QuantityType.class, IncreaseDecreaseType.class, UpDownType.class)
|
||||||
.collect(Collectors.toList()));
|
.collect(Collectors.toList()));
|
||||||
this.min = min;
|
this.min = min;
|
||||||
this.max = max;
|
this.max = max;
|
||||||
this.step = step == null ? BigDecimal.ONE : step;
|
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;
|
BigDecimal min = this.min;
|
||||||
if (min != null && newValue.compareTo(min) == -1) {
|
if (min != null && newValue.compareTo(min) == -1) {
|
||||||
logger.trace("Number not accepted as it is below the configured minimum");
|
logger.trace("Number not accepted as it is below the configured minimum");
|
||||||
@ -90,49 +92,54 @@ public class NumberValue extends Value {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void update(Command command) throws IllegalArgumentException {
|
public void update(Command command) throws IllegalArgumentException {
|
||||||
DecimalType oldvalue = (state == UnDefType.UNDEF) ? new DecimalType() : (DecimalType) state;
|
|
||||||
BigDecimal newValue = null;
|
BigDecimal newValue = null;
|
||||||
if (command instanceof DecimalType) {
|
if (command instanceof DecimalType) {
|
||||||
if (!checkConditions(((DecimalType) command).toBigDecimal(), oldvalue)) {
|
newValue = ((DecimalType) command).toBigDecimal();
|
||||||
return;
|
|
||||||
}
|
|
||||||
state = (DecimalType) command;
|
|
||||||
} else if (command instanceof IncreaseDecreaseType || command instanceof UpDownType) {
|
} else if (command instanceof IncreaseDecreaseType || command instanceof UpDownType) {
|
||||||
|
BigDecimal oldValue = getOldValue();
|
||||||
if (command == IncreaseDecreaseType.INCREASE || command == UpDownType.UP) {
|
if (command == IncreaseDecreaseType.INCREASE || command == UpDownType.UP) {
|
||||||
newValue = oldvalue.toBigDecimal().add(step);
|
newValue = oldValue.add(step);
|
||||||
} else {
|
} else {
|
||||||
newValue = oldvalue.toBigDecimal().subtract(step);
|
newValue = oldValue.subtract(step);
|
||||||
}
|
}
|
||||||
if (!checkConditions(newValue, oldvalue)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
state = new DecimalType(newValue);
|
|
||||||
} else if (command instanceof QuantityType<?>) {
|
} else if (command instanceof QuantityType<?>) {
|
||||||
QuantityType<?> qType = (QuantityType<?>) command;
|
newValue = getQuantityTypeAsDecimal((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);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
newValue = new BigDecimal(command.toString());
|
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);
|
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
|
@Override
|
||||||
public StateDescriptionFragmentBuilder createStateDescription(boolean readOnly) {
|
public StateDescriptionFragmentBuilder createStateDescription(boolean readOnly) {
|
||||||
StateDescriptionFragmentBuilder builder = super.createStateDescription(readOnly);
|
StateDescriptionFragmentBuilder builder = super.createStateDescription(readOnly);
|
||||||
@ -144,10 +151,6 @@ public class NumberValue extends Value {
|
|||||||
if (min != null) {
|
if (min != null) {
|
||||||
builder = builder.withMinimum(min);
|
builder = builder.withMinimum(min);
|
||||||
}
|
}
|
||||||
builder = builder.withStep(step);
|
return builder.withStep(step).withPattern("%s %unit%");
|
||||||
if (this.unit.length() > 0) {
|
|
||||||
builder = builder.withPattern("%s " + this.unit.replace("%", "%%"));
|
|
||||||
}
|
|
||||||
return builder;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -16,6 +16,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
|
|||||||
import org.openhab.binding.mqtt.generic.ChannelConfig;
|
import org.openhab.binding.mqtt.generic.ChannelConfig;
|
||||||
import org.openhab.binding.mqtt.generic.internal.MqttBindingConstants;
|
import org.openhab.binding.mqtt.generic.internal.MqttBindingConstants;
|
||||||
import org.openhab.binding.mqtt.generic.mapping.ColorMode;
|
import org.openhab.binding.mqtt.generic.mapping.ColorMode;
|
||||||
|
import org.openhab.core.types.util.UnitUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A factory t
|
* A factory t
|
||||||
@ -24,6 +25,7 @@ import org.openhab.binding.mqtt.generic.mapping.ColorMode;
|
|||||||
*/
|
*/
|
||||||
@NonNullByDefault
|
@NonNullByDefault
|
||||||
public class ValueFactory {
|
public class ValueFactory {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new channel state value.
|
* Creates a new channel state value.
|
||||||
*
|
*
|
||||||
@ -47,7 +49,7 @@ public class ValueFactory {
|
|||||||
value = new LocationValue();
|
value = new LocationValue();
|
||||||
break;
|
break;
|
||||||
case MqttBindingConstants.NUMBER:
|
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;
|
break;
|
||||||
case MqttBindingConstants.DIMMER:
|
case MqttBindingConstants.DIMMER:
|
||||||
value = new PercentageValue(config.min, config.max, config.step, config.on, config.off);
|
value = new PercentageValue(config.min, config.max, config.step, config.on, config.off);
|
||||||
|
|||||||
@ -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.HSBType;
|
||||||
import org.openhab.core.library.types.RawType;
|
import org.openhab.core.library.types.RawType;
|
||||||
import org.openhab.core.library.types.StringType;
|
import org.openhab.core.library.types.StringType;
|
||||||
|
import org.openhab.core.library.unit.Units;
|
||||||
import org.openhab.core.thing.ChannelUID;
|
import org.openhab.core.thing.ChannelUID;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -185,6 +186,36 @@ public class ChannelStateTests {
|
|||||||
assertThat(value.getChannelState().toString(), is("16.0"));
|
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
|
@Test
|
||||||
public void receivePercentageTest() {
|
public void receivePercentageTest() {
|
||||||
PercentageValue value = new PercentageValue(new BigDecimal(-100), new BigDecimal(100), new BigDecimal(10), null,
|
PercentageValue value = new PercentageValue(new BigDecimal(-100), new BigDecimal(100), new BigDecimal(10), null,
|
||||||
|
|||||||
@ -26,8 +26,11 @@ import org.openhab.core.library.types.IncreaseDecreaseType;
|
|||||||
import org.openhab.core.library.types.OnOffType;
|
import org.openhab.core.library.types.OnOffType;
|
||||||
import org.openhab.core.library.types.OpenClosedType;
|
import org.openhab.core.library.types.OpenClosedType;
|
||||||
import org.openhab.core.library.types.PercentType;
|
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.StringType;
|
||||||
import org.openhab.core.library.types.UpDownType;
|
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.Command;
|
||||||
import org.openhab.core.types.TypeParser;
|
import org.openhab.core.types.TypeParser;
|
||||||
|
|
||||||
@ -160,6 +163,39 @@ public class ValueTests {
|
|||||||
assertThat(v.getChannelState(), is(OpenClosedType.OPEN));
|
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
|
@Test
|
||||||
public void rollershutterUpdateWithStrings() {
|
public void rollershutterUpdateWithStrings() {
|
||||||
RollershutterValue v = new RollershutterValue("fancyON", "fancyOff", "fancyStop");
|
RollershutterValue v = new RollershutterValue("fancyON", "fancyOff", "fancyStop");
|
||||||
|
|||||||
@ -17,6 +17,9 @@ import java.util.Arrays;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.function.Predicate;
|
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.NonNullByDefault;
|
||||||
import org.eclipse.jdt.annotation.Nullable;
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
import org.openhab.binding.mqtt.generic.ChannelStateUpdateListener;
|
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.ComponentChannel;
|
||||||
import org.openhab.binding.mqtt.homeassistant.internal.config.dto.AbstractChannelConfiguration;
|
import org.openhab.binding.mqtt.homeassistant.internal.config.dto.AbstractChannelConfiguration;
|
||||||
import org.openhab.core.library.types.StringType;
|
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.Command;
|
||||||
import org.openhab.core.types.State;
|
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 TEMPERATURE_LOW_CH_ID = "temperatureLow";
|
||||||
public static final String POWER_CH_ID = "power";
|
public static final String POWER_CH_ID = "power";
|
||||||
|
|
||||||
private static final String CELSIUM = "C";
|
public static enum TemperatureUnit {
|
||||||
private static final String FAHRENHEIT = "F";
|
@SerializedName("C")
|
||||||
private static final float DEFAULT_CELSIUM_PRECISION = 0.1f;
|
CELSIUS(SIUnits.CELSIUS, new BigDecimal("0.1")),
|
||||||
private static final float DEFAULT_FAHRENHEIT_PRECISION = 1f;
|
@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 String ACTION_OFF = "off";
|
||||||
private static final State ACTION_OFF_STATE = new StringType(ACTION_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;
|
protected Integer initial = 21;
|
||||||
@SerializedName("max_temp")
|
@SerializedName("max_temp")
|
||||||
protected @Nullable Float maxTemp;
|
protected @Nullable BigDecimal maxTemp;
|
||||||
@SerializedName("min_temp")
|
@SerializedName("min_temp")
|
||||||
protected @Nullable Float minTemp;
|
protected @Nullable BigDecimal minTemp;
|
||||||
@SerializedName("temperature_unit")
|
@SerializedName("temperature_unit")
|
||||||
protected String temperatureUnit = CELSIUM; // System unit by default
|
protected TemperatureUnit temperatureUnit = TemperatureUnit.CELSIUS; // System unit by default
|
||||||
@SerializedName("temp_step")
|
@SerializedName("temp_step")
|
||||||
protected Float tempStep = 1f;
|
protected BigDecimal tempStep = BigDecimal.ONE;
|
||||||
protected @Nullable Float precision;
|
protected @Nullable BigDecimal precision;
|
||||||
@SerializedName("send_if_off")
|
@SerializedName("send_if_off")
|
||||||
protected Boolean sendIfOff = true;
|
protected Boolean sendIfOff = true;
|
||||||
}
|
}
|
||||||
@ -190,13 +213,8 @@ public class Climate extends AbstractComponent<Climate.ChannelConfiguration> {
|
|||||||
public Climate(ComponentFactory.ComponentConfiguration componentConfiguration) {
|
public Climate(ComponentFactory.ComponentConfiguration componentConfiguration) {
|
||||||
super(componentConfiguration, ChannelConfiguration.class);
|
super(componentConfiguration, ChannelConfiguration.class);
|
||||||
|
|
||||||
BigDecimal minTemp = channelConfiguration.minTemp != null ? BigDecimal.valueOf(channelConfiguration.minTemp)
|
BigDecimal precision = channelConfiguration.precision != null ? channelConfiguration.precision
|
||||||
: null;
|
: channelConfiguration.temperatureUnit.getDefaultPrecision();
|
||||||
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);
|
|
||||||
final ChannelStateUpdateListener updateListener = componentConfiguration.getUpdateListener();
|
final ChannelStateUpdateListener updateListener = componentConfiguration.getUpdateListener();
|
||||||
|
|
||||||
ComponentChannel actionChannel = buildOptionalChannel(ACTION_CH_ID,
|
ComponentChannel actionChannel = buildOptionalChannel(ACTION_CH_ID,
|
||||||
@ -214,7 +232,8 @@ public class Climate extends AbstractComponent<Climate.ChannelConfiguration> {
|
|||||||
channelConfiguration.awayModeStateTopic, commandFilter);
|
channelConfiguration.awayModeStateTopic, commandFilter);
|
||||||
|
|
||||||
buildOptionalChannel(CURRENT_TEMPERATURE_CH_ID,
|
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,
|
updateListener, null, null, channelConfiguration.currentTemperatureTemplate,
|
||||||
channelConfiguration.currentTemperatureTopic, commandFilter);
|
channelConfiguration.currentTemperatureTopic, commandFilter);
|
||||||
|
|
||||||
@ -237,22 +256,22 @@ public class Climate extends AbstractComponent<Climate.ChannelConfiguration> {
|
|||||||
channelConfiguration.swingStateTemplate, channelConfiguration.swingStateTopic, commandFilter);
|
channelConfiguration.swingStateTemplate, channelConfiguration.swingStateTopic, commandFilter);
|
||||||
|
|
||||||
buildOptionalChannel(TEMPERATURE_CH_ID,
|
buildOptionalChannel(TEMPERATURE_CH_ID,
|
||||||
new NumberValue(minTemp, maxTemp, BigDecimal.valueOf(channelConfiguration.tempStep),
|
new NumberValue(channelConfiguration.minTemp, channelConfiguration.maxTemp,
|
||||||
channelConfiguration.temperatureUnit),
|
channelConfiguration.tempStep, channelConfiguration.temperatureUnit.getUnit()),
|
||||||
updateListener, channelConfiguration.temperatureCommandTemplate,
|
updateListener, channelConfiguration.temperatureCommandTemplate,
|
||||||
channelConfiguration.temperatureCommandTopic, channelConfiguration.temperatureStateTemplate,
|
channelConfiguration.temperatureCommandTopic, channelConfiguration.temperatureStateTemplate,
|
||||||
channelConfiguration.temperatureStateTopic, commandFilter);
|
channelConfiguration.temperatureStateTopic, commandFilter);
|
||||||
|
|
||||||
buildOptionalChannel(TEMPERATURE_HIGH_CH_ID,
|
buildOptionalChannel(TEMPERATURE_HIGH_CH_ID,
|
||||||
new NumberValue(minTemp, maxTemp, BigDecimal.valueOf(channelConfiguration.tempStep),
|
new NumberValue(channelConfiguration.minTemp, channelConfiguration.maxTemp,
|
||||||
channelConfiguration.temperatureUnit),
|
channelConfiguration.tempStep, channelConfiguration.temperatureUnit.getUnit()),
|
||||||
updateListener, channelConfiguration.temperatureHighCommandTemplate,
|
updateListener, channelConfiguration.temperatureHighCommandTemplate,
|
||||||
channelConfiguration.temperatureHighCommandTopic, channelConfiguration.temperatureHighStateTemplate,
|
channelConfiguration.temperatureHighCommandTopic, channelConfiguration.temperatureHighStateTemplate,
|
||||||
channelConfiguration.temperatureHighStateTopic, commandFilter);
|
channelConfiguration.temperatureHighStateTopic, commandFilter);
|
||||||
|
|
||||||
buildOptionalChannel(TEMPERATURE_LOW_CH_ID,
|
buildOptionalChannel(TEMPERATURE_LOW_CH_ID,
|
||||||
new NumberValue(minTemp, maxTemp, BigDecimal.valueOf(channelConfiguration.tempStep),
|
new NumberValue(channelConfiguration.minTemp, channelConfiguration.maxTemp,
|
||||||
channelConfiguration.temperatureUnit),
|
channelConfiguration.tempStep, channelConfiguration.temperatureUnit.getUnit()),
|
||||||
updateListener, channelConfiguration.temperatureLowCommandTemplate,
|
updateListener, channelConfiguration.temperatureLowCommandTemplate,
|
||||||
channelConfiguration.temperatureLowCommandTopic, channelConfiguration.temperatureLowStateTemplate,
|
channelConfiguration.temperatureLowCommandTopic, channelConfiguration.temperatureLowStateTemplate,
|
||||||
channelConfiguration.temperatureLowStateTopic, commandFilter);
|
channelConfiguration.temperatureLowStateTopic, commandFilter);
|
||||||
|
|||||||
@ -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.generic.values.Value;
|
||||||
import org.openhab.binding.mqtt.homeassistant.internal.config.dto.AbstractChannelConfiguration;
|
import org.openhab.binding.mqtt.homeassistant.internal.config.dto.AbstractChannelConfiguration;
|
||||||
import org.openhab.binding.mqtt.homeassistant.internal.listener.ExpireUpdateStateListener;
|
import org.openhab.binding.mqtt.homeassistant.internal.listener.ExpireUpdateStateListener;
|
||||||
|
import org.openhab.core.types.util.UnitUtils;
|
||||||
|
|
||||||
import com.google.gson.annotations.SerializedName;
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
|
||||||
@ -71,7 +72,7 @@ public class Sensor extends AbstractComponent<Sensor.ChannelConfiguration> {
|
|||||||
String uom = channelConfiguration.unitOfMeasurement;
|
String uom = channelConfiguration.unitOfMeasurement;
|
||||||
|
|
||||||
if (uom != null && !uom.isBlank()) {
|
if (uom != null && !uom.isBlank()) {
|
||||||
value = new NumberValue(null, null, null, uom);
|
value = new NumberValue(null, null, null, UnitUtils.parseUnit(uom));
|
||||||
} else {
|
} else {
|
||||||
value = new TextValue();
|
value = new TextValue();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -23,7 +23,10 @@ import org.openhab.binding.mqtt.generic.values.OnOffValue;
|
|||||||
import org.openhab.binding.mqtt.generic.values.TextValue;
|
import org.openhab.binding.mqtt.generic.values.TextValue;
|
||||||
import org.openhab.core.library.types.DecimalType;
|
import org.openhab.core.library.types.DecimalType;
|
||||||
import org.openhab.core.library.types.OnOffType;
|
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.types.StringType;
|
||||||
|
import org.openhab.core.library.unit.ImperialUnits;
|
||||||
|
import org.openhab.core.library.unit.SIUnits;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests for {@link Climate}
|
* Tests for {@link Climate}
|
||||||
@ -82,10 +85,10 @@ public class ClimateTests extends AbstractComponentTests {
|
|||||||
+ "\"current_heating_setpoint\": \"24\"}");
|
+ "\"current_heating_setpoint\": \"24\"}");
|
||||||
assertState(component, Climate.ACTION_CH_ID, new StringType("off"));
|
assertState(component, Climate.ACTION_CH_ID, new StringType("off"));
|
||||||
assertState(component, Climate.AWAY_MODE_CH_ID, OnOffType.ON);
|
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.HOLD_CH_ID, new StringType("schedule"));
|
||||||
assertState(component, Climate.MODE_CH_ID, new StringType("heat"));
|
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);
|
component.getChannel(Climate.AWAY_MODE_CH_ID).getState().publishValue(OnOffType.OFF);
|
||||||
assertPublished("zigbee2mqtt/th1/set/away_mode", "OFF");
|
assertPublished("zigbee2mqtt/th1/set/away_mode", "OFF");
|
||||||
@ -146,10 +149,10 @@ public class ClimateTests extends AbstractComponentTests {
|
|||||||
+ "\"current_heating_setpoint\": \"24\"}");
|
+ "\"current_heating_setpoint\": \"24\"}");
|
||||||
assertState(component, Climate.ACTION_CH_ID, new StringType("off"));
|
assertState(component, Climate.ACTION_CH_ID, new StringType("off"));
|
||||||
assertState(component, Climate.AWAY_MODE_CH_ID, OnOffType.ON);
|
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.HOLD_CH_ID, new StringType("schedule"));
|
||||||
assertState(component, Climate.MODE_CH_ID, new StringType("heat"));
|
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
|
// Climate is in OFF state
|
||||||
component.getChannel(Climate.AWAY_MODE_CH_ID).getState().publishValue(OnOffType.OFF);
|
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.ACTION_CH_ID, new StringType("fan"));
|
||||||
assertState(component, Climate.AUX_CH_ID, OnOffType.ON);
|
assertState(component, Climate.AUX_CH_ID, OnOffType.ON);
|
||||||
assertState(component, Climate.AWAY_MODE_CH_ID, OnOffType.OFF);
|
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.FAN_MODE_CH_ID, new StringType("p2"));
|
||||||
assertState(component, Climate.HOLD_CH_ID, new StringType("u2"));
|
assertState(component, Climate.HOLD_CH_ID, new StringType("u2"));
|
||||||
assertState(component, Climate.MODE_CH_ID, new StringType("B1"));
|
assertState(component, Climate.MODE_CH_ID, new StringType("B1"));
|
||||||
assertState(component, Climate.SWING_CH_ID, new StringType("G1"));
|
assertState(component, Climate.SWING_CH_ID, new StringType("G1"));
|
||||||
assertState(component, Climate.TEMPERATURE_CH_ID, new DecimalType(30));
|
assertState(component, Climate.TEMPERATURE_CH_ID, new QuantityType<>(30, ImperialUnits.FAHRENHEIT));
|
||||||
assertState(component, Climate.TEMPERATURE_HIGH_CH_ID, new DecimalType(37));
|
assertState(component, Climate.TEMPERATURE_HIGH_CH_ID, new QuantityType<>(37, ImperialUnits.FAHRENHEIT));
|
||||||
assertState(component, Climate.TEMPERATURE_LOW_CH_ID, new DecimalType(20));
|
assertState(component, Climate.TEMPERATURE_LOW_CH_ID, new QuantityType<>(20, ImperialUnits.FAHRENHEIT));
|
||||||
|
|
||||||
component.getChannel(Climate.AUX_CH_ID).getState().publishValue(OnOffType.OFF);
|
component.getChannel(Climate.AUX_CH_ID).getState().publishValue(OnOffType.OFF);
|
||||||
assertPublished("zigbee2mqtt/th1/aux", "OFF");
|
assertPublished("zigbee2mqtt/th1/aux", "OFF");
|
||||||
@ -291,6 +294,7 @@ public class ClimateTests extends AbstractComponentTests {
|
|||||||
assertPublished("zigbee2mqtt/th1/power", "OFF");
|
assertPublished("zigbee2mqtt/th1/power", "OFF");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
protected Set<String> getConfigTopics() {
|
protected Set<String> getConfigTopics() {
|
||||||
return Set.of(CONFIG_TOPIC);
|
return Set.of(CONFIG_TOPIC);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,6 +19,7 @@ import static org.hamcrest.collection.IsIterableContainingInOrder.contains;
|
|||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
|
import java.math.BigDecimal;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@ -171,18 +172,18 @@ public class HAConfigurationTests {
|
|||||||
assertThat(config.holdStateTemplate, is("{{ value_json.preset }}"));
|
assertThat(config.holdStateTemplate, is("{{ value_json.preset }}"));
|
||||||
assertThat(config.holdStateTopic, is("zigbee2mqtt/th1"));
|
assertThat(config.holdStateTopic, is("zigbee2mqtt/th1"));
|
||||||
assertThat(config.jsonAttributesTopic, is("zigbee2mqtt/th1"));
|
assertThat(config.jsonAttributesTopic, is("zigbee2mqtt/th1"));
|
||||||
assertThat(config.maxTemp, is(35f));
|
assertThat(config.maxTemp, is(new BigDecimal(35)));
|
||||||
assertThat(config.minTemp, is(5f));
|
assertThat(config.minTemp, is(new BigDecimal(5)));
|
||||||
assertThat(config.modeCommandTopic, is("zigbee2mqtt/th1/set/system_mode"));
|
assertThat(config.modeCommandTopic, is("zigbee2mqtt/th1/set/system_mode"));
|
||||||
assertThat(config.modeStateTemplate, is("{{ value_json.system_mode }}"));
|
assertThat(config.modeStateTemplate, is("{{ value_json.system_mode }}"));
|
||||||
assertThat(config.modeStateTopic, is("zigbee2mqtt/th1"));
|
assertThat(config.modeStateTopic, is("zigbee2mqtt/th1"));
|
||||||
assertThat(config.modes, is(List.of("heat", "auto", "off")));
|
assertThat(config.modes, is(List.of("heat", "auto", "off")));
|
||||||
assertThat(config.getName(), is("th1"));
|
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.temperatureCommandTopic, is("zigbee2mqtt/th1/set/current_heating_setpoint"));
|
||||||
assertThat(config.temperatureStateTemplate, is("{{ value_json.current_heating_setpoint }}"));
|
assertThat(config.temperatureStateTemplate, is("{{ value_json.current_heating_setpoint }}"));
|
||||||
assertThat(config.temperatureStateTopic, is("zigbee2mqtt/th1"));
|
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.getUniqueId(), is("0x847127fffe11dd6a_climate_zigbee2mqtt"));
|
||||||
|
|
||||||
assertThat(config.initial, is(21));
|
assertThat(config.initial, is(21));
|
||||||
@ -240,11 +241,11 @@ public class HAConfigurationTests {
|
|||||||
assertThat(config.temperatureLowStateTopic, is("T"));
|
assertThat(config.temperatureLowStateTopic, is("T"));
|
||||||
assertThat(config.powerCommandTopic, is("U"));
|
assertThat(config.powerCommandTopic, is("U"));
|
||||||
assertThat(config.initial, is(10));
|
assertThat(config.initial, is(10));
|
||||||
assertThat(config.maxTemp, is(40f));
|
assertThat(config.maxTemp, is(new BigDecimal(40)));
|
||||||
assertThat(config.minTemp, is(0f));
|
assertThat(config.minTemp, is(BigDecimal.ZERO));
|
||||||
assertThat(config.temperatureUnit, is("F"));
|
assertThat(config.temperatureUnit, is(Climate.TemperatureUnit.FAHRENHEIT));
|
||||||
assertThat(config.tempStep, is(1f));
|
assertThat(config.tempStep, is(BigDecimal.ONE));
|
||||||
assertThat(config.precision, is(0.5f));
|
assertThat(config.precision, is(new BigDecimal("0.5")));
|
||||||
assertThat(config.sendIfOff, is(false));
|
assertThat(config.sendIfOff, is(false));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,7 +19,8 @@ import java.util.Set;
|
|||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.openhab.binding.mqtt.generic.values.NumberValue;
|
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;
|
import org.openhab.core.types.UnDefType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -67,15 +68,16 @@ public class SensorTests extends AbstractComponentTests {
|
|||||||
NumberValue.class);
|
NumberValue.class);
|
||||||
|
|
||||||
publishMessage("zigbee2mqtt/sensor/state", "10");
|
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");
|
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)
|
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);
|
waitForAssert(() -> assertState(component, Sensor.SENSOR_CHANNEL_ID, UnDefType.UNDEF), 10000, 200);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
protected Set<String> getConfigTopics() {
|
protected Set<String> getConfigTopics() {
|
||||||
return Set.of(CONFIG_TOPIC);
|
return Set.of(CONFIG_TOPIC);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -48,6 +48,7 @@ import org.openhab.core.thing.type.AutoUpdatePolicy;
|
|||||||
import org.openhab.core.thing.type.ChannelType;
|
import org.openhab.core.thing.type.ChannelType;
|
||||||
import org.openhab.core.thing.type.ChannelTypeBuilder;
|
import org.openhab.core.thing.type.ChannelTypeBuilder;
|
||||||
import org.openhab.core.thing.type.ChannelTypeUID;
|
import org.openhab.core.thing.type.ChannelTypeUID;
|
||||||
|
import org.openhab.core.types.util.UnitUtils;
|
||||||
import org.openhab.core.util.UIDUtils;
|
import org.openhab.core.util.UIDUtils;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
@ -216,7 +217,7 @@ public class Property implements AttributeChanged {
|
|||||||
if (attributes.unit.contains("%") && attributes.settable) {
|
if (attributes.unit.contains("%") && attributes.settable) {
|
||||||
value = new PercentageValue(min, max, step, null, null);
|
value = new PercentageValue(min, max, step, null, null);
|
||||||
} else {
|
} else {
|
||||||
value = new NumberValue(min, max, step, attributes.unit);
|
value = new NumberValue(min, max, step, UnitUtils.parseUnit(attributes.unit));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case datetime_:
|
case datetime_:
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user