mirror of
https://github.com/danieldemus/openhab-addons
synced 2026-07-29 12:34:21 +02:00
[basicprofiles] Fix division-by-zero error in $DELTA_PERCENT state filter (#18089)
* [basicprofiles] Fix division-by-zero error in $DELTA_PERCENT state filter * use Optional for acceptedState Signed-off-by: Jimmy Tanagra <jcode@tanagra.id.au>
This commit is contained in:
+38
-10
@@ -103,7 +103,7 @@ public class StateFilterProfile implements StateProfile {
|
||||
private @Nullable Item linkedItem = null;
|
||||
|
||||
private State newState = UnDefType.UNDEF;
|
||||
private State acceptedState = UnDefType.UNDEF;
|
||||
private Optional<State> acceptedState = Optional.empty();
|
||||
private LinkedList<State> previousStates = new LinkedList<>();
|
||||
|
||||
private final int windowSize;
|
||||
@@ -230,7 +230,7 @@ public class StateFilterProfile implements StateProfile {
|
||||
}
|
||||
|
||||
if (conditions.stream().allMatch(c -> c.check(state))) {
|
||||
acceptedState = state;
|
||||
acceptedState = Optional.of(state);
|
||||
return state;
|
||||
} else {
|
||||
return configMismatchState;
|
||||
@@ -339,8 +339,7 @@ public class StateFilterProfile implements StateProfile {
|
||||
if (rhsState == null) {
|
||||
rhsItem = getItemOrNull(rhsString);
|
||||
} else if (rhsState instanceof FunctionType rhsFunction) {
|
||||
if (acceptedState == UnDefType.UNDEF && (rhsFunction.getType() == FunctionType.Function.DELTA
|
||||
|| rhsFunction.getType() == FunctionType.Function.DELTA_PERCENT)) {
|
||||
if (rhsFunction.alwaysAccept()) {
|
||||
return true;
|
||||
}
|
||||
rhsItem = getLinkedItem();
|
||||
@@ -378,8 +377,7 @@ public class StateFilterProfile implements StateProfile {
|
||||
}
|
||||
|
||||
if (lhsState instanceof FunctionType lhsFunction) {
|
||||
if (acceptedState == UnDefType.UNDEF && (lhsFunction.getType() == FunctionType.Function.DELTA
|
||||
|| lhsFunction.getType() == FunctionType.Function.DELTA_PERCENT)) {
|
||||
if (lhsFunction.alwaysAccept()) {
|
||||
return true;
|
||||
}
|
||||
lhsItem = getLinkedItem();
|
||||
@@ -567,6 +565,30 @@ public class StateFilterProfile implements StateProfile {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* If the profile uses the DELTA or DELTA_PERCENT functions, the new state value will always be accepted if the
|
||||
* 'acceptedState' (prior state) has not yet been initialised, or -- in the case of the DELTA_PERCENT function
|
||||
* only -- if 'acceptedState' has a zero value. This ensures that 'acceptedState' is always initialised. And it
|
||||
* also ensures that the DELTA_PERCENT function cannot cause a divide by zero error.
|
||||
*
|
||||
* @return true if the new state value shall be accepted
|
||||
*/
|
||||
public boolean alwaysAccept() {
|
||||
if ((type == Function.DELTA || type == Function.DELTA_PERCENT) && acceptedState.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
if (type == Function.DELTA_PERCENT) {
|
||||
// avoid division by zero
|
||||
if (acceptedState.get() instanceof QuantityType base) {
|
||||
return base.toBigDecimal().compareTo(BigDecimal.ZERO) == 0;
|
||||
}
|
||||
if (acceptedState.get() instanceof DecimalType base) {
|
||||
return base.toBigDecimal().compareTo(BigDecimal.ZERO) == 0;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends State> @Nullable T as(@Nullable Class<T> target) {
|
||||
if (target == DecimalType.class || target == QuantityType.class) {
|
||||
@@ -692,27 +714,33 @@ public class StateFilterProfile implements StateProfile {
|
||||
}
|
||||
|
||||
private @Nullable State calculateDelta() {
|
||||
if (acceptedState.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
if (newState instanceof QuantityType newStateQuantity) {
|
||||
QuantityType result = newStateQuantity.subtract((QuantityType) acceptedState);
|
||||
QuantityType result = newStateQuantity.subtract((QuantityType) acceptedState.get());
|
||||
return result.toBigDecimal().compareTo(BigDecimal.ZERO) < 0 ? result.negate() : result;
|
||||
}
|
||||
BigDecimal result = ((DecimalType) newState).toBigDecimal()
|
||||
.subtract(((DecimalType) acceptedState).toBigDecimal()) //
|
||||
.subtract(((DecimalType) acceptedState.get()).toBigDecimal()) //
|
||||
.abs();
|
||||
return new DecimalType(result);
|
||||
}
|
||||
|
||||
private @Nullable State calculateDeltaPercent() {
|
||||
if (acceptedState.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
State calculatedDelta = calculateDelta();
|
||||
BigDecimal bdDelta;
|
||||
BigDecimal bdBase;
|
||||
if (acceptedState instanceof QuantityType acceptedStateQuantity) {
|
||||
if (acceptedState.get() instanceof QuantityType acceptedStateQuantity) {
|
||||
// Assume that delta and base are in the same unit
|
||||
bdDelta = ((QuantityType) calculatedDelta).toBigDecimal();
|
||||
bdBase = acceptedStateQuantity.toBigDecimal();
|
||||
} else {
|
||||
bdDelta = ((DecimalType) calculatedDelta).toBigDecimal();
|
||||
bdBase = ((DecimalType) acceptedState).toBigDecimal();
|
||||
bdBase = ((DecimalType) acceptedState.get()).toBigDecimal();
|
||||
}
|
||||
bdBase = bdBase.abs();
|
||||
BigDecimal percent = bdDelta.multiply(BigDecimal.valueOf(100)).divide(bdBase, 2, RoundingMode.HALF_EVEN);
|
||||
|
||||
+4
-2
@@ -722,15 +722,17 @@ public class StateFilterProfileTest {
|
||||
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), //
|
||||
Arguments.of(decimalItem, "< 10%", decimals, DecimalType.valueOf("0.89"), false), //
|
||||
|
||||
// Check against possible division-by-zero errors in $DELTA_PERCENT
|
||||
Arguments.of(decimalItem, "> 10%", List.of(DecimalType.ZERO), DecimalType.valueOf("1"), true), //
|
||||
Arguments.of(decimalItem, "< 10%", List.of(DecimalType.ZERO), DecimalType.valueOf("1"), true), //
|
||||
|
||||
// Contrast a simple comparison against a Percent QuantityType vs delta percent check
|
||||
Arguments.of(percentItem, "> 5%", percentQuantities, QuantityType.valueOf("5.1 %"), true), //
|
||||
Arguments.of(percentItem, "$DELTA_PERCENT > 5", percentQuantities, QuantityType.valueOf("5.1 %"),
|
||||
|
||||
Reference in New Issue
Block a user