From 019fb8acf48b91949200b9134a2f25af1761114e Mon Sep 17 00:00:00 2001 From: jimtng <2554958+jimtng@users.noreply.github.com> Date: Mon, 3 Feb 2025 06:24:50 +1000 Subject: [PATCH] [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 --- .../internal/profiles/StateFilterProfile.java | 14 +++++ .../profiles/StateFilterProfileTest.java | 62 +++++++++++++++++++ 2 files changed, 76 insertions(+) diff --git a/bundles/org.openhab.transform.basicprofiles/src/main/java/org/openhab/transform/basicprofiles/internal/profiles/StateFilterProfile.java b/bundles/org.openhab.transform.basicprofiles/src/main/java/org/openhab/transform/basicprofiles/internal/profiles/StateFilterProfile.java index b52cbb6d8b2..137cfd31509 100644 --- a/bundles/org.openhab.transform.basicprofiles/src/main/java/org/openhab/transform/basicprofiles/internal/profiles/StateFilterProfile.java +++ b/bundles/org.openhab.transform.basicprofiles/src/main/java/org/openhab/transform/basicprofiles/internal/profiles/StateFilterProfile.java @@ -334,6 +334,7 @@ public class StateFilterProfile implements StateProfile { State rhsState = this.rhsState; Item lhsItem = null; Item rhsItem = null; + boolean isDeltaCheck = false; if (rhsState == null) { rhsItem = getItemOrNull(rhsString); @@ -387,6 +388,9 @@ public class StateFilterProfile implements StateProfile { logger.debug("Couldn't calculate the left hand side function '{}'", lhsString); return false; } + if (lhsFunction.getType() == FunctionType.Function.DELTA) { + isDeltaCheck = true; + } } if (rhsState == null) { @@ -395,6 +399,10 @@ public class StateFilterProfile implements StateProfile { // Don't convert QuantityType to other types, so that 1500 != 1500 W 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 // This allows comparing compatible types, e.g. PercentType vs OnOffType rhsState = rhsState.as(lhsState.getClass()); @@ -432,6 +440,12 @@ public class StateFilterProfile implements StateProfile { 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 (lhsString.isEmpty()) { logger.debug("Performing a comparison between input '{}' ({}) and value '{}' ({})", lhs, diff --git a/bundles/org.openhab.transform.basicprofiles/src/test/java/org/openhab/transform/basicprofiles/internal/profiles/StateFilterProfileTest.java b/bundles/org.openhab.transform.basicprofiles/src/test/java/org/openhab/transform/basicprofiles/internal/profiles/StateFilterProfileTest.java index f3837527327..706872ac336 100644 --- a/bundles/org.openhab.transform.basicprofiles/src/test/java/org/openhab/transform/basicprofiles/internal/profiles/StateFilterProfileTest.java +++ b/bundles/org.openhab.transform.basicprofiles/src/test/java/org/openhab/transform/basicprofiles/internal/profiles/StateFilterProfileTest.java @@ -793,6 +793,68 @@ public class StateFilterProfileTest { @MethodSource public void testFunctions(Item item, String condition, List states, State input, boolean expected) throws ItemNotFoundException { + internalTestFunctions(item, condition, states, input, expected); + } + + public static Stream 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 states, State input, boolean expected) + throws ItemNotFoundException { when(mockContext.getConfiguration()).thenReturn(new Configuration(Map.of("conditions", condition))); when(mockItemRegistry.getItem(item.getName())).thenReturn(item); when(mockItemChannelLink.getItemName()).thenReturn(item.getName());