[basicprofiles] Fix delta state filter handling of negative values (#17997)

* [basicprofiles] Pass the initial data for delta state filter

Signed-off-by: Jimmy Tanagra <jcode@tanagra.id.au>
This commit is contained in:
jimtng 2025-01-01 05:23:41 +10:00 committed by GitHub
parent 37c491bcd1
commit 34d8fec597
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 47 additions and 13 deletions

View File

@ -341,7 +341,12 @@ public class StateFilterProfile implements StateProfile {
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 class StateFilterProfile implements StateProfile {
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 class StateFilterProfile implements StateProfile {
}
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 class StateFilterProfile implements StateProfile {
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);
}

View File

@ -35,6 +35,7 @@ import org.junit.jupiter.api.extension.ExtendWith;
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 class StateFilterProfileTest {
NumberItem powerItem = new NumberItem("Number:Power", "powerItem", UNIT_PROVIDER);
NumberItem decimalItem = new NumberItem("decimalItem");
List<Number> numbers = List.of(1, 2, 3, 4, 5);
List<Number> negatives = List.of(-1, -2, -3, -4, -5);
List<QuantityType> quantities = numbers.stream().map(n -> new QuantityType(n, Units.WATT)).toList();
List<DecimalType> decimals = numbers.stream().map(DecimalType::new).toList();
List<DecimalType> negativeDecimals = negatives.stream().map(DecimalType::new).toList();
return Stream.of( //
// test custom window size
@ -695,6 +698,9 @@ public class StateFilterProfileTest {
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 class StateFilterProfileTest {
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"));
}
}