[homekit] increase flexibility of ColorTemperature (#13538)

* [homekit] increase flexibility of ColorTemperature

allow Number or Dimmer items, and mired or Kelvin units.

Signed-off-by: Cody Cutrer <cody@cutrer.us>
This commit is contained in:
Cody Cutrer 2022-10-22 12:24:51 -06:00 committed by GitHub
parent 16c0bae0dd
commit 66c7211b26
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 155 additions and 8 deletions

View File

@ -243,6 +243,38 @@ Examples:
Dimmer dimmer_light_3 "Dimmer Light 3" {homekit="Lighting, Lighting.Brightness" [dimmerMode="filterOnExceptBrightness100"]}
```
### Color Temperature
Color temperature can be represented various ways in OpenHAB. Given the base bulb configured like this:
```xtend
Group gLight "CCT Light" { homekit="Lighting" }
Switch light_switch (gLight) { homekit="Lighting.OnState" }
```
The color temperature might be configured in any of these ways:
```xtend
// Number item presumed in mireds
Number light_temp (gLight) { homekit="Lighting.ColorTemperature" }
// Number item explicitly in mireds
Number:Temperature light_temp "Temp [%.0f mired]" { homekit="Lighting.ColorTemperature" }
// Number item explicitly in Kelvin
Number:Temperature light_temp "Temp [%.0f K]" { homekit="Lighting.ColorTemperature" }
// Dimmer item, with allowed range given in mireds
Dimmer light_temp { homekit="Lighting.ColorTemperature"[ minValue=50, maxValue=400 ]}
// Dimmer item, with allowed range given in Kelvin
Dimmer light_temp { homekit="Lighting.ColorTemperature"[ minValue="2700 K", maxValue="5000 K" ]}
// Dimmer item, where 0% represents "warm" instead of "cool" (i.e. if it's backed by a channel
// that's ultimately interpreting the value in Kelvin instead of mireds)
Dimmer light_temp { homekit="Lighting.ColorTemperature"[ minValue="2700 K", maxValue="5000 K", inverted=true ]}
```
### Windows Covering (Blinds) / Window / Door
HomeKit Windows Covering, Window and Door accessory types have following mandatory characteristics:
@ -674,7 +706,7 @@ Support for this is planned for the future release of openHAB HomeKit binding.
| | | Hue | Dimmer, Color | Hue |
| | | Saturation | Dimmer, Color | Saturation in % (1-100) |
| | | Brightness | Dimmer, Color | Brightness in % (1-100). See "Usage of dimmer modes" for configuration details. |
| | | ColorTemperature | Number | Color temperature represented in reciprocal megaKelvin. The default value range is from 50 to 400. Color temperature should not be used in combination with hue, saturation and brightness. It supports following configuration parameters: minValue, maxValue |
| | | ColorTemperature | Number, Dimmer | Color temperature. If the item is a Number with no units, it is represented in mireds. The default value range is from 50 to 400 (2500 K to 20,000 K). If the item is a Dimmer, it will be transformed linearly to mireds. Color temperature should not be used in combination with hue, saturation and brightness. It supports following configuration parameters: minValue, maxValue, inverted |
| Fan | | | | Fan |
| | ActiveStatus | | Switch, Dimmer | Accessory current working status. A value of "ON"/"OPEN" indicates that the accessory is active and is functioning without any errors. |
| | | CurrentFanState | Number | Current fan state. values: 0=INACTIVE, 1=IDLE, 2=BLOWING AIR |

View File

@ -30,6 +30,7 @@ import org.openhab.core.library.items.SwitchItem;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.PercentType;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.types.State;
import org.openhab.core.types.StateDescription;
@ -55,6 +56,7 @@ public class HomekitTaggedItem {
public final static String MIN_VALUE = "minValue";
public final static String PRIMARY_SERVICE = "primary";
public final static String STEP = "step";
public final static String UNIT = "unit";
private static final Map<Integer, String> CREATED_ACCESSORY_IDS = new ConcurrentHashMap<>();
@ -181,6 +183,23 @@ public class HomekitTaggedItem {
getName());
}
/**
* Send QuantityType command to a NumberItem (or a Group:Number)
*
* @param command
*/
public void send(QuantityType command) {
if (getItem() instanceof GroupItem && getBaseItem() instanceof NumberItem) {
((GroupItem) getItem()).send(command);
return;
} else if (getItem() instanceof NumberItem) {
((NumberItem) getItem()).send(command);
return;
}
logger.warn("Received QuantityType command for item {} that doesn't support it. This is probably a bug.",
getName());
}
/**
* Send OnOffType command to a SwitchItem (or a Group:Switch)
*
@ -322,6 +341,9 @@ public class HomekitTaggedItem {
if ((value instanceof Long) && (defaultValue instanceof BigDecimal)) {
return (T) BigDecimal.valueOf((Long) value);
}
if (defaultValue instanceof String) {
return (T) value.toString();
}
}
}
@ -384,6 +406,27 @@ public class HomekitTaggedItem {
return getConfiguration(key, BigDecimal.valueOf(defaultValue)).doubleValue();
}
/**
* return configuration as quantity of the given unit
*
* @param key configuration key
* @param defaultValue default value
* @return value
*/
public QuantityType getConfigurationAsQuantity(String key, QuantityType defaultValue) {
String stringValue = getConfiguration(key, new String());
if (stringValue.isEmpty()) {
return defaultValue;
}
var parsedValue = new QuantityType(stringValue);
var convertedValue = parsedValue.toInvertibleUnit(defaultValue.getUnit());
// not convertible? just assume it's in the expected unit
if (convertedValue == null) {
return new QuantityType(parsedValue.toBigDecimal(), defaultValue.getUnit());
}
return convertedValue;
}
/**
* parse and apply item configuration.
*/

View File

@ -18,6 +18,7 @@ import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.function.BiFunction;
import java.util.function.Consumer;
@ -43,6 +44,7 @@ import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.library.unit.ImperialUnits;
import org.openhab.core.library.unit.SIUnits;
import org.openhab.core.library.unit.Units;
import org.openhab.core.types.State;
import org.openhab.core.types.UnDefType;
import org.openhab.io.homekit.Homekit;
@ -614,13 +616,83 @@ public class HomekitCharacteristicFactory {
private static ColorTemperatureCharacteristic createColorTemperatureCharacteristic(HomekitTaggedItem taggedItem,
HomekitAccessoryUpdater updater) {
int minValue = taggedItem.getConfigurationAsInt(HomekitTaggedItem.MIN_VALUE,
ColorTemperatureCharacteristic.DEFAULT_MIN_VALUE);
return new ColorTemperatureCharacteristic(minValue,
taggedItem.getConfigurationAsInt(HomekitTaggedItem.MAX_VALUE,
ColorTemperatureCharacteristic.DEFAULT_MAX_VALUE),
getIntSupplier(taggedItem, minValue), setIntConsumer(taggedItem),
getSubscriber(taggedItem, COLOR_TEMPERATURE, updater),
// Check if units are expressed in Kelvin, not mireds, and adjust
// the min/max appropriately
Unit unit = null;
var numberItem = taggedItem.getBaseItem();
if (numberItem instanceof NumberItem) {
unit = ((NumberItem) numberItem).getUnit();
}
if (unit == null) {
unit = Units.MIRED;
}
final Unit finalUnit = unit;
final boolean inverted = taggedItem.isInverted();
if (!unit.equals(Units.KELVIN) && !unit.equals(Units.MIRED)) {
logger.warn("Item {} must be in either K or mired. Given {}.", taggedItem.getName(), unit);
return new ColorTemperatureCharacteristic(null, null, null, null);
}
var minValueQt = taggedItem.getConfigurationAsQuantity(HomekitTaggedItem.MIN_VALUE,
Objects.requireNonNull(new QuantityType(ColorTemperatureCharacteristic.DEFAULT_MIN_VALUE, Units.MIRED)
.toInvertibleUnit(unit)));
var maxValueQt = taggedItem.getConfigurationAsQuantity(HomekitTaggedItem.MAX_VALUE,
Objects.requireNonNull(new QuantityType(ColorTemperatureCharacteristic.DEFAULT_MAX_VALUE, Units.MIRED)
.toInvertibleUnit(unit)));
int minValue = minValueQt.toInvertibleUnit(Units.MIRED).intValue();
int maxValue = maxValueQt.toInvertibleUnit(Units.MIRED).intValue();
// It's common to swap these if you're providing in Kelvin instead of mired
if (minValue > maxValue) {
int temp = minValue;
minValue = maxValue;
maxValue = temp;
}
final int finalMinValue = minValue;
final int range = maxValue - minValue;
return new ColorTemperatureCharacteristic(minValue, maxValue, () -> {
int value = finalMinValue;
final State state = taggedItem.getItem().getState();
if (state instanceof QuantityType<?>) {
// Number:Temperature
QuantityType<?> qt = (QuantityType<?>) state;
qt = qt.toInvertibleUnit(Units.MIRED);
if (qt == null) {
logger.warn("Item {}'s state '{}' is not convertible to mireds.", taggedItem.getName(), state);
} else {
value = qt.intValue();
}
} else if (state instanceof PercentType) {
double percent = ((PercentType) state).doubleValue();
// invert so that 0% == coolest
if (inverted) {
percent = 100.0 - percent;
}
// Dimmer
// scale to the originally configured range
value = (int) (percent * range / 100) + finalMinValue;
} else if (state instanceof DecimalType) {
value = ((DecimalType) state).intValue();
}
return CompletableFuture.completedFuture(value);
}, (value) -> {
if (taggedItem.getBaseItem() instanceof DimmerItem) {
// scale to a percent
double percent = (((double) value) - finalMinValue) * 100 / range;
if (inverted) {
percent = 100.0 - percent;
}
taggedItem.send(new PercentType(BigDecimal.valueOf(percent)));
} else if (taggedItem.getBaseItem() instanceof NumberItem) {
taggedItem.send(new QuantityType(value, Units.MIRED));
}
}, getSubscriber(taggedItem, COLOR_TEMPERATURE, updater),
getUnsubscriber(taggedItem, COLOR_TEMPERATURE, updater));
}