2 * Copyright (c) 2010-2024 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.io.homekit.internal.accessories;
15 import static org.openhab.io.homekit.internal.HomekitCharacteristicType.CURRENT_HEATING_COOLING_STATE;
16 import static org.openhab.io.homekit.internal.HomekitCharacteristicType.TARGET_HEATING_COOLING_STATE;
18 import java.math.BigDecimal;
19 import java.util.ArrayList;
20 import java.util.List;
22 import java.util.Optional;
23 import java.util.concurrent.CompletableFuture;
25 import org.openhab.core.library.items.NumberItem;
26 import org.openhab.core.library.types.DecimalType;
27 import org.openhab.io.homekit.internal.HomekitAccessoryUpdater;
28 import org.openhab.io.homekit.internal.HomekitCharacteristicType;
29 import org.openhab.io.homekit.internal.HomekitException;
30 import org.openhab.io.homekit.internal.HomekitSettings;
31 import org.openhab.io.homekit.internal.HomekitTaggedItem;
32 import org.slf4j.Logger;
33 import org.slf4j.LoggerFactory;
35 import io.github.hapjava.accessories.ThermostatAccessory;
36 import io.github.hapjava.characteristics.HomekitCharacteristicChangeCallback;
37 import io.github.hapjava.characteristics.impl.thermostat.CurrentHeatingCoolingStateEnum;
38 import io.github.hapjava.characteristics.impl.thermostat.CurrentTemperatureCharacteristic;
39 import io.github.hapjava.characteristics.impl.thermostat.TargetHeatingCoolingStateEnum;
40 import io.github.hapjava.characteristics.impl.thermostat.TargetTemperatureCharacteristic;
41 import io.github.hapjava.characteristics.impl.thermostat.TemperatureDisplayUnitEnum;
42 import io.github.hapjava.services.impl.ThermostatService;
45 * Implements Thermostat as a GroupedAccessory made up of multiple items:
47 * <li>Current Temperature: Number type</li>
48 * <li>Target Temperature: Number type</li>
49 * <li>Current Heating/Cooling Mode: String type (see HomekitSettings.thermostat*Mode)</li>
50 * <li>Target Heating/Cooling Mode: String type (see HomekitSettings.thermostat*Mode)</li>
53 * @author Andy Lintner - Initial contribution
55 class HomekitThermostatImpl extends AbstractHomekitAccessoryImpl implements ThermostatAccessory {
56 private final Logger logger = LoggerFactory.getLogger(HomekitThermostatImpl.class);
57 private final Map<CurrentHeatingCoolingStateEnum, String> currentHeatingCoolingStateMapping;
58 private final Map<TargetHeatingCoolingStateEnum, String> targetHeatingCoolingStateMapping;
59 private final List<CurrentHeatingCoolingStateEnum> customCurrentHeatingCoolingStateList;
60 private final List<TargetHeatingCoolingStateEnum> customTargetHeatingCoolingStateList;
62 public HomekitThermostatImpl(HomekitTaggedItem taggedItem, List<HomekitTaggedItem> mandatoryCharacteristics,
63 HomekitAccessoryUpdater updater, HomekitSettings settings) {
64 super(taggedItem, mandatoryCharacteristics, updater, settings);
65 customCurrentHeatingCoolingStateList = new ArrayList<>();
66 customTargetHeatingCoolingStateList = new ArrayList<>();
67 currentHeatingCoolingStateMapping = createMapping(CURRENT_HEATING_COOLING_STATE,
68 CurrentHeatingCoolingStateEnum.class, customCurrentHeatingCoolingStateList);
69 targetHeatingCoolingStateMapping = createMapping(TARGET_HEATING_COOLING_STATE,
70 TargetHeatingCoolingStateEnum.class, customTargetHeatingCoolingStateList);
74 public void init() throws HomekitException {
76 this.getServices().add(new ThermostatService(this));
80 public CurrentHeatingCoolingStateEnum[] getCurrentHeatingCoolingStateValidValues() {
81 return customCurrentHeatingCoolingStateList.isEmpty()
82 ? currentHeatingCoolingStateMapping.keySet().toArray(new CurrentHeatingCoolingStateEnum[0])
83 : customCurrentHeatingCoolingStateList.toArray(new CurrentHeatingCoolingStateEnum[0]);
87 public TargetHeatingCoolingStateEnum[] getTargetHeatingCoolingStateValidValues() {
88 return customTargetHeatingCoolingStateList.isEmpty()
89 ? targetHeatingCoolingStateMapping.keySet().toArray(new TargetHeatingCoolingStateEnum[0])
90 : customTargetHeatingCoolingStateList.toArray(new TargetHeatingCoolingStateEnum[0]);
94 public CompletableFuture<CurrentHeatingCoolingStateEnum> getCurrentState() {
95 return CompletableFuture.completedFuture(getKeyFromMapping(CURRENT_HEATING_COOLING_STATE,
96 currentHeatingCoolingStateMapping, CurrentHeatingCoolingStateEnum.OFF));
100 public CompletableFuture<Double> getCurrentTemperature() {
101 Double state = getStateAsTemperature(HomekitCharacteristicType.CURRENT_TEMPERATURE);
102 return CompletableFuture.completedFuture(state != null ? state : getMinCurrentTemperature());
106 public double getMinCurrentTemperature() {
107 // Apple defines default values in Celsius. We need to convert them to Fahrenheit if openHAB is using Fahrenheit
108 // convertToCelsius and convertFromCelsius are only converting if useFahrenheit is set to true, so no additional
111 return HomekitCharacteristicFactory.convertToCelsius(
112 getAccessoryConfiguration(HomekitCharacteristicType.CURRENT_TEMPERATURE, HomekitTaggedItem.MIN_VALUE,
113 BigDecimal.valueOf(HomekitCharacteristicFactory
114 .convertFromCelsius(CurrentTemperatureCharacteristic.DEFAULT_MIN_VALUE)))
119 public double getMaxCurrentTemperature() {
120 return HomekitCharacteristicFactory.convertToCelsius(
121 getAccessoryConfiguration(HomekitCharacteristicType.CURRENT_TEMPERATURE, HomekitTaggedItem.MAX_VALUE,
122 BigDecimal.valueOf(HomekitCharacteristicFactory
123 .convertFromCelsius(CurrentTemperatureCharacteristic.DEFAULT_MAX_VALUE)))
128 public double getMinStepCurrentTemperature() {
129 return HomekitCharacteristicFactory.getTemperatureStep(
130 getCharacteristic(HomekitCharacteristicType.CURRENT_TEMPERATURE).get(),
131 TargetTemperatureCharacteristic.DEFAULT_STEP);
135 public CompletableFuture<TargetHeatingCoolingStateEnum> getTargetState() {
136 return CompletableFuture.completedFuture(getKeyFromMapping(TARGET_HEATING_COOLING_STATE,
137 targetHeatingCoolingStateMapping, TargetHeatingCoolingStateEnum.OFF));
141 public CompletableFuture<TemperatureDisplayUnitEnum> getTemperatureDisplayUnit() {
142 return CompletableFuture
143 .completedFuture(HomekitCharacteristicFactory.useFahrenheit() ? TemperatureDisplayUnitEnum.FAHRENHEIT
144 : TemperatureDisplayUnitEnum.CELSIUS);
148 public void setTemperatureDisplayUnit(TemperatureDisplayUnitEnum value) {
149 // TODO: add support for display unit change
153 public CompletableFuture<Double> getTargetTemperature() {
154 Double state = getStateAsTemperature(HomekitCharacteristicType.TARGET_TEMPERATURE);
155 return CompletableFuture.completedFuture(state != null ? state : 0.0);
159 public void setTargetState(TargetHeatingCoolingStateEnum mode) {
160 HomekitCharacteristicFactory.setValueFromEnum(getCharacteristic(TARGET_HEATING_COOLING_STATE).get(), mode,
161 targetHeatingCoolingStateMapping);
165 public void setTargetTemperature(Double value) {
166 final Optional<HomekitTaggedItem> characteristic = getCharacteristic(
167 HomekitCharacteristicType.TARGET_TEMPERATURE);
168 if (characteristic.isPresent()) {
169 ((NumberItem) characteristic.get().getItem())
170 .send(new DecimalType(BigDecimal.valueOf(HomekitCharacteristicFactory.convertFromCelsius(value))));
172 logger.warn("Missing mandatory characteristic {}", HomekitCharacteristicType.TARGET_TEMPERATURE);
177 public double getMinTargetTemperature() {
178 return HomekitCharacteristicFactory
180 getAccessoryConfiguration(HomekitCharacteristicType.TARGET_TEMPERATURE,
181 HomekitTaggedItem.MIN_VALUE,
182 BigDecimal.valueOf(HomekitCharacteristicFactory
183 .convertFromCelsius(TargetTemperatureCharacteristic.DEFAULT_MIN_VALUE)))
188 public double getMaxTargetTemperature() {
189 return HomekitCharacteristicFactory
191 getAccessoryConfiguration(HomekitCharacteristicType.TARGET_TEMPERATURE,
192 HomekitTaggedItem.MAX_VALUE,
193 BigDecimal.valueOf(HomekitCharacteristicFactory
194 .convertFromCelsius(TargetTemperatureCharacteristic.DEFAULT_MAX_VALUE)))
199 public double getMinStepTargetTemperature() {
200 return HomekitCharacteristicFactory.getTemperatureStep(
201 getCharacteristic(HomekitCharacteristicType.TARGET_TEMPERATURE).get(),
202 TargetTemperatureCharacteristic.DEFAULT_STEP);
206 public void subscribeCurrentState(HomekitCharacteristicChangeCallback callback) {
207 subscribe(CURRENT_HEATING_COOLING_STATE, callback);
211 public void subscribeCurrentTemperature(HomekitCharacteristicChangeCallback callback) {
212 subscribe(HomekitCharacteristicType.CURRENT_TEMPERATURE, callback);
216 public void subscribeTargetState(HomekitCharacteristicChangeCallback callback) {
217 subscribe(HomekitCharacteristicType.TARGET_HEATING_COOLING_STATE, callback);
221 public void subscribeTargetTemperature(HomekitCharacteristicChangeCallback callback) {
222 subscribe(HomekitCharacteristicType.TARGET_TEMPERATURE, callback);
226 public void subscribeTemperatureDisplayUnit(HomekitCharacteristicChangeCallback callback) {
227 // TODO: add support for display unit change
231 public void unsubscribeCurrentState() {
232 unsubscribe(CURRENT_HEATING_COOLING_STATE);
236 public void unsubscribeCurrentTemperature() {
237 unsubscribe(HomekitCharacteristicType.CURRENT_TEMPERATURE);
241 public void unsubscribeTemperatureDisplayUnit() {
242 // TODO: add support for display unit change
246 public void unsubscribeTargetState() {
247 unsubscribe(HomekitCharacteristicType.TARGET_HEATING_COOLING_STATE);
251 public void unsubscribeTargetTemperature() {
252 unsubscribe(HomekitCharacteristicType.TARGET_TEMPERATURE);