Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Normalise GroupItem member calculations to use the GroupItem's Unit #4563

Open
wants to merge 15 commits into
base: main
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -90,15 +90,15 @@ private GroupFunction createDimensionGroupFunction(GroupFunctionDTO function, @N
final String functionName = function.name;
switch (functionName.toUpperCase()) {
case "AVG":
return new QuantityTypeArithmeticGroupFunction.Avg(dimension);
return new QuantityTypeArithmeticGroupFunction.Avg(dimension, baseItem);
case "MEDIAN":
return new QuantityTypeArithmeticGroupFunction.Median(dimension, baseItem);
case "SUM":
return new QuantityTypeArithmeticGroupFunction.Sum(dimension);
return new QuantityTypeArithmeticGroupFunction.Sum(dimension, baseItem);
case "MIN":
return new QuantityTypeArithmeticGroupFunction.Min(dimension);
return new QuantityTypeArithmeticGroupFunction.Min(dimension, baseItem);
case "MAX":
return new QuantityTypeArithmeticGroupFunction.Max(dimension);
return new QuantityTypeArithmeticGroupFunction.Max(dimension, baseItem);
default:
return createDefaultGroupFunction(function, baseItem);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import org.openhab.core.items.GroupItem;
import org.openhab.core.items.Item;
import org.openhab.core.library.items.NumberItem;
import org.openhab.core.library.unit.Units;
import org.openhab.core.types.State;
import org.openhab.core.types.UnDefType;
import org.openhab.core.util.Statistics;
Expand All @@ -42,9 +43,11 @@ public interface QuantityTypeArithmeticGroupFunction extends GroupFunction {
abstract class DimensionalGroupFunction implements GroupFunction {

protected final Class<? extends Quantity<?>> dimension;
protected final @Nullable Item baseItem;

public DimensionalGroupFunction(Class<? extends Quantity<?>> dimension) {
public DimensionalGroupFunction(Class<? extends Quantity<?>> dimension, @Nullable Item baseItem) {
andrewfg marked this conversation as resolved.
Show resolved Hide resolved
this.dimension = dimension;
this.baseItem = baseItem;
}

@Override
Expand All @@ -62,11 +65,26 @@ public State[] getParameters() {
return new State[0];
}

protected boolean isSameDimension(@Nullable Item item) {
if (item instanceof GroupItem groupItem) {
return isSameDimension(groupItem.getBaseItem());
protected boolean isCompatible(@Nullable Item member) {
if (member instanceof GroupItem groupItem) {
return isCompatible(groupItem.getBaseItem());
}
if ((baseItem instanceof NumberItem baseNI) && (member instanceof NumberItem memberNI)) {
Unit<? extends Quantity<?>> baseUnit = baseNI.getUnit();
Unit<? extends Quantity<?>> memberUnit = memberNI.getUnit();
if (baseUnit != null && memberUnit != null) {
return baseUnit.isCompatible(memberUnit) || baseUnit.isCompatible(memberUnit.inverse());
andrewfg marked this conversation as resolved.
Show resolved Hide resolved
}
}
return member instanceof NumberItem memberNI && dimension.equals(memberNI.getDimension());
}

protected Unit<?> getBaseUnitOrDefault(Item defaultItem) {
Unit<?> unit = baseItem instanceof NumberItem baseNI ? baseNI.getUnit() : null;
if (unit == null) {
unit = defaultItem instanceof NumberItem defaultNI ? defaultNI.getUnit() : null;
}
return item instanceof NumberItem ni && dimension.equals(ni.getDimension());
return unit != null ? unit : Units.ONE;
}
}

Expand All @@ -75,8 +93,8 @@ protected boolean isSameDimension(@Nullable Item item) {
*/
class Avg extends DimensionalGroupFunction {

public Avg(Class<? extends Quantity<?>> dimension) {
super(dimension);
public Avg(Class<? extends Quantity<?>> dimension, @Nullable Item baseItem) {
super(dimension, baseItem);
}

@Override
Expand All @@ -86,23 +104,27 @@ public State calculate(@Nullable Set<Item> items) {
return UnDefType.UNDEF;
}

Unit<?> unit = null;
QuantityType<?> sum = null;
int count = 0;
for (Item item : items) {
if (isSameDimension(item)) {
QuantityType itemState = item.getStateAs(QuantityType.class);
if (itemState != null) {
if (sum == null) {
sum = itemState; // initialise the sum from the first item
count++;
} else {
itemState = itemState.toInvertibleUnit(sum.getUnit());
if (itemState != null) {
sum = sum.add(itemState);
count++;
}
}
}
andrewfg marked this conversation as resolved.
Show resolved Hide resolved
if (!isCompatible(item)) {
continue;
}
QuantityType itemState = item.getStateAs(QuantityType.class);
if (itemState == null) {
continue;
}
if (unit == null) {
unit = getBaseUnitOrDefault(item);
}
if (sum == null) {
sum = QuantityType.valueOf(0, unit);
}
itemState = itemState.toInvertibleUnit(unit);
if (itemState != null) {
sum = sum.add(itemState);
count++;
}
}

Expand All @@ -120,35 +142,30 @@ public State calculate(@Nullable Set<Item> items) {
*/
class Median extends DimensionalGroupFunction {

private @Nullable Item baseItem;

public Median(Class<? extends Quantity<?>> dimension, @Nullable Item baseItem) {
super(dimension);
this.baseItem = baseItem;
super(dimension, baseItem);
}

@Override
@SuppressWarnings({ "unchecked", "rawtypes" })
public State calculate(@Nullable Set<Item> items) {
if (items != null) {
List<BigDecimal> values = new ArrayList<>();

Unit<?> unit = null;
if (baseItem instanceof NumberItem numberItem) {
unit = numberItem.getUnit();
}
for (Item item : items) {
if (!isSameDimension(item)) {
if (!isCompatible(item)) {
continue;
}
QuantityType itemState = item.getStateAs(QuantityType.class);
if (itemState == null) {
continue;
}
if (unit == null) {
unit = itemState.getUnit(); // set it to the first item's unit
unit = getBaseUnitOrDefault(item);
}
if (itemState.toInvertibleUnit(unit) instanceof QuantityType<?> inverted) {
values.add(inverted.toBigDecimal());
if (itemState.toInvertibleUnit(unit) instanceof QuantityType<?> value) {
values.add(value.toBigDecimal());
}
}

Expand All @@ -169,8 +186,8 @@ public State calculate(@Nullable Set<Item> items) {
*/
class Sum extends DimensionalGroupFunction {

public Sum(Class<? extends Quantity<?>> dimension) {
super(dimension);
public Sum(Class<? extends Quantity<?>> dimension, @Nullable Item baseItem) {
super(dimension, baseItem);
}

@Override
Expand All @@ -180,20 +197,25 @@ public State calculate(@Nullable Set<Item> items) {
return UnDefType.UNDEF;
}

Unit<?> unit = null;
QuantityType<?> sum = null;
for (Item item : items) {
if (isSameDimension(item)) {
QuantityType itemState = item.getStateAs(QuantityType.class);
if (itemState != null) {
if (sum == null) {
sum = itemState; // initialise the sum from the first item
} else {
itemState = itemState.toUnit(sum.getUnit());
if (itemState != null) {
sum = sum.add(itemState);
}
}
}
if (!isCompatible(item)) {
continue;
}
QuantityType itemState = item.getStateAs(QuantityType.class);
if (itemState == null) {
continue;
}
if (unit == null) {
unit = getBaseUnitOrDefault(item);
}
if (sum == null) {
sum = QuantityType.valueOf(0, unit);
}
itemState = itemState.toInvertibleUnit(unit);
if (itemState != null) {
sum = sum.add(itemState);
}
}

Expand All @@ -206,8 +228,8 @@ public State calculate(@Nullable Set<Item> items) {
*/
class Min extends DimensionalGroupFunction {

public Min(Class<? extends Quantity<?>> dimension) {
super(dimension);
public Min(Class<? extends Quantity<?>> dimension, @Nullable Item baseItem) {
super(dimension, baseItem);
}

@Override
Expand All @@ -217,16 +239,22 @@ public State calculate(@Nullable Set<Item> items) {
return UnDefType.UNDEF;
}

Unit<?> unit = null;
QuantityType<?> min = null;
for (Item item : items) {
if (isSameDimension(item)) {
QuantityType itemState = item.getStateAs(QuantityType.class);
if (itemState != null) {
if (min == null
|| (min.getUnit().isCompatible(itemState.getUnit()) && min.compareTo(itemState) > 0)) {
min = itemState;
}
}
if (!isCompatible(item)) {
continue;
}
QuantityType itemState = item.getStateAs(QuantityType.class);
if (itemState == null) {
continue;
}
if (unit == null) {
unit = getBaseUnitOrDefault(item);
}
itemState = itemState.toInvertibleUnit(unit);
if (itemState != null && (min == null || min.compareTo(itemState) > 0)) {
min = itemState;
}
}

Expand All @@ -239,8 +267,8 @@ public State calculate(@Nullable Set<Item> items) {
*/
class Max extends DimensionalGroupFunction {

public Max(Class<? extends Quantity<?>> dimension) {
super(dimension);
public Max(Class<? extends Quantity<?>> dimension, @Nullable Item groupItem) {
super(dimension, groupItem);
}

@Override
Expand All @@ -250,16 +278,22 @@ public State calculate(@Nullable Set<Item> items) {
return UnDefType.UNDEF;
}

Unit<?> unit = null;
QuantityType<?> max = null;
for (Item item : items) {
if (isSameDimension(item)) {
QuantityType itemState = item.getStateAs(QuantityType.class);
if (itemState != null) {
if (max == null
|| (max.getUnit().isCompatible(itemState.getUnit()) && max.compareTo(itemState) < 0)) {
max = itemState;
}
}
if (!isCompatible(item)) {
continue;
}
QuantityType itemState = item.getStateAs(QuantityType.class);
if (itemState == null) {
continue;
}
if (unit == null) {
unit = getBaseUnitOrDefault(item);
}
itemState = itemState.toInvertibleUnit(unit);
if (itemState != null && (max == null || max.compareTo(itemState) < 0)) {
max = itemState;
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* Copyright (c) 2010-2025 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.core.internal.i18n;
andrewfg marked this conversation as resolved.
Show resolved Hide resolved

import java.util.Collection;
import java.util.Set;

import javax.measure.Quantity;
import javax.measure.Unit;
import javax.measure.spi.SystemOfUnits;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.i18n.UnitProvider;
import org.openhab.core.library.unit.Units;

/**
* The {@link TestKelvinProvider} implements a {@link UnitProvider} for testing purposes
* that only returns {@link Units.KELVIN}
*
* @author Andrew Fiddian-Green - Initial contribution
*/
@NonNullByDefault
public class TestKelvinProvider implements UnitProvider {

@Override
@SuppressWarnings("unchecked")
public <T extends Quantity<T>> Unit<T> getUnit(Class<T> dimension) {
return (Unit<T>) Units.KELVIN;
}

@Override
public SystemOfUnits getMeasurementSystem() {
return Units.getInstance();
}

@Override
public Collection<Class<? extends Quantity<?>>> getAllDimensions() {
return Set.of();
}
}
Loading