From 3659542baeedef979db474f60c5acc951a7a36ea Mon Sep 17 00:00:00 2001 From: Cody Cutrer Date: Sun, 16 Oct 2022 05:50:46 -0600 Subject: [PATCH] Support mired units (#3108) * Support mired units Mired are fairly common to describe the color temperature of lightbulbs (slightly less common than Kelvin), but are very useful for various calculations when adjusting the color temperature, as well as being necessary for various integerations that require mired units. This commit makes them a well-known unit (previously they were still usable, using "MK^-1"), as well as making them easier to work with on QuantityType. The hiccup is that Mireds aren't technically a Temperature dimension, because they're a reciprocal. So add a `inverse` method that delegates to javax.measure's same method, and then use it as necessary when doing unit conversions and comparisons. Unfortunately, because the dimension changes, the return value of a conversion won't necessarily be the same type, an additional method is added for callers that are willing to handle the change in dimension. This is implemented for all callers that can use it in core. Signed-off-by: Cody Cutrer --- .../internal/SseItemStatesEventBuilder.java | 2 +- .../model/script/lib/NumberExtensions.java | 2 +- .../SystemHysteresisStateProfile.java | 7 ++-- .../profiles/SystemRangeStateProfile.java | 6 +-- .../ui/internal/items/ItemUIRegistryImpl.java | 4 +- .../core/library/items/NumberItem.java | 2 +- .../core/library/types/QuantityType.java | 38 +++++++++++++++++-- .../QuantityTypeArithmeticGroupFunction.java | 2 +- .../org/openhab/core/library/unit/Units.java | 2 + .../openhab/core/types/util/UnitUtils.java | 8 +++- .../core/library/items/NumberItemTest.java | 24 ++++++++++++ .../core/library/types/QuantityTypeTest.java | 11 ++++++ 12 files changed, 91 insertions(+), 17 deletions(-) diff --git a/bundles/org.openhab.core.io.rest.sse/src/main/java/org/openhab/core/io/rest/sse/internal/SseItemStatesEventBuilder.java b/bundles/org.openhab.core.io.rest.sse/src/main/java/org/openhab/core/io/rest/sse/internal/SseItemStatesEventBuilder.java index 3391c3e9dd..f7f164edc5 100644 --- a/bundles/org.openhab.core.io.rest.sse/src/main/java/org/openhab/core/io/rest/sse/internal/SseItemStatesEventBuilder.java +++ b/bundles/org.openhab.core.io.rest.sse/src/main/java/org/openhab/core/io/rest/sse/internal/SseItemStatesEventBuilder.java @@ -135,7 +135,7 @@ public class SseItemStatesEventBuilder { // state description will display the new unit: Unit patternUnit = UnitUtils.parseUnit(pattern); if (patternUnit != null && !quantityState.getUnit().equals(patternUnit)) { - quantityState = quantityState.toUnit(patternUnit); + quantityState = quantityState.toInvertibleUnit(patternUnit); } if (quantityState != null) { diff --git a/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/lib/NumberExtensions.java b/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/lib/NumberExtensions.java index 2cfe4428f2..d1b37673f6 100644 --- a/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/lib/NumberExtensions.java +++ b/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/lib/NumberExtensions.java @@ -374,7 +374,7 @@ public class NumberExtensions { public static BigDecimal numberToBigDecimal(Number number) { if (number instanceof QuantityType) { QuantityType state = ((QuantityType) number) - .toUnit(((QuantityType) number).getUnit().getSystemUnit()); + .toInvertibleUnit(((QuantityType) number).getUnit().getSystemUnit()); if (state != null) { return state.toBigDecimal(); } diff --git a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/profiles/SystemHysteresisStateProfile.java b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/profiles/SystemHysteresisStateProfile.java index 28fc07f6e5..83f09b371d 100644 --- a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/profiles/SystemHysteresisStateProfile.java +++ b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/profiles/SystemHysteresisStateProfile.java @@ -66,7 +66,8 @@ public class SystemHysteresisStateProfile implements StateProfile { } this.lower = lowerParam; final QuantityType upperParam = getParam(context, UPPER_PARAM); - final QuantityType convertedUpperParam = upperParam == null ? lower : upperParam.toUnit(lower.getUnit()); + final QuantityType convertedUpperParam = upperParam == null ? lower + : upperParam.toInvertibleUnit(lower.getUnit()); if (convertedUpperParam == null) { throw new IllegalArgumentException( String.format("Units of parameters '%s' and '%s' are not compatible: %s != %s", LOWER_PARAM, @@ -145,8 +146,8 @@ public class SystemHysteresisStateProfile implements StateProfile { finalLower = new QuantityType<>(lower.toBigDecimal(), qtState.getUnit()); finalUpper = new QuantityType<>(upper.toBigDecimal(), qtState.getUnit()); } else { - finalLower = lower.toUnit(qtState.getUnit()); - finalUpper = upper.toUnit(qtState.getUnit()); + finalLower = lower.toInvertibleUnit(qtState.getUnit()); + finalUpper = upper.toInvertibleUnit(qtState.getUnit()); if (finalLower == null || finalUpper == null) { logger.warn( "Cannot compare state '{}' to boundaries because units (lower={}, upper={}) do not match.", diff --git a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/profiles/SystemRangeStateProfile.java b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/profiles/SystemRangeStateProfile.java index 84ecd36e25..adb5dc025d 100644 --- a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/profiles/SystemRangeStateProfile.java +++ b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/profiles/SystemRangeStateProfile.java @@ -69,7 +69,7 @@ public class SystemRangeStateProfile implements StateProfile { if (upperParam == null) { throw new IllegalArgumentException(String.format("Parameter '%s' is not a Number value.", UPPER_PARAM)); } - final QuantityType convertedUpperParam = upperParam.toUnit(lower.getUnit()); + final QuantityType convertedUpperParam = upperParam.toInvertibleUnit(lower.getUnit()); if (convertedUpperParam == null) { throw new IllegalArgumentException( String.format("Units of parameters '%s' and '%s' are not compatible: %s != %s", LOWER_PARAM, @@ -153,8 +153,8 @@ public class SystemRangeStateProfile implements StateProfile { finalLower = new QuantityType<>(lower.toBigDecimal(), qtState.getUnit()); finalUpper = new QuantityType<>(upper.toBigDecimal(), qtState.getUnit()); } else { - finalLower = lower.toUnit(qtState.getUnit()); - finalUpper = upper.toUnit(qtState.getUnit()); + finalLower = lower.toInvertibleUnit(qtState.getUnit()); + finalUpper = upper.toInvertibleUnit(qtState.getUnit()); if (finalLower == null || finalUpper == null) { logger.warn( "Cannot compare state '{}' to boundaries because units (lower={}, upper={}) do not match.", diff --git a/bundles/org.openhab.core.ui/src/main/java/org/openhab/core/ui/internal/items/ItemUIRegistryImpl.java b/bundles/org.openhab.core.ui/src/main/java/org/openhab/core/ui/internal/items/ItemUIRegistryImpl.java index 27a826fb90..f8302862cd 100644 --- a/bundles/org.openhab.core.ui/src/main/java/org/openhab/core/ui/internal/items/ItemUIRegistryImpl.java +++ b/bundles/org.openhab.core.ui/src/main/java/org/openhab/core/ui/internal/items/ItemUIRegistryImpl.java @@ -413,7 +413,7 @@ public class ItemUIRegistryImpl implements ItemUIRegistry { // display the new unit: Unit patternUnit = UnitUtils.parseUnit(formatPattern); if (patternUnit != null && !quantityState.getUnit().equals(patternUnit)) { - quantityState = quantityState.toUnit(patternUnit); + quantityState = quantityState.toInvertibleUnit(patternUnit); } // The widget may define its own unit in the widget label. Convert to this unit: @@ -462,7 +462,7 @@ public class ItemUIRegistryImpl implements ItemUIRegistry { private QuantityType convertStateToWidgetUnit(QuantityType quantityState, Widget w) { Unit widgetUnit = UnitUtils.parseUnit(getFormatPattern(w.getLabel())); if (widgetUnit != null && !widgetUnit.equals(quantityState.getUnit())) { - return Objects.requireNonNullElse(quantityState.toUnit(widgetUnit), quantityState); + return Objects.requireNonNullElse(quantityState.toInvertibleUnit(widgetUnit), quantityState); } return quantityState; diff --git a/bundles/org.openhab.core/src/main/java/org/openhab/core/library/items/NumberItem.java b/bundles/org.openhab.core/src/main/java/org/openhab/core/library/items/NumberItem.java index 90e22bc9b0..4f337676f1 100644 --- a/bundles/org.openhab.core/src/main/java/org/openhab/core/library/items/NumberItem.java +++ b/bundles/org.openhab.core/src/main/java/org/openhab/core/library/items/NumberItem.java @@ -124,7 +124,7 @@ public class NumberItem extends GenericItem { Unit stateUnit = ((QuantityType) state).getUnit(); if (itemUnit != null && (!stateUnit.getSystemUnit().equals(itemUnit.getSystemUnit()) || UnitUtils.isDifferentMeasurementSystem(itemUnit, stateUnit))) { - QuantityType convertedState = ((QuantityType) state).toUnit(itemUnit); + QuantityType convertedState = ((QuantityType) state).toInvertibleUnit(itemUnit); if (convertedState != null) { super.setState(convertedState); return; diff --git a/bundles/org.openhab.core/src/main/java/org/openhab/core/library/types/QuantityType.java b/bundles/org.openhab.core/src/main/java/org/openhab/core/library/types/QuantityType.java index d4bed2c4ee..22869b661a 100644 --- a/bundles/org.openhab.core/src/main/java/org/openhab/core/library/types/QuantityType.java +++ b/bundles/org.openhab.core/src/main/java/org/openhab/core/library/types/QuantityType.java @@ -219,9 +219,10 @@ public class QuantityType> extends Number return false; } QuantityType other = (QuantityType) obj; - if (!quantity.getUnit().isCompatible(other.quantity.getUnit())) { + if (!quantity.getUnit().isCompatible(other.quantity.getUnit()) + && !quantity.getUnit().inverse().isCompatible(other.quantity.getUnit())) { return false; - } else if (compareTo((QuantityType) other) != 0) { + } else if (internalCompareTo(other) != 0) { return false; } @@ -230,6 +231,10 @@ public class QuantityType> extends Number @Override public int compareTo(QuantityType o) { + return internalCompareTo((QuantityType) o); + } + + private int internalCompareTo(QuantityType o) { if (quantity.getUnit().isCompatible(o.quantity.getUnit())) { QuantityType v1 = this.toUnit(getUnit().getSystemUnit()); QuantityType v2 = o.toUnit(o.getUnit().getSystemUnit()); @@ -238,6 +243,8 @@ public class QuantityType> extends Number } else { throw new IllegalArgumentException("Unable to convert to system unit during compare."); } + } else if (quantity.getUnit().inverse().isCompatible(o.quantity.getUnit())) { + return inverse().internalCompareTo(o); } else { throw new IllegalArgumentException("Can not compare incompatible units."); } @@ -255,7 +262,7 @@ public class QuantityType> extends Number * Convert this QuantityType to a new {@link QuantityType} using the given target unit. * * @param targetUnit the unit to which this {@link QuantityType} will be converted to. - * @return the new {@link QuantityType} in the given {@link Unit} or {@code null} in case of a + * @return the new {@link QuantityType} in the given {@link Unit} or {@code null} in case of an error. */ @SuppressWarnings("unchecked") public @Nullable QuantityType toUnit(Unit targetUnit) { @@ -283,6 +290,22 @@ public class QuantityType> extends Number return null; } + /** + * Convert this QuantityType to a new {@link QuantityType} using the given target unit. + * + * Implicit conversions using inverse units are allowed (i.e. mired <=> Kelvin). This may + * change the dimension. + * + * @param targetUnit the unit to which this {@link QuantityType} will be converted to. + * @return the new {@link QuantityType} in the given {@link Unit} or {@code null} in case of an erro. + */ + public @Nullable QuantityType toInvertibleUnit(Unit targetUnit) { + if (!targetUnit.equals(getUnit()) && getUnit().inverse().isCompatible(targetUnit)) { + return inverse().toUnit(targetUnit); + } + return toUnit(targetUnit); + } + public BigDecimal toBigDecimal() { return new BigDecimal(quantity.getValue().toString()); } @@ -490,4 +513,13 @@ public class QuantityType> extends Number .get(); return new QuantityType(sum); } + + /** + * Return the reciprocal of this QuantityType. + * + * @return a QuantityType with both the value and unit reciprocated + */ + public QuantityType inverse() { + return new QuantityType<>(this.quantity.inverse()); + } } diff --git a/bundles/org.openhab.core/src/main/java/org/openhab/core/library/types/QuantityTypeArithmeticGroupFunction.java b/bundles/org.openhab.core/src/main/java/org/openhab/core/library/types/QuantityTypeArithmeticGroupFunction.java index 74a0985ee6..0b4b4a32ae 100644 --- a/bundles/org.openhab.core/src/main/java/org/openhab/core/library/types/QuantityTypeArithmeticGroupFunction.java +++ b/bundles/org.openhab.core/src/main/java/org/openhab/core/library/types/QuantityTypeArithmeticGroupFunction.java @@ -92,7 +92,7 @@ public interface QuantityTypeArithmeticGroupFunction extends GroupFunction { sum = itemState; // initialise the sum from the first item count++; } else { - itemState = itemState.toUnit(sum.getUnit()); + itemState = itemState.toInvertibleUnit(sum.getUnit()); if (itemState != null) { sum = sum.add(itemState); count++; diff --git a/bundles/org.openhab.core/src/main/java/org/openhab/core/library/unit/Units.java b/bundles/org.openhab.core/src/main/java/org/openhab/core/library/unit/Units.java index fee3aa592e..0419aca16f 100644 --- a/bundles/org.openhab.core/src/main/java/org/openhab/core/library/unit/Units.java +++ b/bundles/org.openhab.core/src/main/java/org/openhab/core/library/unit/Units.java @@ -171,6 +171,7 @@ public final class Units extends CustomUnits { MultiplyConverter.ofRational(BigInteger.valueOf(1852), BigInteger.valueOf(1000)))); public static final Unit STERADIAN = addUnit(tech.units.indriya.unit.Units.STERADIAN); public static final Unit KELVIN = addUnit(tech.units.indriya.unit.Units.KELVIN); + public static final Unit MIRED = addUnit(MetricPrefix.MEGA(tech.units.indriya.unit.Units.KELVIN).inverse()); public static final Unit