Skip to content

Commit

Permalink
Add UOM for MQTT Channels
Browse files Browse the repository at this point in the history
Signed-off-by: James Melville <jamesmelville@gmail.com>
  • Loading branch information
jamesmelville committed May 19, 2021
1 parent 7d2c875 commit 120ad67
Show file tree
Hide file tree
Showing 6 changed files with 135 additions and 42 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ public void initialize() {
}
final ChannelConfig channelConfig = channel.getConfiguration().as(ChannelConfig.class);
try {
Value value = ValueFactory.createValueState(channelConfig, channelTypeUID.getId());
Value value = ValueFactory.createValueState(channel.getUID(), channelConfig, channelTypeUID.getId());
ChannelState channelState = createChannelState(channelConfig, channel.getUID(), value);
channelStateByChannelUID.put(channel.getUID(), channelState);
StateDescription description = value.createStateDescription(channelConfig.commandTopic.isBlank())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
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;
Expand All @@ -24,9 +26,11 @@
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.types.UpDownType;
import org.openhab.core.library.unit.Units;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.types.Command;
import org.openhab.core.types.StateDescriptionFragmentBuilder;
import org.openhab.core.types.UnDefType;
import org.openhab.core.types.util.UnitUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand All @@ -48,18 +52,32 @@ public class NumberValue extends Value {
private final @Nullable BigDecimal max;
private final BigDecimal step;
private final String unit;
private final Unit<?> unitType;

public NumberValue(@Nullable BigDecimal min, @Nullable BigDecimal max, @Nullable BigDecimal step,
@Nullable String unit) {
public NumberValue(ChannelUID channelUID, @Nullable BigDecimal min, @Nullable BigDecimal max,
@Nullable BigDecimal step, @Nullable String 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;
String interpretedUnitString = unit == null ? "" : unit;
Unit<?> interpretedUnitType = null;
if (interpretedUnitString != "") {
try {
interpretedUnitType = UnitUtils.parseUnit(unit);
} finally {
if (interpretedUnitType == null) {
logger.warn("unrecognised unit {} for channel {}, ignored", unit, channelUID);
interpretedUnitString = "";
}
}
}
this.unit = interpretedUnitString;
this.unitType = interpretedUnitType != null ? interpretedUnitType : 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");
Expand Down Expand Up @@ -90,49 +108,54 @@ public String getMQTTpublishValue(@Nullable String pattern) {

@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 (unitType != Units.ONE) {
state = new QuantityType<>(newValue, unitType);
} 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(unitType);
if (convertedType != null) {
val = convertedType.toBigDecimal();
}
}
return val;
}

@Override
public StateDescriptionFragmentBuilder createStateDescription(boolean readOnly) {
StateDescriptionFragmentBuilder builder = super.createStateDescription(readOnly);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
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.thing.ChannelUID;

/**
* A factory t
Expand All @@ -30,7 +31,8 @@ public class ValueFactory {
* @param config The channel configuration
* @param channelTypeID The channel type, for instance TEXT_CHANNEL.
*/
public static Value createValueState(ChannelConfig config, String channelTypeID) throws IllegalArgumentException {
public static Value createValueState(ChannelUID channelUID, ChannelConfig config, String channelTypeID)
throws IllegalArgumentException {
Value value;
switch (channelTypeID) {
case MqttBindingConstants.STRING:
Expand All @@ -47,7 +49,7 @@ public static Value createValueState(ChannelConfig config, String channelTypeID)
value = new LocationValue();
break;
case MqttBindingConstants.NUMBER:
value = new NumberValue(config.min, config.max, config.step, config.unit);
value = new NumberValue(channelUID, config.min, config.max, config.step, config.unit);
break;
case MqttBindingConstants.DIMMER:
value = new PercentageValue(config.min, config.max, config.step, config.on, config.off);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ public void receiveStringTest() throws InterruptedException, ExecutionException,

@Test
public void receiveDecimalTest() {
NumberValue value = new NumberValue(null, null, new BigDecimal(10), null);
NumberValue value = new NumberValue(channelUID, null, null, new BigDecimal(10), null);
ChannelState c = spy(new ChannelState(config, channelUID, value, channelStateUpdateListener));
c.start(connection, mock(ScheduledExecutorService.class), 100);

Expand All @@ -174,7 +174,7 @@ public void receiveDecimalTest() {

@Test
public void receiveDecimalFractionalTest() {
NumberValue value = new NumberValue(null, null, new BigDecimal(10.5), null);
NumberValue value = new NumberValue(channelUID, null, null, new BigDecimal(10.5), null);
ChannelState c = spy(new ChannelState(config, channelUID, value, channelStateUpdateListener));
c.start(connection, mock(ScheduledExecutorService.class), 100);

Expand All @@ -185,6 +185,36 @@ public void receiveDecimalFractionalTest() {
assertThat(value.getChannelState().toString(), is("16.0"));
}

@Test
public void receiveDecimalUnitTest() {
NumberValue value = new NumberValue(channelUID, null, null, new BigDecimal(10), "W");
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(channelUID, null, null, new BigDecimal(10), "%");
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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,8 @@ public void setUp() {
public void initializeWithUnknownThingUID() {
ChannelConfig config = textConfiguration().as(ChannelConfig.class);
assertThrows(IllegalArgumentException.class,
() -> thingHandler.createChannelState(config, new ChannelUID(testGenericThing, "test"),
ValueFactory.createValueState(config, unknownChannel.getId())));
() -> thingHandler.createChannelState(config, new ChannelUID(testGenericThing, "test"), ValueFactory
.createValueState(new ChannelUID(testGenericThing, "test"), config, unknownChannel.getId())));
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.*;
import static org.openhab.binding.mqtt.generic.internal.handler.ThingChannelConstants.testGenericThing;

import java.math.BigDecimal;

Expand All @@ -26,8 +27,12 @@
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.thing.ChannelUID;
import org.openhab.core.types.Command;
import org.openhab.core.types.TypeParser;

Expand Down Expand Up @@ -80,7 +85,7 @@ public void illegalColorUpdate() {

@Test
public void illegalNumberCommand() {
NumberValue v = new NumberValue(null, null, null, null);
NumberValue v = new NumberValue(new ChannelUID(testGenericThing, "test"), null, null, null, null);
assertThrows(IllegalArgumentException.class, () -> v.update(OnOffType.OFF));
}

Expand Down Expand Up @@ -160,6 +165,39 @@ public void openCloseUpdate() {
assertThat(v.getChannelState(), is(OpenClosedType.OPEN));
}

@Test
public void numberUpdate() {
NumberValue v = new NumberValue(new ChannelUID(testGenericThing, "test"), null, null, new BigDecimal(10), "W");

// 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(new ChannelUID(testGenericThing, "test"), null, null, new BigDecimal(10), "%");

// 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");
Expand Down

0 comments on commit 120ad67

Please sign in to comment.