Skip to content

Commit

Permalink
[uom] Add unit metadata for NumberItem (openhab#3481)
Browse files Browse the repository at this point in the history
* Add defaultUnit metadata for NumberItem

Signed-off-by: Jan N. Klug <[email protected]>
GitOrigin-RevId: 9ef076d
  • Loading branch information
J-N-K authored and splatch committed Jul 12, 2023
1 parent 9ea0a0b commit e454a49
Show file tree
Hide file tree
Showing 31 changed files with 915 additions and 695 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,16 @@
package org.openhab.core.automation.internal.module.handler;

import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import java.time.ZoneId;
import java.util.Arrays;
import java.util.Collection;
import java.util.Map;

import javax.measure.quantity.Temperature;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
Expand All @@ -34,6 +37,7 @@
import org.openhab.core.automation.util.ConditionBuilder;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.i18n.TimeZoneProvider;
import org.openhab.core.i18n.UnitProvider;
import org.openhab.core.items.Item;
import org.openhab.core.items.ItemNotFoundException;
import org.openhab.core.items.ItemRegistry;
Expand Down Expand Up @@ -77,7 +81,9 @@ public ParameterSet(String itemType, String comparisonState, State itemState, bo
((NumberItem) item).setState(itemState);
break;
case "Number:Temperature":
item = new NumberItem("Number:Temperature", ITEM_NAME);
UnitProvider unitProviderMock = mock(UnitProvider.class);
when(unitProviderMock.getUnit(Temperature.class)).thenReturn(SIUnits.CELSIUS);
item = new NumberItem("Number:Temperature", ITEM_NAME, unitProviderMock);
((NumberItem) item).setState(itemState);
break;
case "Dimmer":
Expand All @@ -102,8 +108,8 @@ public static Collection<Object[]> equalsParameters() {
{ new ParameterSet("Number", "5", new DecimalType(23), false) }, //
{ new ParameterSet("Number", "5", new DecimalType(5), true) }, //
{ new ParameterSet("Number:Temperature", "5 °C", new DecimalType(23), false) }, //
{ new ParameterSet("Number:Temperature", "5 °C", new DecimalType(5), false) }, //
{ new ParameterSet("Number:Temperature", "0", new QuantityType<>(), true) }, //
{ new ParameterSet("Number:Temperature", "5 °C", new DecimalType(5), true) }, //
{ new ParameterSet("Number:Temperature", "0", new DecimalType(), false) }, //
{ new ParameterSet("Number:Temperature", "5", new QuantityType<>(23, SIUnits.CELSIUS), false) }, //
{ new ParameterSet("Number:Temperature", "5", new QuantityType<>(5, SIUnits.CELSIUS), false) }, //
{ new ParameterSet("Number:Temperature", "5 °C", new QuantityType<>(23, SIUnits.CELSIUS), false) }, //
Expand All @@ -119,7 +125,7 @@ public static Collection<Object[]> greaterThanParameters() {
{ new ParameterSet("Number", "5", new DecimalType(5), false) }, //
{ new ParameterSet("Number", "5 °C", new DecimalType(23), true) }, //
{ new ParameterSet("Number", "5 °C", new DecimalType(5), false) }, //
{ new ParameterSet("Number:Temperature", "0", new QuantityType<>(), false) }, //
{ new ParameterSet("Number:Temperature", "0", new DecimalType(), false) }, //
{ new ParameterSet("Number:Temperature", "5", new QuantityType<>(23, SIUnits.CELSIUS), true) }, //
{ new ParameterSet("Number:Temperature", "5", new QuantityType<>(5, SIUnits.CELSIUS), false) }, //
{ new ParameterSet("Number:Temperature", "5 °C", new QuantityType<>(23, SIUnits.CELSIUS), true) }, //
Expand All @@ -138,7 +144,7 @@ public static Collection<Object[]> greaterThanOrEqualsParameters() {
{ new ParameterSet("Number", "5 °C", new DecimalType(23), true) }, //
{ new ParameterSet("Number", "5 °C", new DecimalType(5), true) }, //
{ new ParameterSet("Number", "5 °C", new DecimalType(4), false) }, //
{ new ParameterSet("Number:Temperature", "0", new QuantityType<>(), true) }, //
{ new ParameterSet("Number:Temperature", "0", new DecimalType(), true) }, //
{ new ParameterSet("Number:Temperature", "5", new QuantityType<>(23, SIUnits.CELSIUS), true) }, //
{ new ParameterSet("Number:Temperature", "5", new QuantityType<>(5, SIUnits.CELSIUS), true) }, //
{ new ParameterSet("Number:Temperature", "5", new QuantityType<>(4, SIUnits.CELSIUS), false) }, //
Expand All @@ -159,7 +165,7 @@ public static Collection<Object[]> lessThanParameters() {
{ new ParameterSet("Number", "5", new DecimalType(4), true) }, //
{ new ParameterSet("Number", "5 °C", new DecimalType(23), false) }, //
{ new ParameterSet("Number", "5 °C", new DecimalType(4), true) }, //
{ new ParameterSet("Number:Temperature", "0", new QuantityType<>(), false) }, //
{ new ParameterSet("Number:Temperature", "0", new DecimalType(), false) }, //
{ new ParameterSet("Number:Temperature", "5", new QuantityType<>(23, SIUnits.CELSIUS), false) }, //
{ new ParameterSet("Number:Temperature", "5", new QuantityType<>(4, SIUnits.CELSIUS), true) }, //
{ new ParameterSet("Number:Temperature", "5 °C", new QuantityType<>(23, SIUnits.CELSIUS), false) }, //
Expand All @@ -179,7 +185,7 @@ public static Collection<Object[]> lessThanOrEqualsParameters() {
{ new ParameterSet("Number", "5 °C", new DecimalType(23), false) }, //
{ new ParameterSet("Number", "5 °C", new DecimalType(5), true) }, //
{ new ParameterSet("Number", "5 °C", new DecimalType(4), true) }, //
{ new ParameterSet("Number:Temperature", "0", new QuantityType<>(), true) }, //
{ new ParameterSet("Number:Temperature", "0", new DecimalType(), true) }, //
{ new ParameterSet("Number:Temperature", "5", new QuantityType<>(23, SIUnits.CELSIUS), false) }, //
{ new ParameterSet("Number:Temperature", "5", new QuantityType<>(5, SIUnits.CELSIUS), true) }, //
{ new ParameterSet("Number:Temperature", "5", new QuantityType<>(4, SIUnits.CELSIUS), true) }, //
Expand Down Expand Up @@ -220,9 +226,11 @@ public void testEqualsCondition(ParameterSet parameterSet) {
ItemStateConditionHandler handler = initItemStateConditionHandler("=", parameterSet.comparisonState);

if (parameterSet.expectedResult) {
assertTrue(handler.isSatisfied(Map.of()));
assertTrue(handler.isSatisfied(Map.of()),
parameterSet.item + ", comparisonState=" + parameterSet.comparisonState);
} else {
assertFalse(handler.isSatisfied(Map.of()));
assertFalse(handler.isSatisfied(Map.of()),
parameterSet.item + ", comparisonState=" + parameterSet.comparisonState);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,12 +142,12 @@ public class I18nProviderImpl
// UnitProvider
static final String MEASUREMENT_SYSTEM = "measurementSystem";
private @Nullable SystemOfUnits measurementSystem;
private final Map<Class<? extends Quantity<?>>, Map<SystemOfUnits, Unit<? extends Quantity<?>>>> dimensionMap = new HashMap<>();
private static final Map<Class<? extends Quantity<?>>, Map<SystemOfUnits, Unit<? extends Quantity<?>>>> DIMENSION_MAP = getDimensionMap();

@Activate
@SuppressWarnings("unchecked")
public I18nProviderImpl(ComponentContext componentContext) {
initDimensionMap();
getDimensionMap();
modified((Map<String, Object>) componentContext.getProperties());

this.resourceBundleTracker = new ResourceBundleTracker(componentContext.getBundleContext(), this);
Expand Down Expand Up @@ -187,16 +187,12 @@ private void setMeasurementSystem(@Nullable String measurementSystem) {

final SystemOfUnits newMeasurementSystem;
switch (ms) {
case SIUnits.MEASUREMENT_SYSTEM_NAME:
newMeasurementSystem = SIUnits.getInstance();
break;
case ImperialUnits.MEASUREMENT_SYSTEM_NAME:
newMeasurementSystem = ImperialUnits.getInstance();
break;
default:
case SIUnits.MEASUREMENT_SYSTEM_NAME -> newMeasurementSystem = SIUnits.getInstance();
case ImperialUnits.MEASUREMENT_SYSTEM_NAME -> newMeasurementSystem = ImperialUnits.getInstance();
default -> {
logger.debug("Error setting measurement system for value '{}'.", measurementSystem);
newMeasurementSystem = null;
break;
}
}
this.measurementSystem = newMeasurementSystem;

Expand Down Expand Up @@ -358,12 +354,14 @@ public Locale getLocale() {

@Override
@SuppressWarnings("unchecked")
public <T extends Quantity<T>> @Nullable Unit<T> getUnit(@Nullable Class<T> dimension) {
Map<SystemOfUnits, Unit<? extends Quantity<?>>> map = dimensionMap.get(dimension);
public <T extends Quantity<T>> Unit<T> getUnit(Class<T> dimension) {
Map<SystemOfUnits, Unit<? extends Quantity<?>>> map = DIMENSION_MAP.get(dimension);
if (map == null) {
return null;
throw new IllegalArgumentException("Dimension " + dimension.getName() + " is unknown. This is a bug.");
}
return (Unit<T>) map.get(getMeasurementSystem());
Unit<T> unit = (Unit<T>) map.get(getMeasurementSystem());
assert unit != null;
return unit;
}

@Override
Expand All @@ -380,54 +378,62 @@ public SystemOfUnits getMeasurementSystem() {
return SIUnits.getInstance();
}

private void initDimensionMap() {
addDefaultUnit(Acceleration.class, Units.METRE_PER_SQUARE_SECOND);
addDefaultUnit(AmountOfSubstance.class, Units.MOLE);
addDefaultUnit(Angle.class, Units.DEGREE_ANGLE, Units.DEGREE_ANGLE);
addDefaultUnit(Area.class, SIUnits.SQUARE_METRE, ImperialUnits.SQUARE_FOOT);
addDefaultUnit(ArealDensity.class, Units.DOBSON_UNIT);
addDefaultUnit(CatalyticActivity.class, Units.KATAL);
addDefaultUnit(DataAmount.class, Units.BYTE);
addDefaultUnit(DataTransferRate.class, Units.MEGABIT_PER_SECOND);
addDefaultUnit(Density.class, Units.KILOGRAM_PER_CUBICMETRE);
addDefaultUnit(Dimensionless.class, Units.ONE);
addDefaultUnit(ElectricCapacitance.class, Units.FARAD);
addDefaultUnit(ElectricCharge.class, Units.COULOMB);
addDefaultUnit(ElectricConductance.class, Units.SIEMENS);
addDefaultUnit(ElectricConductivity.class, Units.SIEMENS_PER_METRE);
addDefaultUnit(ElectricCurrent.class, Units.AMPERE);
addDefaultUnit(ElectricInductance.class, Units.HENRY);
addDefaultUnit(ElectricPotential.class, Units.VOLT);
addDefaultUnit(ElectricResistance.class, Units.OHM);
addDefaultUnit(Energy.class, Units.KILOWATT_HOUR);
addDefaultUnit(Force.class, Units.NEWTON);
addDefaultUnit(Frequency.class, Units.HERTZ);
addDefaultUnit(Illuminance.class, Units.LUX);
addDefaultUnit(Intensity.class, Units.IRRADIANCE);
addDefaultUnit(Length.class, SIUnits.METRE, ImperialUnits.INCH);
addDefaultUnit(LuminousFlux.class, Units.LUMEN);
addDefaultUnit(LuminousIntensity.class, Units.CANDELA);
addDefaultUnit(MagneticFlux.class, Units.WEBER);
addDefaultUnit(MagneticFluxDensity.class, Units.TESLA);
addDefaultUnit(Mass.class, SIUnits.KILOGRAM, ImperialUnits.POUND);
addDefaultUnit(Power.class, Units.WATT);
addDefaultUnit(Pressure.class, HECTO(SIUnits.PASCAL), ImperialUnits.INCH_OF_MERCURY);
addDefaultUnit(RadiationDoseAbsorbed.class, Units.GRAY);
addDefaultUnit(RadiationDoseEffective.class, Units.SIEVERT);
addDefaultUnit(Radioactivity.class, Units.BECQUEREL);
addDefaultUnit(SolidAngle.class, Units.STERADIAN);
addDefaultUnit(Speed.class, SIUnits.KILOMETRE_PER_HOUR, ImperialUnits.MILES_PER_HOUR);
addDefaultUnit(Temperature.class, SIUnits.CELSIUS, ImperialUnits.FAHRENHEIT);
addDefaultUnit(Time.class, Units.SECOND);
addDefaultUnit(Volume.class, SIUnits.CUBIC_METRE, ImperialUnits.GALLON_LIQUID_US);
addDefaultUnit(VolumetricFlowRate.class, Units.LITRE_PER_MINUTE, ImperialUnits.GALLON_PER_MINUTE);
public static Map<Class<? extends Quantity<?>>, Map<SystemOfUnits, Unit<? extends Quantity<?>>>> getDimensionMap() {
Map<Class<? extends Quantity<?>>, Map<SystemOfUnits, Unit<? extends Quantity<?>>>> dimensionMap = new HashMap<>();

addDefaultUnit(dimensionMap, Acceleration.class, Units.METRE_PER_SQUARE_SECOND);
addDefaultUnit(dimensionMap, AmountOfSubstance.class, Units.MOLE);
addDefaultUnit(dimensionMap, Angle.class, Units.DEGREE_ANGLE, Units.DEGREE_ANGLE);
addDefaultUnit(dimensionMap, Area.class, SIUnits.SQUARE_METRE, ImperialUnits.SQUARE_FOOT);
addDefaultUnit(dimensionMap, ArealDensity.class, Units.DOBSON_UNIT);
addDefaultUnit(dimensionMap, CatalyticActivity.class, Units.KATAL);
addDefaultUnit(dimensionMap, DataAmount.class, Units.BYTE);
addDefaultUnit(dimensionMap, DataTransferRate.class, Units.MEGABIT_PER_SECOND);
addDefaultUnit(dimensionMap, Density.class, Units.KILOGRAM_PER_CUBICMETRE);
addDefaultUnit(dimensionMap, Dimensionless.class, Units.ONE);
addDefaultUnit(dimensionMap, ElectricCapacitance.class, Units.FARAD);
addDefaultUnit(dimensionMap, ElectricCharge.class, Units.COULOMB);
addDefaultUnit(dimensionMap, ElectricConductance.class, Units.SIEMENS);
addDefaultUnit(dimensionMap, ElectricConductivity.class, Units.SIEMENS_PER_METRE);
addDefaultUnit(dimensionMap, ElectricCurrent.class, Units.AMPERE);
addDefaultUnit(dimensionMap, ElectricInductance.class, Units.HENRY);
addDefaultUnit(dimensionMap, ElectricPotential.class, Units.VOLT);
addDefaultUnit(dimensionMap, ElectricResistance.class, Units.OHM);
addDefaultUnit(dimensionMap, Energy.class, Units.KILOWATT_HOUR);
addDefaultUnit(dimensionMap, Force.class, Units.NEWTON);
addDefaultUnit(dimensionMap, Frequency.class, Units.HERTZ);
addDefaultUnit(dimensionMap, Illuminance.class, Units.LUX);
addDefaultUnit(dimensionMap, Intensity.class, Units.IRRADIANCE);
addDefaultUnit(dimensionMap, Length.class, SIUnits.METRE, ImperialUnits.INCH);
addDefaultUnit(dimensionMap, LuminousFlux.class, Units.LUMEN);
addDefaultUnit(dimensionMap, LuminousIntensity.class, Units.CANDELA);
addDefaultUnit(dimensionMap, MagneticFlux.class, Units.WEBER);
addDefaultUnit(dimensionMap, MagneticFluxDensity.class, Units.TESLA);
addDefaultUnit(dimensionMap, Mass.class, SIUnits.KILOGRAM, ImperialUnits.POUND);
addDefaultUnit(dimensionMap, Power.class, Units.WATT);
addDefaultUnit(dimensionMap, Pressure.class, HECTO(SIUnits.PASCAL), ImperialUnits.INCH_OF_MERCURY);
addDefaultUnit(dimensionMap, RadiationDoseAbsorbed.class, Units.GRAY);
addDefaultUnit(dimensionMap, RadiationDoseEffective.class, Units.SIEVERT);
addDefaultUnit(dimensionMap, Radioactivity.class, Units.BECQUEREL);
addDefaultUnit(dimensionMap, SolidAngle.class, Units.STERADIAN);
addDefaultUnit(dimensionMap, Speed.class, SIUnits.KILOMETRE_PER_HOUR, ImperialUnits.MILES_PER_HOUR);
addDefaultUnit(dimensionMap, Temperature.class, SIUnits.CELSIUS, ImperialUnits.FAHRENHEIT);
addDefaultUnit(dimensionMap, Time.class, Units.SECOND);
addDefaultUnit(dimensionMap, Volume.class, SIUnits.CUBIC_METRE, ImperialUnits.GALLON_LIQUID_US);
addDefaultUnit(dimensionMap, VolumetricFlowRate.class, Units.LITRE_PER_MINUTE, ImperialUnits.GALLON_PER_MINUTE);

return dimensionMap;
}

private <T extends Quantity<T>> void addDefaultUnit(Class<T> dimension, Unit<T> siUnit, Unit<T> imperialUnit) {
private static <T extends Quantity<T>> void addDefaultUnit(
Map<Class<? extends Quantity<?>>, Map<SystemOfUnits, Unit<? extends Quantity<?>>>> dimensionMap,
Class<T> dimension, Unit<T> siUnit, Unit<T> imperialUnit) {
dimensionMap.put(dimension, Map.of(SIUnits.getInstance(), siUnit, ImperialUnits.getInstance(), imperialUnit));
}

private <T extends Quantity<T>> void addDefaultUnit(Class<T> dimension, Unit<T> unit) {
private static <T extends Quantity<T>> void addDefaultUnit(
Map<Class<? extends Quantity<?>>, Map<SystemOfUnits, Unit<? extends Quantity<?>>>> dimensionMap,
Class<T> dimension, Unit<T> unit) {
dimensionMap.put(dimension, Map.of(SIUnits.getInstance(), unit, ImperialUnits.getInstance(), unit));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/**
* Copyright (c) 2010-2023 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;

import java.util.Map;

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.SIUnits;

/**
* The {@link TestUnitProvider} implements a {@link UnitProvider} for testing purposes
*
* @author Jan N. Klug - Initial contribution
*/
@NonNullByDefault
public class TestUnitProvider implements UnitProvider {

private final Map<Class<? extends Quantity<?>>, Map<SystemOfUnits, Unit<? extends Quantity<?>>>> dimensionMap = I18nProviderImpl
.getDimensionMap();

@Override
@SuppressWarnings("unchecked")
public <T extends Quantity<T>> Unit<T> getUnit(Class<T> dimension) {
Unit<T> unit = (Unit<T>) dimensionMap.getOrDefault(dimension, Map.of()).get(SIUnits.getInstance());
assert unit != null;
return unit;
}

@Override
public SystemOfUnits getMeasurementSystem() {
return SIUnits.getInstance();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
import javax.measure.spi.SystemOfUnits;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;

/**
* Provides {@link Unit}s and the current {@link SystemOfUnits}.
Expand All @@ -34,9 +33,10 @@ public interface UnitProvider {
* @param dimension The {@link Quantity}, called dimension here, defines the base unit for the retrieved unit. E.g.
* call {@code getUnit(javax.measure.quantity.Temperature.class)} to retrieve the temperature unit
* according to the current {@link SystemOfUnits}.
* @return The {@link Unit} matching the given {@link Quantity}, {@code null} otherwise.
* @return The {@link Unit} matching the given {@link Quantity}
* @throws IllegalArgumentException when the dimension is unknown
*/
<T extends Quantity<T>> @Nullable Unit<T> getUnit(@Nullable Class<T> dimension);
<T extends Quantity<T>> Unit<T> getUnit(Class<T> dimension) throws IllegalArgumentException;

/**
* Returns the {@link SystemOfUnits} which is currently set, must not be null.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,12 @@
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.mockito.Mockito.mock;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.openhab.core.i18n.UnitProvider;
import org.openhab.core.items.GenericItem;
import org.openhab.core.items.GroupItem;
import org.openhab.core.library.CoreItemFactory;
Expand All @@ -38,7 +40,7 @@ public void setup() {

@Test
public void testFiltering() {
CoreItemFactory itemFactory = new CoreItemFactory();
CoreItemFactory itemFactory = new CoreItemFactory(mock(UnitProvider.class));

GroupItem group = new GroupItem("TestGroup");
GroupItem subGroup = new GroupItem("TestSubGroup");
Expand Down
Loading

0 comments on commit e454a49

Please sign in to comment.