[profiles] Added 'Range' profile (#2046)

Signed-off-by: Christoph Weitkamp <github@christophweitkamp.de>
This commit is contained in:
Christoph Weitkamp
2021-02-27 10:28:09 +01:00
committed by GitHub
parent 7de57f9ffe
commit 9c0302ec26
8 changed files with 469 additions and 12 deletions
@@ -35,7 +35,7 @@ import org.slf4j.LoggerFactory;
import tec.uom.se.AbstractUnit;
/***
* This is the default implementation for a {@link SystemHysteresisStateProfile}}.
* This is the default implementation for a {@link SystemHysteresisStateProfile}.
*
* @author Christoph Weitkamp - Initial contribution
*/
@@ -64,13 +64,13 @@ public class SystemProfileFactory implements ProfileFactory, ProfileAdvisor, Pro
private final ChannelTypeRegistry channelTypeRegistry;
private static final Set<ProfileType> SUPPORTED_PROFILE_TYPES = Set.of(DEFAULT_TYPE, FOLLOW_TYPE, HYSTERESIS_TYPE,
OFFSET_TYPE, RAWBUTTON_ON_OFF_SWITCH_TYPE, RAWBUTTON_TOGGLE_PLAYER_TYPE, RAWBUTTON_TOGGLE_SWITCH_TYPE,
RAWROCKER_DIMMER_TYPE, RAWROCKER_NEXT_PREVIOUS_TYPE, RAWROCKER_ON_OFF_TYPE, RAWROCKER_PLAY_PAUSE_TYPE,
RAWROCKER_REWIND_FASTFORWARD_TYPE, RAWROCKER_STOP_MOVE_TYPE, RAWROCKER_UP_DOWN_TYPE, TIMESTAMP_CHANGE_TYPE,
TIMESTAMP_UPDATE_TYPE);
OFFSET_TYPE, RANGE_TYPE, RAWBUTTON_ON_OFF_SWITCH_TYPE, RAWBUTTON_TOGGLE_PLAYER_TYPE,
RAWBUTTON_TOGGLE_SWITCH_TYPE, RAWROCKER_DIMMER_TYPE, RAWROCKER_NEXT_PREVIOUS_TYPE, RAWROCKER_ON_OFF_TYPE,
RAWROCKER_PLAY_PAUSE_TYPE, RAWROCKER_REWIND_FASTFORWARD_TYPE, RAWROCKER_STOP_MOVE_TYPE,
RAWROCKER_UP_DOWN_TYPE, TIMESTAMP_CHANGE_TYPE, TIMESTAMP_UPDATE_TYPE);
private static final Set<ProfileTypeUID> SUPPORTED_PROFILE_TYPE_UIDS = Set.of(DEFAULT, FOLLOW, HYSTERESIS, OFFSET,
RAWBUTTON_ON_OFF_SWITCH, RAWBUTTON_TOGGLE_PLAYER, RAWBUTTON_TOGGLE_SWITCH, RAWROCKER_DIMMER,
RANGE, RAWBUTTON_ON_OFF_SWITCH, RAWBUTTON_TOGGLE_PLAYER, RAWBUTTON_TOGGLE_SWITCH, RAWROCKER_DIMMER,
RAWROCKER_NEXT_PREVIOUS, RAWROCKER_ON_OFF, RAWROCKER_PLAY_PAUSE, RAWROCKER_REWIND_FASTFORWARD,
RAWROCKER_STOP_MOVE, RAWROCKER_UP_DOWN, TIMESTAMP_CHANGE, TIMESTAMP_UPDATE);
@@ -99,6 +99,8 @@ public class SystemProfileFactory implements ProfileFactory, ProfileAdvisor, Pro
return new SystemHysteresisStateProfile(callback, context);
} else if (OFFSET.equals(profileTypeUID)) {
return new SystemOffsetProfile(callback, context);
} else if (RANGE.equals(profileTypeUID)) {
return new SystemRangeStateProfile(callback, context);
} else if (RAWBUTTON_ON_OFF_SWITCH.equals(profileTypeUID)) {
return new RawButtonOnOffSwitchProfile(callback);
} else if (RAWBUTTON_TOGGLE_SWITCH.equals(profileTypeUID)) {
@@ -0,0 +1,174 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.core.thing.internal.profiles;
import java.math.BigDecimal;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
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.unit.Units;
import org.openhab.core.thing.profiles.ProfileCallback;
import org.openhab.core.thing.profiles.ProfileContext;
import org.openhab.core.thing.profiles.ProfileTypeUID;
import org.openhab.core.thing.profiles.StateProfile;
import org.openhab.core.thing.profiles.SystemProfiles;
import org.openhab.core.types.Command;
import org.openhab.core.types.State;
import org.openhab.core.types.Type;
import org.openhab.core.types.UnDefType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import tec.uom.se.AbstractUnit;
/***
* This is the default implementation for a {@link SystemRangeStateProfile}.
*
* @author Christoph Weitkamp - Initial contribution
*/
@NonNullByDefault
public class SystemRangeStateProfile implements StateProfile {
static final String LOWER_PARAM = "lower";
static final String UPPER_PARAM = "upper";
static final String INVERTED_PARAM = "inverted";
private final Logger logger = LoggerFactory.getLogger(SystemRangeStateProfile.class);
private final ProfileCallback callback;
private final QuantityType<?> lower;
private final QuantityType<?> upper;
private final OnOffType inRange;
private final OnOffType notInRange;
private Type previousType = UnDefType.UNDEF;
public SystemRangeStateProfile(ProfileCallback callback, ProfileContext context) {
this.callback = callback;
final QuantityType<?> lowerParam = getParam(context, LOWER_PARAM);
if (lowerParam == null) {
throw new IllegalArgumentException(String.format("Parameter '%s' is not a Number value.", LOWER_PARAM));
}
this.lower = lowerParam;
final QuantityType<?> upperParam = getParam(context, UPPER_PARAM);
if (upperParam == null) {
throw new IllegalArgumentException(String.format("Parameter '%s' is not a Number value.", UPPER_PARAM));
}
final QuantityType<?> convertedUpperParam = upperParam.toUnit(lower.getUnit());
if (convertedUpperParam == null) {
throw new IllegalArgumentException(
String.format("Units of parameters '%s' and '%s' are not compatible: %s != %s", LOWER_PARAM,
UPPER_PARAM, lower, upperParam));
}
if (convertedUpperParam.doubleValue() <= lower.doubleValue()) {
throw new IllegalArgumentException(
String.format("Parameter '%s' (%s) is less than or equal to '%s' (%s) parameter.", UPPER_PARAM,
convertedUpperParam, LOWER_PARAM, lower));
}
this.upper = convertedUpperParam;
final Object paramValue = context.getConfiguration().get(INVERTED_PARAM);
final boolean inverted = paramValue == null ? false : Boolean.valueOf(paramValue.toString());
this.inRange = inverted ? OnOffType.OFF : OnOffType.ON;
this.notInRange = inverted ? OnOffType.ON : OnOffType.OFF;
}
private @Nullable QuantityType<?> getParam(ProfileContext context, String param) {
final Object paramValue = context.getConfiguration().get(param);
logger.debug("Configuring profile with {} parameter '{}'", param, paramValue);
if (paramValue instanceof String) {
try {
return new QuantityType<>((String) paramValue);
} catch (IllegalArgumentException e) {
logger.error("Cannot convert value '{}' of parameter {} into a valid QuantityType.", paramValue, param);
}
} else if (paramValue instanceof BigDecimal) {
final BigDecimal value = (BigDecimal) paramValue;
return QuantityType.valueOf(value.doubleValue(), AbstractUnit.ONE);
}
return null;
}
@Override
public ProfileTypeUID getProfileTypeUID() {
return SystemProfiles.RANGE;
}
@Override
public void onStateUpdateFromItem(State state) {
// do nothing
}
@Override
public void onCommandFromHandler(Command command) {
final Type mappedCommand = mapValue(command);
logger.trace("Mapped command from '{}' to command '{}'.", command, mappedCommand);
if (mappedCommand instanceof Command) {
callback.sendCommand((Command) mappedCommand);
}
}
@Override
public void onCommandFromItem(Command command) {
// do nothing
}
@Override
public void onStateUpdateFromHandler(State state) {
final Type mappedState = mapValue(state);
logger.trace("Mapped state from '{}' to state '{}'.", state, mappedState);
if (mappedState instanceof State) {
callback.sendUpdate((State) mappedState);
}
}
private Type mapValue(Type value) {
if (value instanceof QuantityType) {
final QuantityType<?> qtState = (QuantityType<?>) value;
final QuantityType<?> finalLower;
final QuantityType<?> finalUpper;
if (lower.getUnit() == Units.ONE && upper.getUnit() == Units.ONE) {
// allow bounds without unit -> implicitly assume its the same as the one from the state, but warn
// the user
finalLower = new QuantityType<>(lower.toBigDecimal(), qtState.getUnit());
finalUpper = new QuantityType<>(upper.toBigDecimal(), qtState.getUnit());
logger.warn(
"Received a QuantityType '{}' with unit, but the boundaries are defined as a plain number without units (lower={}, upper={}), please consider adding units to them.",
value, lower, upper);
} else {
finalLower = lower.toUnit(qtState.getUnit());
finalUpper = upper.toUnit(qtState.getUnit());
if (finalLower == null || finalUpper == null) {
logger.warn(
"Cannot compare state '{}' to boundaries because units (lower={}, upper={}) do not match.",
qtState, lower, upper);
return previousType;
}
}
return previousType = mapValue(finalLower.doubleValue(), finalUpper.doubleValue(), qtState.doubleValue());
} else if (value instanceof DecimalType) {
return previousType = mapValue(lower.doubleValue(), upper.doubleValue(),
((DecimalType) value).doubleValue());
}
return previousType;
}
private Type mapValue(double lower, double upper, double value) {
return lower <= value && value <= upper ? inRange : notInRange;
}
}
@@ -30,6 +30,7 @@ public interface SystemProfiles {
ProfileTypeUID FOLLOW = new ProfileTypeUID(SYSTEM_SCOPE, "follow");
ProfileTypeUID OFFSET = new ProfileTypeUID(SYSTEM_SCOPE, "offset");
ProfileTypeUID HYSTERESIS = new ProfileTypeUID(SYSTEM_SCOPE, "hysteresis");
ProfileTypeUID RANGE = new ProfileTypeUID(SYSTEM_SCOPE, "range");
ProfileTypeUID RAWBUTTON_ON_OFF_SWITCH = new ProfileTypeUID(SYSTEM_SCOPE, "rawbutton-on-off-switch");
ProfileTypeUID RAWBUTTON_TOGGLE_PLAYER = new ProfileTypeUID(SYSTEM_SCOPE, "rawbutton-toggle-player");
ProfileTypeUID RAWBUTTON_TOGGLE_ROLLERSHUTTER = new ProfileTypeUID(SYSTEM_SCOPE, "rawbutton-toggle-rollershutter");
@@ -51,11 +52,17 @@ public interface SystemProfiles {
StateProfileType OFFSET_TYPE = ProfileTypeBuilder.newState(OFFSET, "Offset")
.withSupportedItemTypes(CoreItemFactory.NUMBER).withSupportedItemTypesOfChannel(CoreItemFactory.NUMBER)
.build();
ProfileType HYSTERESIS_TYPE = ProfileTypeBuilder.newState(HYSTERESIS, "Hysteresis") //
.withSupportedItemTypesOfChannel(CoreItemFactory.DIMMER, CoreItemFactory.NUMBER) //
.withSupportedItemTypes(CoreItemFactory.SWITCH) //
.build();
ProfileType RANGE_TYPE = ProfileTypeBuilder.newState(RANGE, "Range") //
.withSupportedItemTypesOfChannel(CoreItemFactory.DIMMER, CoreItemFactory.NUMBER) //
.withSupportedItemTypes(CoreItemFactory.SWITCH) //
.build();
TriggerProfileType RAWBUTTON_ON_OFF_SWITCH_TYPE = ProfileTypeBuilder
.newTrigger(RAWBUTTON_ON_OFF_SWITCH, "Raw Button To On Off")
.withSupportedItemTypes(CoreItemFactory.SWITCH, CoreItemFactory.DIMMER, CoreItemFactory.COLOR)
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<config-description:config-descriptions
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:config-description="https://openhab.org/schemas/config-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/config-description/v1.0.0 https://openhab.org/schemas/config-description-1.0.0.xsd">
<config-description uri="profile:system:range">
<parameter name="lower" type="text" required="true">
<label>Lower Bound</label>
<description>Maps to ON if value is between lower and upper bound (plain number or number with unit).</description>
</parameter>
<parameter name="upper" type="text" required="true">
<label>Upper Bound</label>
<description>Maps to ON if value is between lower and upper bound (plain number or number with unit).</description>
</parameter>
<parameter name="inverted" type="boolean">
<label>Inverted</label>
<description>Inverts resulting mapping of ON / OFF, if true.</description>
<default>false</default>
</parameter>
</config-description>
</config-description:config-descriptions>
@@ -2,13 +2,20 @@ profile-type.system.default.label = Standard
profile-type.system.follow.label = Folgen
profile-type.system.offset.label = Versatz
profile.config.system.offset.offset.label = Versatz
profile.config.system.offset.offset.description = Versatz (Numerischer Wert oder numerischer Wert mit Einheit), welcher auf den Wert addiert wird. Ein negativer Versatz wird vom Wert abgezogen.
profile.config.system.offset.offset.description = Versatz (numerischer Wert oder numerischer Wert mit Einheit), welcher auf den Wert addiert wird. Ein negativer Versatz wird vom Wert abgezogen.
profile-type.system.hysteresis.label = Hysterese
profile.config.system.hysteresis.lower.label = Untere Grenze
profile.config.system.hysteresis.lower.description = Bildet bei Unterschreitung des unteren Grenzwertes OFF auf ein Switch Item ab (Numerischer Wert oder numerischer Wert mit Einheit).
profile.config.system.hysteresis.lower.description = Bildet bei Unterschreitung des unteren Grenzwertes OFF auf ein Switch Item ab (numerischer Wert oder numerischer Wert mit Einheit).
profile.config.system.hysteresis.upper.label = Obere Grenze
profile.config.system.hysteresis.upper.description = Bildet bei Überschreitung des oberen Grenzwertes ON auf ein Switch Item ab (Numerischer Wert oder numerischer Wert mit Einheit).
profile.config.system.hysteresis.upper.description = Bildet bei Überschreitung des oberen Grenzwertes ON auf ein Switch Item ab (numerischer Wert oder numerischer Wert mit Einheit).
profile.config.system.hysteresis.inverted.label = Invertiert
profile.config.system.hysteresis.inverted.description = Invertiert die resultierende Zuordnung von ON / OFF, falls true.
profile-type.system.timestamp-change.label = Zeitstempel bei Änderung
profile-type.system.range.label = Bereich
profile.config.system.range.lower.label = Untere Grenze
profile.config.system.range.lower.description = Bildet bei ON auf ein Switch Item ab, wenn der Wert zwischen der unteren und oberen Grenze liegt (numerischer Wert oder numerischer Wert mit Einheit).
profile.config.system.range.upper.label = Obere Grenze
profile.config.system.range.upper.description = Bildet bei ON auf ein Switch Item ab, wenn der Wert zwischen der unteren und oberen Grenze liegt (numerischer Wert oder numerischer Wert mit Einheit).
profile.config.system.range.inverted.label = Invertiert
profile.config.system.range.inverted.description = Invertiert die resultierende Zuordnung von ON / OFF, falls true.
profile-type.system.timestamp-change.label = Zeitstempel bei Änderung
profile-type.system.timestamp-update.label = Zeitstempel bei Aktualisierung
@@ -0,0 +1,244 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.core.thing.internal.profiles;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.*;
import static org.mockito.MockitoAnnotations.openMocks;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import org.mockito.Mock;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.PercentType;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.thing.profiles.ProfileCallback;
import org.openhab.core.thing.profiles.ProfileContext;
import org.openhab.core.thing.profiles.StateProfile;
import org.openhab.core.types.Command;
import org.openhab.core.types.State;
import org.openhab.core.types.Type;
import org.openhab.core.types.UnDefType;
/**
* Basic unit tests for {@link SystemRangeStateProfileTest}.
*
* @author Christoph Weitkamp - Initial contribution
*/
public class SystemRangeStateProfileTest {
private static final String STRING_FOURTY = "40";
private static final String QUANTITY_STRING_TEN = "10 %";
private static final String QUANTITY_STRING_FOURTY = "40 %";
private static final BigDecimal BIGDECIMAL_FOURTY = new BigDecimal(STRING_FOURTY);
private static final PercentType PERCENT_TYPE_TEN = new PercentType(BigDecimal.TEN);
private static final PercentType PERCENT_TYPE_TWENTY_FIVE = new PercentType(BigDecimal.valueOf(25));
@NonNullByDefault
public static class ParameterSet {
public final List<State> states;
public final List<State> resultingStates;
public final List<Command> commands;
public final List<@Nullable Command> resultingCommands;
public final Object lower;
public final @Nullable Object upper;
public final boolean inverted;
public ParameterSet(List<? extends Type> sources, List<? extends Type> results, Object lower,
@Nullable Object upper, boolean inverted) {
this.states = (List<State>) sources;
this.resultingStates = (List<State>) results;
this.commands = (List<Command>) sources;
this.resultingCommands = new ArrayList<>(results.size());
results.forEach(result -> {
resultingCommands.add(result instanceof Command ? (Command) result : null);
});
this.lower = lower;
this.upper = upper;
this.inverted = inverted;
}
}
public static Collection<Object[]> parameters() {
return Arrays.asList(new Object[][] { //
// lower bound = 10, upper bound = 40 (as BigDecimal), one state update / command (PercentType), not
// inverted
{ new ParameterSet(List.of(PercentType.HUNDRED), List.of(OnOffType.OFF), BigDecimal.TEN,
BIGDECIMAL_FOURTY, false) }, //
{ new ParameterSet(List.of(PERCENT_TYPE_TWENTY_FIVE), List.of(OnOffType.ON), BigDecimal.TEN,
BIGDECIMAL_FOURTY, false) }, //
{ new ParameterSet(List.of(PERCENT_TYPE_TEN), List.of(OnOffType.ON), BigDecimal.TEN, BIGDECIMAL_FOURTY,
false) }, //
{ new ParameterSet(List.of(PercentType.ZERO), List.of(OnOffType.OFF), BigDecimal.TEN, BIGDECIMAL_FOURTY,
false) }, //
// lower bound = 10, upper bound = 40 (as BigDecimal), one state update / command (QuantityType), not
// inverted
{ new ParameterSet(List.of(QuantityType.valueOf("100 %")), List.of(OnOffType.OFF), BigDecimal.TEN,
BIGDECIMAL_FOURTY, false) }, //
{ new ParameterSet(List.of(QuantityType.valueOf("25 %")), List.of(OnOffType.ON), BigDecimal.TEN,
BIGDECIMAL_FOURTY, false) }, //
{ new ParameterSet(List.of(QuantityType.valueOf(QUANTITY_STRING_TEN)), List.of(OnOffType.ON),
BigDecimal.TEN, BIGDECIMAL_FOURTY, false) }, //
{ new ParameterSet(List.of(QuantityType.valueOf("0 %")), List.of(OnOffType.OFF), BigDecimal.TEN,
BIGDECIMAL_FOURTY, false) }, //
// lower bound = 10, upper bound = 40 (as QuantityType), one state update / command (QuantityType), not
// inverted
{ new ParameterSet(List.of(QuantityType.valueOf("100 %")), List.of(OnOffType.OFF), QUANTITY_STRING_TEN,
QUANTITY_STRING_FOURTY, false) }, //
{ new ParameterSet(List.of(QuantityType.valueOf("25 %")), List.of(OnOffType.ON), QUANTITY_STRING_TEN,
QUANTITY_STRING_FOURTY, false) }, //
{ new ParameterSet(List.of(QuantityType.valueOf(QUANTITY_STRING_TEN)), List.of(OnOffType.ON),
QUANTITY_STRING_TEN, QUANTITY_STRING_FOURTY, false) }, //
{ new ParameterSet(List.of(QuantityType.valueOf("0 %")), List.of(OnOffType.OFF), QUANTITY_STRING_TEN,
QUANTITY_STRING_FOURTY, false) }, //
// lower bound = 10, upper bound = 40 (as QuantityType), one state update / command (QuantityType) ->
// values
// are converted to the same unit, not inverted
{ new ParameterSet(List.of(QuantityType.valueOf("10 m")), List.of(OnOffType.OFF), "25 cm", "100cm",
false) }, //
// lower bound = upper bound = 10 (as QuantityType), one state update / command (QuantityType) ->
// incompatible units cannot be compared
{ new ParameterSet(List.of(QuantityType.valueOf("10 m")), List.of(UnDefType.UNDEF), "25 °C", "30 °C",
false) }, //
// lower bound = 10, upper bound = 40 (as BigDecimal), one state update / command (PercentType),
// inverted
{ new ParameterSet(List.of(PercentType.HUNDRED), List.of(OnOffType.ON), BigDecimal.TEN,
BIGDECIMAL_FOURTY, true) }, //
{ new ParameterSet(List.of(PERCENT_TYPE_TWENTY_FIVE), List.of(OnOffType.OFF), BigDecimal.TEN,
BIGDECIMAL_FOURTY, true) }, //
{ new ParameterSet(List.of(PERCENT_TYPE_TEN), List.of(OnOffType.OFF), BigDecimal.TEN, BIGDECIMAL_FOURTY,
true) }, //
{ new ParameterSet(List.of(PercentType.ZERO), List.of(OnOffType.ON), BigDecimal.TEN, BIGDECIMAL_FOURTY,
true) }, //
// lower bound = 10, upper bound = 40 (as BigDecimal), one state update / command (QuantityType),
// inverted
{ new ParameterSet(List.of(QuantityType.valueOf("100 %")), List.of(OnOffType.ON), BigDecimal.TEN,
BIGDECIMAL_FOURTY, true) }, //
{ new ParameterSet(List.of(QuantityType.valueOf("25 %")), List.of(OnOffType.OFF), BigDecimal.TEN,
BIGDECIMAL_FOURTY, true) }, //
{ new ParameterSet(List.of(QuantityType.valueOf(QUANTITY_STRING_TEN)), List.of(OnOffType.OFF),
BigDecimal.TEN, BIGDECIMAL_FOURTY, true) }, //
{ new ParameterSet(List.of(QuantityType.valueOf("0 %")), List.of(OnOffType.ON), BigDecimal.TEN,
BIGDECIMAL_FOURTY, true) }, //
// lower bound = 10, upper bound = 40 (as QuantityType), one state update / command (QuantityType),
// inverted
{ new ParameterSet(List.of(QuantityType.valueOf("100 %")), List.of(OnOffType.ON), QUANTITY_STRING_TEN,
QUANTITY_STRING_FOURTY, true) }, //
{ new ParameterSet(List.of(QuantityType.valueOf("25 %")), List.of(OnOffType.OFF), QUANTITY_STRING_TEN,
QUANTITY_STRING_FOURTY, true) }, //
{ new ParameterSet(List.of(QuantityType.valueOf(QUANTITY_STRING_TEN)), List.of(OnOffType.OFF),
QUANTITY_STRING_TEN, QUANTITY_STRING_FOURTY, true) }, //
{ new ParameterSet(List.of(QuantityType.valueOf("0 %")), List.of(OnOffType.ON), QUANTITY_STRING_TEN,
QUANTITY_STRING_FOURTY, true) }, //
});
}
private AutoCloseable mocksCloseable;
private @Mock ProfileCallback mockCallback;
private @Mock ProfileContext mockContext;
@BeforeEach
public void setup() {
mocksCloseable = openMocks(this);
}
@AfterEach
public void afterEach() throws Exception {
mocksCloseable.close();
}
@Test
public void testWrongParameterLower() {
assertThrows(IllegalArgumentException.class, () -> initProfile(null, null));
}
@Test
public void testWrongParameterUpper() {
assertThrows(IllegalArgumentException.class, () -> initProfile(QUANTITY_STRING_TEN, null));
}
@Test
public void testWrongParameterUnits() {
assertThrows(IllegalArgumentException.class, () -> initProfile(QUANTITY_STRING_TEN, "5 °C"));
}
@Test
public void testWrongParameterUpperLessThanOrEqualsToLower() {
assertThrows(IllegalArgumentException.class, () -> initProfile(QUANTITY_STRING_FOURTY, QUANTITY_STRING_TEN));
assertThrows(IllegalArgumentException.class, () -> initProfile(QUANTITY_STRING_FOURTY, QUANTITY_STRING_FOURTY));
}
@ParameterizedTest
@MethodSource("parameters")
public void testOnCommandFromHandler(ParameterSet parameterSet) {
final StateProfile profile = initProfile(parameterSet.lower, parameterSet.upper, parameterSet.inverted);
for (int i = 0; i < parameterSet.commands.size(); i++) {
verifySendCommand(profile, parameterSet.commands.get(i), parameterSet.resultingCommands.get(i));
}
}
@ParameterizedTest
@MethodSource("parameters")
public void testOnStateUpdateFromHandler(ParameterSet parameterSet) {
final StateProfile profile = initProfile(parameterSet.lower, parameterSet.upper, parameterSet.inverted);
for (int i = 0; i < parameterSet.states.size(); i++) {
verifySendUpdate(profile, parameterSet.states.get(i), parameterSet.resultingStates.get(i));
}
}
private StateProfile initProfile(Object lower, Object upper) {
return initProfile(lower, upper, false);
}
private StateProfile initProfile(Object lower, Object upper, boolean inverted) {
final Map<String, @Nullable Object> properties = new HashMap<>(2);
properties.put(SystemRangeStateProfile.LOWER_PARAM, lower);
properties.put(SystemRangeStateProfile.UPPER_PARAM, upper);
properties.put(SystemRangeStateProfile.INVERTED_PARAM, inverted);
when(mockContext.getConfiguration()).thenReturn(new Configuration(properties));
return new SystemRangeStateProfile(mockCallback, mockContext);
}
private void verifySendCommand(StateProfile profile, Command command, @Nullable Command expectedCommand) {
reset(mockCallback);
profile.onCommandFromHandler(command);
Command eC = expectedCommand;
if (eC == null) {
verifyNoInteractions(mockCallback);
} else {
verify(mockCallback, times(1)).sendCommand(eq(eC));
}
}
private void verifySendUpdate(StateProfile profile, State state, State expectedState) {
reset(mockCallback);
profile.onStateUpdateFromHandler(state);
verify(mockCallback, times(1)).sendUpdate(eq(expectedState));
}
}
@@ -50,7 +50,8 @@ import org.openhab.core.thing.type.ChannelType;
public class SystemProfileFactoryOSGiTest extends JavaOSGiTest {
private final Map<String, Object> properties = Map.of(SystemOffsetProfile.OFFSET_PARAM, BigDecimal.ZERO,
SystemHysteresisStateProfile.LOWER_PARAM, BigDecimal.TEN);
SystemHysteresisStateProfile.LOWER_PARAM, BigDecimal.TEN, SystemRangeStateProfile.UPPER_PARAM,
BigDecimal.valueOf(40));
private SystemProfileFactory profileFactory;
@@ -68,7 +69,7 @@ public class SystemProfileFactoryOSGiTest extends JavaOSGiTest {
@Test
public void systemProfileTypesAndUidsShouldBeAvailable() {
Collection<ProfileTypeUID> systemProfileTypeUIDs = profileFactory.getSupportedProfileTypeUIDs();
assertEquals(16, systemProfileTypeUIDs.size());
assertEquals(17, systemProfileTypeUIDs.size());
Collection<ProfileType> systemProfileTypes = profileFactory.getProfileTypes(null);
assertEquals(systemProfileTypeUIDs.size(), systemProfileTypes.size());