From 34d8fec597630507ce8ae4da306747aab5c0caa3 Mon Sep 17 00:00:00 2001 From: jimtng <2554958+jimtng@users.noreply.github.com> Date: Wed, 1 Jan 2025 05:23:41 +1000 Subject: [PATCH] [basicprofiles] Fix delta state filter handling of negative values (#17997) * [basicprofiles] Pass the initial data for delta state filter Signed-off-by: Jimmy Tanagra --- .../internal/profiles/StateFilterProfile.java | 28 ++++++++-------- .../profiles/StateFilterProfileTest.java | 32 +++++++++++++++++++ 2 files changed, 47 insertions(+), 13 deletions(-) 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 9340a9d0cea6c..10ae40488afda 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 @@ -341,7 +341,12 @@ public boolean check(State input) { if (rhsState == null) { rhsItem = getItemOrNull(rhsString); - } else if (rhsState instanceof FunctionType) { + } else if (rhsState instanceof FunctionType rhsFunction) { + if (acceptedState == UnDefType.UNDEF && (rhsFunction.getType() == FunctionType.Function.DELTA + || rhsFunction.getType() == FunctionType.Function.DELTA_PERCENT)) { + acceptedState = input; + return true; + } rhsItem = getLinkedItem(); } @@ -359,6 +364,11 @@ public boolean check(State input) { return false; } } else if (lhsState instanceof FunctionType lhsFunction) { + if (acceptedState == UnDefType.UNDEF && (lhsFunction.getType() == FunctionType.Function.DELTA + || lhsFunction.getType() == FunctionType.Function.DELTA_PERCENT)) { + acceptedState = input; + return true; + } lhsItem = getLinkedItem(); lhsState = lhsFunction.calculate(); if (lhsState == null) { @@ -660,27 +670,18 @@ public String toString() { } private @Nullable State calculateDelta() { - if (acceptedState == UnDefType.UNDEF) { - logger.debug("No previous data to calculate delta"); - acceptedState = newState; - return null; - } - if (newState instanceof QuantityType newStateQuantity) { QuantityType result = newStateQuantity.subtract((QuantityType) acceptedState); return result.toBigDecimal().compareTo(BigDecimal.ZERO) < 0 ? result.negate() : result; } BigDecimal result = ((DecimalType) newState).toBigDecimal() - .subtract(((DecimalType) acceptedState).toBigDecimal()); - return result.compareTo(BigDecimal.ZERO) < 0 ? new DecimalType(result.negate()) : new DecimalType(result); + .subtract(((DecimalType) acceptedState).toBigDecimal()) // + .abs(); + return new DecimalType(result); } private @Nullable State calculateDeltaPercent() { State calculatedDelta = calculateDelta(); - if (calculatedDelta == null) { - return null; - } - BigDecimal bdDelta; BigDecimal bdBase; if (acceptedState instanceof QuantityType acceptedStateQuantity) { @@ -691,6 +692,7 @@ public String toString() { bdDelta = ((DecimalType) calculatedDelta).toBigDecimal(); bdBase = ((DecimalType) acceptedState).toBigDecimal(); } + bdBase = bdBase.abs(); BigDecimal percent = bdDelta.multiply(BigDecimal.valueOf(100)).divide(bdBase, 2, RoundingMode.HALF_EVEN); return new DecimalType(percent); } 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 a97cd4724e1a9..301bfb1ee430f 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 @@ -35,6 +35,7 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.ValueSource; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.junit.jupiter.MockitoExtension; @@ -658,8 +659,10 @@ public static Stream testFunctions() { NumberItem powerItem = new NumberItem("Number:Power", "powerItem", UNIT_PROVIDER); NumberItem decimalItem = new NumberItem("decimalItem"); List numbers = List.of(1, 2, 3, 4, 5); + List negatives = List.of(-1, -2, -3, -4, -5); List quantities = numbers.stream().map(n -> new QuantityType(n, Units.WATT)).toList(); List decimals = numbers.stream().map(DecimalType::new).toList(); + List negativeDecimals = negatives.stream().map(DecimalType::new).toList(); return Stream.of( // // test custom window size @@ -695,6 +698,9 @@ public static Stream testFunctions() { Arguments.of(decimalItem, "$DELTA_PERCENT < 10", decimals, DecimalType.valueOf("0.91"), true), // Arguments.of(decimalItem, "$DELTA_PERCENT < 10", decimals, DecimalType.valueOf("0.89"), false), // + Arguments.of(decimalItem, "$DELTA_PERCENT < 10", negativeDecimals, DecimalType.valueOf("0"), false), // + Arguments.of(decimalItem, "10 > $DELTA_PERCENT", negativeDecimals, DecimalType.valueOf("0"), false), // + Arguments.of(decimalItem, "< 10%", decimals, DecimalType.valueOf("1.09"), true), // Arguments.of(decimalItem, "< 10%", decimals, DecimalType.valueOf("1.11"), false), // Arguments.of(decimalItem, "< 10%", decimals, DecimalType.valueOf("0.91"), true), // @@ -762,4 +768,30 @@ public void testFunctions(Item item, String condition, List states, State profile.onStateUpdateFromHandler(input); verify(mockCallback, times(expected ? 1 : 0)).sendUpdate(input); } + + @ParameterizedTest + @ValueSource(strings = { // + "$DELTA > 10", // + "$DELTA < 10", // + "10 < $DELTA", // + "10 > $DELTA", // + "$DELTA_PERCENT > 10", // + "$DELTA_PERCENT < 10", // + "10 < $DELTA_PERCENT", // + "10 > $DELTA_PERCENT", // + "> 10%", // + "< 10%" // + }) + public void testFirstDataIsAcceptedForDeltaFunctions(String conditions) throws ItemNotFoundException { + NumberItem decimalItem = new NumberItem("decimalItem"); + + when(mockContext.getConfiguration()).thenReturn(new Configuration(Map.of("conditions", conditions))); + when(mockItemRegistry.getItem(decimalItem.getName())).thenReturn(decimalItem); + when(mockItemChannelLink.getItemName()).thenReturn(decimalItem.getName()); + + StateFilterProfile profile = new StateFilterProfile(mockCallback, mockContext, mockItemRegistry); + + profile.onStateUpdateFromHandler(DecimalType.valueOf("1")); + verify(mockCallback, times(1)).sendUpdate(DecimalType.valueOf("1")); + } }