From a5c488d8c82f75b5ade26385c23b501c723fe637 Mon Sep 17 00:00:00 2001 From: "F. Vollmann" <1691981+fabianvo@users.noreply.github.com> Date: Thu, 26 Sep 2024 08:28:08 +0200 Subject: [PATCH] Add XOR ArithmeticGroupFunction (#4386) * #4385 add XOR ArithmeticGroupFunction (1 of n) Signed-off-by: Fabian Vollmann --- .../src/org/openhab/core/model/Items.xtext | 2 +- .../internal/items/GroupFunctionHelper.java | 8 ++ .../types/ArithmeticGroupFunction.java | 77 +++++++++++++++++++ .../types/ArithmeticGroupFunctionTest.java | 52 +++++++++++++ 4 files changed, 138 insertions(+), 1 deletion(-) diff --git a/bundles/org.openhab.core.model.item/src/org/openhab/core/model/Items.xtext b/bundles/org.openhab.core.model.item/src/org/openhab/core/model/Items.xtext index 266d585cc73..503dc3a7232 100644 --- a/bundles/org.openhab.core.model.item/src/org/openhab/core/model/Items.xtext +++ b/bundles/org.openhab.core.model.item/src/org/openhab/core/model/Items.xtext @@ -27,7 +27,7 @@ ModelGroupItem: ; enum ModelGroupFunction: - EQUALITY='EQUALITY' | AND='AND' | OR='OR' | NAND='NAND' | NOR='NOR' | AVG='AVG' | MEDIAN='MEDIAN' | SUM='SUM' | MAX='MAX' | MIN='MIN' | COUNT='COUNT' | LATEST='LATEST' | EARLIEST='EARLIEST' + EQUALITY='EQUALITY' | AND='AND' | OR='OR' | NAND='NAND' | NOR='NOR' | XOR='XOR' | AVG='AVG' | MEDIAN='MEDIAN' | SUM='SUM' | MAX='MAX' | MIN='MIN' | COUNT='COUNT' | LATEST='LATEST' | EARLIEST='EARLIEST' ; ModelNormalItem: diff --git a/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/items/GroupFunctionHelper.java b/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/items/GroupFunctionHelper.java index 0fdc4e165be..20757d9b3d7 100644 --- a/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/items/GroupFunctionHelper.java +++ b/bundles/org.openhab.core/src/main/java/org/openhab/core/internal/items/GroupFunctionHelper.java @@ -140,6 +140,14 @@ private GroupFunction createDefaultGroupFunction(GroupFunctionDTO function, @Nul logger.error("Group function 'NOT OR' requires two arguments. Using Equality instead."); } break; + case "XOR": + args = parseStates(baseItem, function.params); + if (args.size() == 2) { + return new ArithmeticGroupFunction.Xor(args.get(0), args.get(1)); + } else { + logger.error("Group function 'XOR' requires two arguments. Using Equality instead."); + } + break; case "COUNT": if (function.params != null && function.params.length == 1) { State countParam = new StringType(function.params[0]); diff --git a/bundles/org.openhab.core/src/main/java/org/openhab/core/library/types/ArithmeticGroupFunction.java b/bundles/org.openhab.core/src/main/java/org/openhab/core/library/types/ArithmeticGroupFunction.java index 9abec496149..7a7314d809b 100644 --- a/bundles/org.openhab.core/src/main/java/org/openhab/core/library/types/ArithmeticGroupFunction.java +++ b/bundles/org.openhab.core/src/main/java/org/openhab/core/library/types/ArithmeticGroupFunction.java @@ -35,6 +35,7 @@ * @author Kai Kreuzer - Initial contribution * @author Thomas Eichstädt-Engelen - Added "N" functions * @author Gaël L'hopital - Added count function + * @author Fabian Vollmann - Added XOR function */ @NonNullByDefault public interface ArithmeticGroupFunction extends GroupFunction { @@ -212,6 +213,82 @@ public State calculate(@Nullable Set items) { } } + /** + * This does a logical 'xor' operation. If exactly one item is of 'activeState' this + * is returned, otherwise the 'passiveState' is returned. + * + * Through the getStateAs() method, it can be determined, how many + * items actually are in the 'activeState'. + */ + class Xor implements GroupFunction { + + protected final State activeState; + protected final State passiveState; + + public Xor(@Nullable State activeValue, @Nullable State passiveValue) { + if (activeValue == null || passiveValue == null) { + throw new IllegalArgumentException("Parameters must not be null!"); + } + this.activeState = activeValue; + this.passiveState = passiveValue; + } + + @Override + public State calculate(@Nullable Set items) { + if (items != null) { + boolean foundOne = false; + + for (Item item : items) { + if (activeState.equals(item.getStateAs(activeState.getClass()))) { + if (foundOne) { + return passiveState; + } else { + foundOne = true; + } + } + } + if (foundOne) { + return activeState; + } + } + + return passiveState; + } + + @Override + public @Nullable T getStateAs(@Nullable Set items, Class stateClass) { + State state = calculate(items); + if (stateClass.isInstance(state)) { + return stateClass.cast(state); + } else { + if (stateClass == DecimalType.class) { + if (items != null) { + return stateClass.cast(new DecimalType(count(items, activeState))); + } else { + return stateClass.cast(DecimalType.ZERO); + } + } else { + return null; + } + } + } + + private int count(Set items, State state) { + int count = 0; + for (Item item : items) { + if (state.equals(item.getStateAs(state.getClass()))) { + count++; + } + } + return count; + } + + @Override + public State[] getParameters() { + return new State[] { activeState, passiveState }; + } + } + /** * This calculates the numeric average over all item states of decimal type. */ diff --git a/bundles/org.openhab.core/src/test/java/org/openhab/core/library/types/ArithmeticGroupFunctionTest.java b/bundles/org.openhab.core/src/test/java/org/openhab/core/library/types/ArithmeticGroupFunctionTest.java index dd15f2553e0..e079d642968 100644 --- a/bundles/org.openhab.core/src/test/java/org/openhab/core/library/types/ArithmeticGroupFunctionTest.java +++ b/bundles/org.openhab.core/src/test/java/org/openhab/core/library/types/ArithmeticGroupFunctionTest.java @@ -206,6 +206,58 @@ public void testNAndFunctionNegative() { assertEquals(OpenClosedType.OPEN, state); } + @Test + public void testXorFunction() { + Set items = new HashSet<>(); + items.add(new TestItem("TestItem1", OpenClosedType.OPEN)); + items.add(new TestItem("TestItem2", OpenClosedType.CLOSED)); + + GroupFunction function = new ArithmeticGroupFunction.Xor(OpenClosedType.OPEN, OpenClosedType.CLOSED); + State state = function.calculate(items); + + assertEquals(OpenClosedType.OPEN, state); + } + + @Test + public void testXorFunctionMultiple() { + Set items = new HashSet<>(); + items.add(new TestItem("TestItem1", OpenClosedType.CLOSED)); + items.add(new TestItem("TestItem2", OpenClosedType.CLOSED)); + items.add(new TestItem("TestItem3", OpenClosedType.OPEN)); + items.add(new TestItem("TestItem4", OpenClosedType.CLOSED)); + + GroupFunction function = new ArithmeticGroupFunction.Xor(OpenClosedType.OPEN, OpenClosedType.CLOSED); + State state = function.calculate(items); + + assertEquals(OpenClosedType.OPEN, state); + } + + @Test + public void testXorFunctionNegative() { + Set items = new HashSet<>(); + items.add(new TestItem("TestItem1", OpenClosedType.OPEN)); + items.add(new TestItem("TestItem2", OpenClosedType.OPEN)); + + GroupFunction function = new ArithmeticGroupFunction.Xor(OpenClosedType.OPEN, OpenClosedType.CLOSED); + State state = function.calculate(items); + + assertEquals(OpenClosedType.CLOSED, state); + } + + @Test + public void testXorFunctionNegativeMultiple() { + Set items = new HashSet<>(); + items.add(new TestItem("TestItem1", OpenClosedType.CLOSED)); + items.add(new TestItem("TestItem2", OpenClosedType.OPEN)); + items.add(new TestItem("TestItem3", OpenClosedType.OPEN)); + items.add(new TestItem("TestItem4", OpenClosedType.CLOSED)); + + GroupFunction function = new ArithmeticGroupFunction.Xor(OpenClosedType.OPEN, OpenClosedType.CLOSED); + State state = function.calculate(items); + + assertEquals(OpenClosedType.CLOSED, state); + } + @Test public void testAvgFunction() { Set items = new HashSet<>();