[basicprofiles] Convert to relative unit in State Filter's Delta check (#18127)

* [basicprofiles] Convert to relative unit in State Filter's Delta check

Signed-off-by: Jimmy Tanagra <jcode@tanagra.id.au>
This commit is contained in:
jimtng 2025-02-03 06:24:50 +10:00 committed by GitHub
parent 247495335e
commit 019fb8acf4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 76 additions and 0 deletions

View File

@ -334,6 +334,7 @@ public class StateFilterProfile implements StateProfile {
State rhsState = this.rhsState; State rhsState = this.rhsState;
Item lhsItem = null; Item lhsItem = null;
Item rhsItem = null; Item rhsItem = null;
boolean isDeltaCheck = false;
if (rhsState == null) { if (rhsState == null) {
rhsItem = getItemOrNull(rhsString); rhsItem = getItemOrNull(rhsString);
@ -387,6 +388,9 @@ public class StateFilterProfile implements StateProfile {
logger.debug("Couldn't calculate the left hand side function '{}'", lhsString); logger.debug("Couldn't calculate the left hand side function '{}'", lhsString);
return false; return false;
} }
if (lhsFunction.getType() == FunctionType.Function.DELTA) {
isDeltaCheck = true;
}
} }
if (rhsState == null) { if (rhsState == null) {
@ -395,6 +399,10 @@ public class StateFilterProfile implements StateProfile {
// Don't convert QuantityType to other types, so that 1500 != 1500 W // Don't convert QuantityType to other types, so that 1500 != 1500 W
if (rhsState != null && !(rhsState instanceof QuantityType)) { if (rhsState != null && !(rhsState instanceof QuantityType)) {
if (rhsState instanceof FunctionType rhsFunction
&& rhsFunction.getType() == FunctionType.Function.DELTA) {
isDeltaCheck = true;
}
// Try to convert it to the same type as the lhs // Try to convert it to the same type as the lhs
// This allows comparing compatible types, e.g. PercentType vs OnOffType // This allows comparing compatible types, e.g. PercentType vs OnOffType
rhsState = rhsState.as(lhsState.getClass()); rhsState = rhsState.as(lhsState.getClass());
@ -432,6 +440,12 @@ public class StateFilterProfile implements StateProfile {
rhs = Objects.requireNonNull(rhsState instanceof StringType ? rhsState.toString() : rhsState); rhs = Objects.requireNonNull(rhsState instanceof StringType ? rhsState.toString() : rhsState);
if (isDeltaCheck && rhs instanceof QuantityType rhsQty && lhs instanceof QuantityType lhsQty) {
if (rhsQty.toUnitRelative(lhsQty.getUnit()) instanceof QuantityType relativeRhs) {
rhs = relativeRhs;
}
}
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
if (lhsString.isEmpty()) { if (lhsString.isEmpty()) {
logger.debug("Performing a comparison between input '{}' ({}) and value '{}' ({})", lhs, logger.debug("Performing a comparison between input '{}' ({}) and value '{}' ({})", lhs,

View File

@ -793,6 +793,68 @@ public class StateFilterProfileTest {
@MethodSource @MethodSource
public void testFunctions(Item item, String condition, List<State> states, State input, boolean expected) public void testFunctions(Item item, String condition, List<State> states, State input, boolean expected)
throws ItemNotFoundException { throws ItemNotFoundException {
internalTestFunctions(item, condition, states, input, expected);
}
public static Stream<Arguments> testDeltaWithRelativeUnit() {
NumberItem temperatureItem = new NumberItem("Number:Temperature", "temperatureItem", UNIT_PROVIDER);
State initialC = QuantityType.valueOf("5 °C");
State initialF = QuantityType.valueOf("5 °F");
State qty_7_C = QuantityType.valueOf("7 °C");
State qty_7_F = QuantityType.valueOf("7 °F");
return Stream.of( //
// Celsius inputs
// same unit
Arguments.of(temperatureItem, "$DELTA > 1 °C", initialC, qty_7_C, true), //
Arguments.of(temperatureItem, "1 °C < $DELTA", initialC, qty_7_C, true), //
Arguments.of(temperatureItem, "$DELTA < 1 °C", initialC, qty_7_C, false), //
Arguments.of(temperatureItem, "1 °C > $DELTA", initialC, qty_7_C, false), //
// Celsius vs Fahrenheit: 2 °C = 35.6 °F (absolute), 2 °C = 3.6 °F (relative)
Arguments.of(temperatureItem, "$DELTA > 4 °F", initialC, qty_7_C, false), //
Arguments.of(temperatureItem, "4 °F < $DELTA", initialC, qty_7_C, false), //
Arguments.of(temperatureItem, "$DELTA < 4 °F", initialC, qty_7_C, true), //
Arguments.of(temperatureItem, "4 °F > $DELTA", initialC, qty_7_C, true), //
// Celsius vs Kelvin: °C = K in relative unit
Arguments.of(temperatureItem, "$DELTA > 1 K", initialC, qty_7_C, true), //
Arguments.of(temperatureItem, "1 K < $DELTA", initialC, qty_7_C, true), //
Arguments.of(temperatureItem, "$DELTA < 1 K", initialC, qty_7_C, false), //
Arguments.of(temperatureItem, "1 K > $DELTA", initialC, qty_7_C, false), //
// Fahrenheit inputs
// same unit, in F
Arguments.of(temperatureItem, "$DELTA > 1 °F", initialF, qty_7_F, true), //
Arguments.of(temperatureItem, "1 °F < $DELTA", initialF, qty_7_F, true), //
Arguments.of(temperatureItem, "$DELTA < 2 °F", initialF, qty_7_F, false), //
Arguments.of(temperatureItem, "2 °F > $DELTA", initialF, qty_7_F, false), //
// Fahrenheit vs Celsius: 2 °F = -16.67 °C (absolute), 2 °F = 1.11 °C (relative)
Arguments.of(temperatureItem, "$DELTA > 1 °C", initialF, qty_7_F, true), //
Arguments.of(temperatureItem, "1 °C < $DELTA", initialF, qty_7_F, true), //
Arguments.of(temperatureItem, "$DELTA < 1 °C", initialF, qty_7_F, false), //
Arguments.of(temperatureItem, "1 °C > $DELTA", initialF, qty_7_F, false), //
// Fahreheit vs Kelvin: 2 °F = 256.48 K (absolute), 2 °F = 1.11 K (relative)
Arguments.of(temperatureItem, "$DELTA > 2 K", initialF, qty_7_F, false), //
Arguments.of(temperatureItem, "2 K < $DELTA", initialF, qty_7_F, false), //
Arguments.of(temperatureItem, "$DELTA < 2 K", initialF, qty_7_F, true), //
Arguments.of(temperatureItem, "2 K > $DELTA", initialF, qty_7_F, true) //
);
}
@ParameterizedTest
@MethodSource
public void testDeltaWithRelativeUnit(Item item, String condition, State initialState, State input,
boolean expected) throws ItemNotFoundException {
internalTestFunctions(item, condition, List.of(initialState), input, expected);
}
private void internalTestFunctions(Item item, String condition, List<State> states, State input, boolean expected)
throws ItemNotFoundException {
when(mockContext.getConfiguration()).thenReturn(new Configuration(Map.of("conditions", condition))); when(mockContext.getConfiguration()).thenReturn(new Configuration(Map.of("conditions", condition)));
when(mockItemRegistry.getItem(item.getName())).thenReturn(item); when(mockItemRegistry.getItem(item.getName())).thenReturn(item);
when(mockItemChannelLink.getItemName()).thenReturn(item.getName()); when(mockItemChannelLink.getItemName()).thenReturn(item.getName());