[homekit] use quantity type conversions for temperature characteristics (#12083)

if an Item associated with a temperature characteristic has a QuantityType
(of dimension Temperature) as its state, regardless of current unit,
use that to convert to celsius instead of any other configuration.

Note that this is only for supply values to HomeKit; commands coming from
HomeKit will still send a DecimalType with units according to the HomeKit-wide
useFahrenheit configuration.

Signed-off-by: Cody Cutrer <cody@cutrer.us>
This commit is contained in:
Cody Cutrer 2022-01-23 05:40:14 -07:00 committed by GitHub
parent 6bd37cb02a
commit d4fb20d529
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 147 additions and 132 deletions

View File

@ -102,7 +102,7 @@ org.openhab.homekit:name=openHAB
| blockUserDeletion | Blocks HomeKit user deletion in openHAB and as result unpairing of devices. If you experience an issue with accessories becoming non-responsive after some time, try to enable this setting. You can also enable this setting if your HomeKit setup is done and you will not re-pair ios devices. | false |
| pin | Pin code used for pairing with iOS devices. Apparently, pin codes are provided by Apple and represent specific device types, so they cannot be chosen freely. The pin code 031-45-154 is used in sample applications and known to work. | 031-45-154 |
| startDelay | HomeKit start delay in seconds in case the number of accessories is lower than last time. This helps to avoid resetting home app in case not all items have been initialised properly before HomeKit integration start. | 30 |
| useFahrenheitTemperature | Set to true to use Fahrenheit degrees, or false to use Celsius degrees. | false |
| useFahrenheitTemperature | Set to true to use Fahrenheit degrees, or false to use Celsius degrees. Note if an item has a QuantityType as its state, this configuration is ignored and it's always converted properly. | false |
| thermostatTargetModeCool | Word used for activating the cooling mode of the device (if applicable). It can be overwritten at item level. | CoolOn |
| thermostatTargetModeHeat | Word used for activating the heating mode of the device (if applicable). It can be overwritten at item level. | HeatOn |
| thermostatTargetModeAuto | Word used for activating the automatic mode of the device (if applicable). It can be overwritten at item level. | Auto |
@ -307,8 +307,8 @@ Example:
```xtend
Group gThermostat "Thermostat" {homekit = "Thermostat"}
Number thermostat_current_temp "Thermostat Current Temp [%.1f C]" (gThermostat) {homekit = "CurrentTemperature"}
Number thermostat_target_temp "Thermostat Target Temp[%.1f C]" (gThermostat) {homekit = "TargetTemperature"}
Number thermostat_current_temp "Thermostat Current Temp [%.1f °C]" (gThermostat) {homekit = "CurrentTemperature"}
Number thermostat_target_temp "Thermostat Target Temp [%.1f °C]" (gThermostat) {homekit = "TargetTemperature"}
String thermostat_current_mode "Thermostat Current Mode" (gThermostat) {homekit = "CurrentHeatingCoolingMode"}
String thermostat_target_mode "Thermostat Target Mode" (gThermostat) {homekit = "TargetHeatingCoolingMode"}
```
@ -318,12 +318,12 @@ Example with thresholds:
```xtend
Group gThermostat "Thermostat" {homekit = "Thermostat"}
Number thermostat_current_temp "Thermostat Current Temp [%.1f C]" (gThermostat) {homekit = "CurrentTemperature"}
Number thermostat_target_temp "Thermostat Target Temp[%.1f C]" (gThermostat) {homekit = "TargetTemperature"}
Number thermostat_current_temp "Thermostat Current Temp [%.1f °C]" (gThermostat) {homekit = "CurrentTemperature"}
Number thermostat_target_temp "Thermostat Target Temp[%.1f °C]" (gThermostat) {homekit = "TargetTemperature"}
String thermostat_current_mode "Thermostat Current Mode" (gThermostat) {homekit = "CurrentHeatingCoolingMode"}
String thermostat_target_mode "Thermostat Target Mode" (gThermostat) {homekit = "TargetHeatingCoolingMode"}
Number thermostat_cool_thrs "Thermostat Cool Threshold Temp [%.1f C]" (gThermostat) {homekit = "CoolingThresholdTemperature"}
Number thermostat_heat_thrs "Thermostat Heat Threshold Temp [%.1f C]" (gThermostat) {homekit = "HeatingThresholdTemperature"}
Number thermostat_cool_thrs "Thermostat Cool Threshold Temp [%.1f °C]" (gThermostat) {homekit = "CoolingThresholdTemperature"}
Number thermostat_heat_thrs "Thermostat Heat Threshold Temp [%.1f °C]" (gThermostat) {homekit = "HeatingThresholdTemperature"}
```
#### Min / max temperatures
@ -331,14 +331,14 @@ Number thermostat_heat_thrs "Thermostat Heat Threshold Temp [%.1f C]"
Current and target temperatures have default min and max values. Any values below or above max limits will be replaced with min or max limits.
Default limits are:
- current temperature: min value = 0 C, max value = 100 C
- target temperature: min value = 10 C, max value = 38 C
- current temperature: min value = 0 °C, max value = 100 °C
- target temperature: min value = 10 °C, max value = 38 °C
You can overwrite default values using minValue and maxValue configuration at item level, e.g.
```xtend
Number thermostat_current_temp "Thermostat Current Temp [%.1f C]" (gThermostat) {homekit = "CurrentTemperature" [minValue=5, maxValue=30]}
Number thermostat_target_temp "Thermostat Target Temp[%.1f C]" (gThermostat) {homekit = "TargetTemperature" [minValue=10.5, maxValue=27]}
Number thermostat_current_temp "Thermostat Current Temp [%.1f °C]" (gThermostat) {homekit = "CurrentTemperature" [minValue=5, maxValue=30]}
Number thermostat_target_temp "Thermostat Target Temp[%.1f °C]" (gThermostat) {homekit = "TargetTemperature" [minValue=10.5, maxValue=27]}
```
If "useFahrenheitTemperature" is set to true, the min and max temperature must be provided in Fahrenheit.
@ -426,7 +426,7 @@ Sensors without optional characteristics:
```xtend
Switch leaksensor_single "Leak Sensor" {homekit="LeakSensor"}
Number light_sensor "Light Sensor" {homekit="LightSensor"}
Number temperature_sensor "Temperature Sensor [%.1f C]" {homekit="TemperatureSensor"}
Number temperature_sensor "Temperature Sensor [%.1f °C]" {homekit="TemperatureSensor"}
Contact contact_sensor "Contact Sensor" {homekit="ContactSensor"}
Contact contact_sensor "Contact Sensor" {homekit="ContactSensor" [inverted="true"]}
@ -656,7 +656,7 @@ Switch leaksensor_single "Leak Sensor single"
Switch lock "Lock single" {homekit="Lock"}
Switch valve_single "Valve single" {homekit="Valve" [homekitValveType="Shower"]}
Number temperature_sensor "Temperature Sensor [%.1f C]" {homekit="TemperatureSensor" [minValue=10.5, maxValue=27] }
Number temperature_sensor "Temperature Sensor [%.1f °C]" {homekit="TemperatureSensor" [minValue=10.5, maxValue=27] }
Number light_sensor "Light Sensor" {homekit="LightSensor"}
Group gValve "Valve Group" {homekit="Valve" [homekitValveType="Irrigation"]}
@ -665,8 +665,8 @@ Number valve_duration "Valve duration"
Number valve_remaining_duration "Valve remaining duration" (gValve) {homekit="Valve.RemainingDuration"}
Group gThermostat "Thermostat" {homekit="Thermostat"}
Number thermostat_current_temp "Thermostat Current Temp [%.1f C]" (gThermostat) {homekit="Thermostat.CurrentTemperature" [minValue=0, maxValue=40]}
Number thermostat_target_temp "Thermostat Target Temp[%.1f C]" (gThermostat) {homekit="Thermostat.TargetTemperature" [minValue=10.5, maxValue=27]}
Number thermostat_current_temp "Thermostat Current Temp [%.1f °C]" (gThermostat) {homekit="Thermostat.CurrentTemperature" [minValue=0, maxValue=40]}
Number thermostat_target_temp "Thermostat Target Temp[%.1f °C]" (gThermostat) {homekit="Thermostat.TargetTemperature" [minValue=10.5, maxValue=27]}
String thermostat_current_mode "Thermostat Current Mode" (gThermostat) {homekit="Thermostat.CurrentHeatingCoolingMode"}
String thermostat_target_mode "Thermostat Target Mode" (gThermostat) {homekit="Thermostat.TargetHeatingCoolingMode"}
@ -714,11 +714,11 @@ String security_target_state "Security Target State"
Group gCooler "Cooler Group" {homekit="HeaterCooler"}
Switch cooler_active "Cooler Active" (gCooler) {homekit="ActiveStatus"}
Number cooler_current_temp "Cooler Current Temp [%.1f C]" (gCooler) {homekit="CurrentTemperature"}
Number cooler_current_temp "Cooler Current Temp [%.1f °C]" (gCooler) {homekit="CurrentTemperature"}
String cooler_current_mode "Cooler Current Mode" (gCooler) {homekit="CurrentHeaterCoolerState" [HEATING="HEAT", COOLING="COOL"]}
String cooler_target_mode "Cooler Target Mode" (gCooler) {homekit="TargetHeaterCoolerState"}
Number cooler_cool_thrs "Cooler Cool Threshold Temp [%.1f C]" (gCooler) {homekit="CoolingThresholdTemperature" [minValue=10.5, maxValue=50]}
Number cooler_heat_thrs "Cooler Heat Threshold Temp [%.1f C]" (gCooler) {homekit="HeatingThresholdTemperature" [minValue=0.5, maxValue=20]}
Number cooler_cool_thrs "Cooler Cool Threshold Temp [%.1f °C]" (gCooler) {homekit="CoolingThresholdTemperature" [minValue=10.5, maxValue=50]}
Number cooler_heat_thrs "Cooler Heat Threshold Temp [%.1f °C]" (gCooler) {homekit="HeatingThresholdTemperature" [minValue=0.5, maxValue=20]}
```
## Additional Notes

View File

@ -140,19 +140,28 @@ abstract class AbstractHomekitAccessoryImpl implements HomekitAccessory {
}
}
protected @Nullable <T extends State> T getStateAs(HomekitCharacteristicType characteristic, Class<T> type) {
protected @Nullable State getState(HomekitCharacteristicType characteristic) {
final Optional<HomekitTaggedItem> taggedItem = getCharacteristic(characteristic);
if (taggedItem.isPresent()) {
final State state = taggedItem.get().getItem().getStateAs(type);
if (state != null) {
return state.as(type);
}
return taggedItem.get().getItem().getState();
}
logger.debug("State for characteristic {} at accessory {} cannot be retrieved.", characteristic,
accessory.getName());
return null;
}
protected @Nullable <T extends State> T getStateAs(HomekitCharacteristicType characteristic, Class<T> type) {
final State state = getState(characteristic);
if (state != null) {
return state.as(type);
}
return null;
}
protected @Nullable Double getStateAsTemperature(HomekitCharacteristicType characteristic) {
return HomekitCharacteristicFactory.stateAsTemperature(getState(characteristic));
}
@NonNullByDefault
protected <T extends Item> Optional<T> getItem(HomekitCharacteristicType characteristic, Class<T> type) {
final Optional<HomekitTaggedItem> taggedItem = getCharacteristic(characteristic);

View File

@ -39,6 +39,7 @@ import org.openhab.core.library.types.HSBType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.OpenClosedType;
import org.openhab.core.library.types.PercentType;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.unit.ImperialUnits;
import org.openhab.core.library.unit.SIUnits;
import org.openhab.core.types.State;
@ -102,6 +103,7 @@ import io.github.hapjava.characteristics.impl.windowcovering.CurrentVerticalTilt
import io.github.hapjava.characteristics.impl.windowcovering.HoldPositionCharacteristic;
import io.github.hapjava.characteristics.impl.windowcovering.TargetHorizontalTiltAngleCharacteristic;
import io.github.hapjava.characteristics.impl.windowcovering.TargetVerticalTiltAngleCharacteristic;
import tech.units.indriya.unit.UnitDimension;
/**
* Creates a optional characteristics .
@ -259,6 +261,21 @@ public class HomekitCharacteristicFactory {
return new BigDecimal(rawValue).setScale(1, RoundingMode.HALF_UP).doubleValue();
}
public static @Nullable Double stateAsTemperature(@Nullable State state) {
if (state == null) {
return null;
}
if (state instanceof QuantityType<?>) {
final QuantityType<?> qt = (QuantityType<?>) state;
if (qt.getDimension().equals(UnitDimension.TEMPERATURE)) {
return qt.toUnit(SIUnits.CELSIUS).doubleValue();
}
}
return convertToCelsius(state.as(DecimalType.class).doubleValue());
}
public static double convertToCelsius(double degrees) {
return convertAndRound(degrees, useFahrenheit() ? ImperialUnits.FAHRENHEIT : SIUnits.CELSIUS, SIUnits.CELSIUS);
}
@ -336,9 +353,8 @@ public class HomekitCharacteristicFactory {
private static Supplier<CompletableFuture<Double>> getTemperatureSupplier(HomekitTaggedItem taggedItem,
double defaultValue) {
return () -> {
final @Nullable DecimalType value = taggedItem.getItem().getStateAs(DecimalType.class);
return CompletableFuture
.completedFuture(value != null ? convertToCelsius(value.doubleValue()) : defaultValue);
final @Nullable Double value = stateAsTemperature(taggedItem.getItem().getState());
return CompletableFuture.completedFuture(value != null ? value : defaultValue);
};
}

View File

@ -27,7 +27,6 @@ import java.util.concurrent.CompletableFuture;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.items.GenericItem;
import org.openhab.core.library.items.StringItem;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.OpenClosedType;
import org.openhab.core.library.types.StringType;
@ -106,10 +105,8 @@ public class HomekitHeaterCoolerImpl extends AbstractHomekitAccessoryImpl implem
@Override
public CompletableFuture<Double> getCurrentTemperature() {
final @Nullable DecimalType state = getStateAs(HomekitCharacteristicType.CURRENT_TEMPERATURE,
DecimalType.class);
return CompletableFuture.completedFuture(state != null
? HomekitCharacteristicFactory.convertToCelsius(state.doubleValue())
final @Nullable Double state = getStateAsTemperature(HomekitCharacteristicType.CURRENT_TEMPERATURE);
return CompletableFuture.completedFuture(state != null ? state
: getAccessoryConfiguration(HomekitCharacteristicType.CURRENT_TEMPERATURE, HomekitTaggedItem.MIN_VALUE,
BigDecimal.valueOf(HomekitCharacteristicFactory
.convertFromCelsius(CurrentTemperatureCharacteristic.DEFAULT_MIN_VALUE)))

View File

@ -17,7 +17,6 @@ import java.util.List;
import java.util.concurrent.CompletableFuture;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.library.types.DecimalType;
import org.openhab.io.homekit.internal.HomekitAccessoryUpdater;
import org.openhab.io.homekit.internal.HomekitCharacteristicType;
import org.openhab.io.homekit.internal.HomekitSettings;
@ -44,11 +43,8 @@ class HomekitTemperatureSensorImpl extends AbstractHomekitAccessoryImpl implemen
@Override
public CompletableFuture<Double> getCurrentTemperature() {
final @Nullable DecimalType state = getStateAs(HomekitCharacteristicType.CURRENT_TEMPERATURE,
DecimalType.class);
return CompletableFuture
.completedFuture(state != null ? HomekitCharacteristicFactory.convertToCelsius(state.doubleValue())
: getMinCurrentTemperature());
final @Nullable Double state = getStateAsTemperature(HomekitCharacteristicType.CURRENT_TEMPERATURE);
return CompletableFuture.completedFuture(state != null ? state : getMinCurrentTemperature());
}
@Override

View File

@ -106,10 +106,8 @@ class HomekitThermostatImpl extends AbstractHomekitAccessoryImpl implements Ther
@Override
public CompletableFuture<Double> getCurrentTemperature() {
DecimalType state = getStateAs(HomekitCharacteristicType.CURRENT_TEMPERATURE, DecimalType.class);
return CompletableFuture
.completedFuture(state != null ? HomekitCharacteristicFactory.convertToCelsius(state.doubleValue())
: getMinCurrentTemperature());
Double state = getStateAsTemperature(HomekitCharacteristicType.CURRENT_TEMPERATURE);
return CompletableFuture.completedFuture(state != null ? state : getMinCurrentTemperature());
}
@Override
@ -160,9 +158,8 @@ class HomekitThermostatImpl extends AbstractHomekitAccessoryImpl implements Ther
@Override
public CompletableFuture<Double> getTargetTemperature() {
DecimalType state = getStateAs(HomekitCharacteristicType.TARGET_TEMPERATURE, DecimalType.class);
return CompletableFuture.completedFuture(
state != null ? HomekitCharacteristicFactory.convertToCelsius(state.doubleValue()) : 0.0);
Double state = getStateAsTemperature(HomekitCharacteristicType.TARGET_TEMPERATURE);
return CompletableFuture.completedFuture(state != null ? state : 0.0);
}
@Override