mirror of
https://github.com/danieldemus/openhab-core.git
synced 2025-01-10 21:31:53 +01:00
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 <cody@cutrer.us>
This commit is contained in:
parent
7f38d419c6
commit
3659542bae
@ -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) {
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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.",
|
||||
|
@ -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.",
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -219,9 +219,10 @@ public class QuantityType<T extends Quantity<T>> 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<T>) other) != 0) {
|
||||
} else if (internalCompareTo(other) != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -230,6 +231,10 @@ public class QuantityType<T extends Quantity<T>> extends Number
|
||||
|
||||
@Override
|
||||
public int compareTo(QuantityType<T> o) {
|
||||
return internalCompareTo((QuantityType<?>) o);
|
||||
}
|
||||
|
||||
private int internalCompareTo(QuantityType<?> o) {
|
||||
if (quantity.getUnit().isCompatible(o.quantity.getUnit())) {
|
||||
QuantityType<T> v1 = this.toUnit(getUnit().getSystemUnit());
|
||||
QuantityType<?> v2 = o.toUnit(o.getUnit().getSystemUnit());
|
||||
@ -238,6 +243,8 @@ public class QuantityType<T extends Quantity<T>> 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<T extends Quantity<T>> 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<T> toUnit(Unit<?> targetUnit) {
|
||||
@ -283,6 +290,22 @@ public class QuantityType<T extends Quantity<T>> 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<T extends Quantity<T>> extends Number
|
||||
.get();
|
||||
return new QuantityType<T>(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());
|
||||
}
|
||||
}
|
||||
|
@ -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++;
|
||||
|
@ -171,6 +171,7 @@ public final class Units extends CustomUnits {
|
||||
MultiplyConverter.ofRational(BigInteger.valueOf(1852), BigInteger.valueOf(1000))));
|
||||
public static final Unit<SolidAngle> STERADIAN = addUnit(tech.units.indriya.unit.Units.STERADIAN);
|
||||
public static final Unit<Temperature> 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<Time> SECOND = addUnit(tech.units.indriya.unit.Units.SECOND);
|
||||
public static final Unit<Time> MINUTE = addUnit(tech.units.indriya.unit.Units.MINUTE);
|
||||
public static final Unit<Time> HOUR = addUnit(tech.units.indriya.unit.Units.HOUR);
|
||||
@ -266,6 +267,7 @@ public final class Units extends CustomUnits {
|
||||
SimpleUnitFormat.getInstance().label(MILLIAMPERE_HOUR, "mAh");
|
||||
SimpleUnitFormat.getInstance().label(MILLIBAR, "mbar");
|
||||
SimpleUnitFormat.getInstance().label(MILLIMETRE_OF_MERCURY, MILLIMETRE_OF_MERCURY.getSymbol());
|
||||
SimpleUnitFormat.getInstance().label(MIRED, "mired");
|
||||
SimpleUnitFormat.getInstance().label(PARTS_PER_BILLION, "ppb");
|
||||
SimpleUnitFormat.getInstance().label(PARTS_PER_MILLION, "ppm");
|
||||
SimpleUnitFormat.getInstance().label(PETABYTE, "PB");
|
||||
|
@ -16,6 +16,7 @@ import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.lang.reflect.Type;
|
||||
import java.lang.reflect.WildcardType;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Set;
|
||||
@ -116,8 +117,11 @@ public class UnitUtils {
|
||||
if (field.getType().isAssignableFrom(Unit.class) && Modifier.isStatic(field.getModifiers())) {
|
||||
Type genericType = field.getGenericType();
|
||||
if (genericType instanceof ParameterizedType) {
|
||||
String dimension = ((Class<?>) ((ParameterizedType) genericType).getActualTypeArguments()[0])
|
||||
.getSimpleName();
|
||||
Type typeParam = ((ParameterizedType) genericType).getActualTypeArguments()[0];
|
||||
if (typeParam instanceof WildcardType) {
|
||||
continue;
|
||||
}
|
||||
String dimension = ((Class<?>) typeParam).getSimpleName();
|
||||
try {
|
||||
Unit<?> systemUnit = (Unit<?>) field.get(null);
|
||||
if (systemUnit == null) {
|
||||
|
@ -140,4 +140,28 @@ public class NumberItemTest {
|
||||
|
||||
assertThat(item.getStateDescription().getPattern(), is("%.1f " + UnitUtils.UNIT_PLACEHOLDER));
|
||||
}
|
||||
|
||||
@SuppressWarnings("null")
|
||||
@Test
|
||||
public void testMiredToKelvin() {
|
||||
NumberItem item = new NumberItem("Number:Temperature", ITEM_NAME);
|
||||
when(stateDescriptionServiceMock.getStateDescription(ITEM_NAME, null)).thenReturn(
|
||||
StateDescriptionFragmentBuilder.create().withPattern("%.0f K").build().toStateDescription());
|
||||
item.setStateDescriptionService(stateDescriptionServiceMock);
|
||||
item.setState(new QuantityType<>("370 mired"));
|
||||
|
||||
assertThat(item.getState().format("%.0f K"), is("2703 K"));
|
||||
}
|
||||
|
||||
@SuppressWarnings("null")
|
||||
@Test
|
||||
public void testKelvinToMired() {
|
||||
NumberItem item = new NumberItem("Number:Temperature", ITEM_NAME);
|
||||
when(stateDescriptionServiceMock.getStateDescription(ITEM_NAME, null)).thenReturn(
|
||||
StateDescriptionFragmentBuilder.create().withPattern("%.0f mired").build().toStateDescription());
|
||||
item.setStateDescriptionService(stateDescriptionServiceMock);
|
||||
item.setState(new QuantityType<>("2700 K"));
|
||||
|
||||
assertThat(item.getState().format("%.0f mired"), is("370 mired"));
|
||||
}
|
||||
}
|
||||
|
@ -472,4 +472,15 @@ public class QuantityTypeTest {
|
||||
QuantityType<DataTransferRate> octets = gsm2G.toUnit(MetricPrefix.KILO(Units.OCTET).divide(Units.SECOND));
|
||||
assertEquals(14375, octets.intValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMireds() {
|
||||
QuantityType<Temperature> colorTemp = new QuantityType<>("2700 K");
|
||||
QuantityType<?> mireds = colorTemp.toInvertibleUnit(Units.MIRED);
|
||||
assertEquals(370, mireds.intValue());
|
||||
assertThat(colorTemp.equals(mireds), is(true));
|
||||
assertThat(mireds.equals(colorTemp), is(true));
|
||||
QuantityType<?> andBack = mireds.toInvertibleUnit(Units.KELVIN);
|
||||
assertEquals(2700, andBack.intValue());
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user