2 * Copyright (c) 2010-2022 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.EnumMap;
21 import java.util.List;
23 import java.util.Optional;
24 import java.util.concurrent.CompletableFuture;
26 import org.openhab.core.library.items.NumberItem;
27 import org.openhab.core.library.items.StringItem;
28 import org.openhab.core.library.types.DecimalType;
29 import org.openhab.core.library.types.StringType;
30 import org.openhab.io.homekit.internal.HomekitAccessoryUpdater;
31 import org.openhab.io.homekit.internal.HomekitCharacteristicType;
32 import org.openhab.io.homekit.internal.HomekitSettings;
33 import org.openhab.io.homekit.internal.HomekitTaggedItem;
34 import org.slf4j.Logger;
35 import org.slf4j.LoggerFactory;
37 import io.github.hapjava.accessories.ThermostatAccessory;
38 import io.github.hapjava.characteristics.HomekitCharacteristicChangeCallback;
39 import io.github.hapjava.characteristics.impl.thermostat.CurrentHeatingCoolingStateEnum;
40 import io.github.hapjava.characteristics.impl.thermostat.CurrentTemperatureCharacteristic;
41 import io.github.hapjava.characteristics.impl.thermostat.TargetHeatingCoolingStateEnum;
42 import io.github.hapjava.characteristics.impl.thermostat.TargetTemperatureCharacteristic;
43 import io.github.hapjava.characteristics.impl.thermostat.TemperatureDisplayUnitEnum;
44 import io.github.hapjava.services.impl.ThermostatService;
47 * Implements Thermostat as a GroupedAccessory made up of multiple items:
49 * <li>Current Temperature: Number type</li>
50 * <li>Target Temperature: Number type</li>
51 * <li>Current Heating/Cooling Mode: String type (see HomekitSettings.thermostat*Mode)</li>
52 * <li>Target Heating/Cooling Mode: String type (see HomekitSettings.thermostat*Mode)</li>
55 * @author Andy Lintner - Initial contribution
57 class HomekitThermostatImpl extends AbstractHomekitAccessoryImpl implements ThermostatAccessory {
58 private final Logger logger = LoggerFactory.getLogger(HomekitThermostatImpl.class);
59 private final Map<CurrentHeatingCoolingStateEnum, String> currentHeatingCoolingStateMapping;
60 private final Map<TargetHeatingCoolingStateEnum, String> targetHeatingCoolingStateMapping;
61 private final List<CurrentHeatingCoolingStateEnum> customCurrentHeatingCoolingStateList;
62 private final List<TargetHeatingCoolingStateEnum> customTargetHeatingCoolingStateList;
64 public HomekitThermostatImpl(HomekitTaggedItem taggedItem, List<HomekitTaggedItem> mandatoryCharacteristics,
65 HomekitAccessoryUpdater updater, HomekitSettings settings) {
66 super(taggedItem, mandatoryCharacteristics, updater, settings);
67 currentHeatingCoolingStateMapping = new EnumMap<>(CurrentHeatingCoolingStateEnum.class);
68 currentHeatingCoolingStateMapping.put(CurrentHeatingCoolingStateEnum.OFF, settings.thermostatCurrentModeOff);
69 currentHeatingCoolingStateMapping.put(CurrentHeatingCoolingStateEnum.COOL,
70 settings.thermostatCurrentModeCooling);
71 currentHeatingCoolingStateMapping.put(CurrentHeatingCoolingStateEnum.HEAT,
72 settings.thermostatCurrentModeHeating);
73 targetHeatingCoolingStateMapping = new EnumMap<>(TargetHeatingCoolingStateEnum.class);
74 targetHeatingCoolingStateMapping.put(TargetHeatingCoolingStateEnum.OFF, settings.thermostatTargetModeOff);
75 targetHeatingCoolingStateMapping.put(TargetHeatingCoolingStateEnum.COOL, settings.thermostatTargetModeCool);
76 targetHeatingCoolingStateMapping.put(TargetHeatingCoolingStateEnum.HEAT, settings.thermostatTargetModeHeat);
77 targetHeatingCoolingStateMapping.put(TargetHeatingCoolingStateEnum.AUTO, settings.thermostatTargetModeAuto);
78 customCurrentHeatingCoolingStateList = new ArrayList<>();
79 customTargetHeatingCoolingStateList = new ArrayList<>();
80 updateMapping(CURRENT_HEATING_COOLING_STATE, currentHeatingCoolingStateMapping,
81 customCurrentHeatingCoolingStateList);
82 updateMapping(TARGET_HEATING_COOLING_STATE, targetHeatingCoolingStateMapping,
83 customTargetHeatingCoolingStateList);
84 this.getServices().add(new ThermostatService(this));
88 public CurrentHeatingCoolingStateEnum[] getCurrentHeatingCoolingStateValidValues() {
89 return customCurrentHeatingCoolingStateList.isEmpty()
90 ? currentHeatingCoolingStateMapping.keySet().toArray(new CurrentHeatingCoolingStateEnum[0])
91 : customCurrentHeatingCoolingStateList.toArray(new CurrentHeatingCoolingStateEnum[0]);
95 public TargetHeatingCoolingStateEnum[] getTargetHeatingCoolingStateValidValues() {
96 return customTargetHeatingCoolingStateList.isEmpty()
97 ? targetHeatingCoolingStateMapping.keySet().toArray(new TargetHeatingCoolingStateEnum[0])
98 : customTargetHeatingCoolingStateList.toArray(new TargetHeatingCoolingStateEnum[0]);
102 public CompletableFuture<CurrentHeatingCoolingStateEnum> getCurrentState() {
103 return CompletableFuture.completedFuture(getKeyFromMapping(CURRENT_HEATING_COOLING_STATE,
104 currentHeatingCoolingStateMapping, CurrentHeatingCoolingStateEnum.OFF));
108 public CompletableFuture<Double> getCurrentTemperature() {
109 DecimalType state = getStateAs(HomekitCharacteristicType.CURRENT_TEMPERATURE, DecimalType.class);
110 return CompletableFuture
111 .completedFuture(state != null ? HomekitCharacteristicFactory.convertToCelsius(state.doubleValue())
112 : getMinCurrentTemperature());
116 public double getMinCurrentTemperature() {
117 // Apple defines default values in Celsius. We need to convert them to Fahrenheit if openHAB is using Fahrenheit
118 // convertToCelsius and convertFromCelsius are only converting if useFahrenheit is set to true, so no additional
121 return HomekitCharacteristicFactory.convertToCelsius(
122 getAccessoryConfiguration(HomekitCharacteristicType.CURRENT_TEMPERATURE, HomekitTaggedItem.MIN_VALUE,
123 BigDecimal.valueOf(HomekitCharacteristicFactory
124 .convertFromCelsius(CurrentTemperatureCharacteristic.DEFAULT_MIN_VALUE)))
129 public double getMaxCurrentTemperature() {
130 return HomekitCharacteristicFactory.convertToCelsius(
131 getAccessoryConfiguration(HomekitCharacteristicType.CURRENT_TEMPERATURE, HomekitTaggedItem.MAX_VALUE,
132 BigDecimal.valueOf(HomekitCharacteristicFactory
133 .convertFromCelsius(CurrentTemperatureCharacteristic.DEFAULT_MAX_VALUE)))
138 public double getMinStepCurrentTemperature() {
139 return getAccessoryConfiguration(HomekitCharacteristicType.CURRENT_TEMPERATURE, HomekitTaggedItem.STEP,
140 BigDecimal.valueOf(TargetTemperatureCharacteristic.DEFAULT_STEP)).doubleValue();
144 public CompletableFuture<TargetHeatingCoolingStateEnum> getTargetState() {
145 return CompletableFuture.completedFuture(getKeyFromMapping(TARGET_HEATING_COOLING_STATE,
146 targetHeatingCoolingStateMapping, TargetHeatingCoolingStateEnum.OFF));
150 public CompletableFuture<TemperatureDisplayUnitEnum> getTemperatureDisplayUnit() {
151 return CompletableFuture
152 .completedFuture(HomekitCharacteristicFactory.useFahrenheit() ? TemperatureDisplayUnitEnum.FAHRENHEIT
153 : TemperatureDisplayUnitEnum.CELSIUS);
157 public void setTemperatureDisplayUnit(TemperatureDisplayUnitEnum value) {
158 // TODO: add support for display unit change
162 public CompletableFuture<Double> getTargetTemperature() {
163 DecimalType state = getStateAs(HomekitCharacteristicType.TARGET_TEMPERATURE, DecimalType.class);
164 return CompletableFuture.completedFuture(
165 state != null ? HomekitCharacteristicFactory.convertToCelsius(state.doubleValue()) : 0.0);
169 public void setTargetState(TargetHeatingCoolingStateEnum mode) {
170 getItem(TARGET_HEATING_COOLING_STATE, StringItem.class)
171 .ifPresent(item -> item.send(new StringType(targetHeatingCoolingStateMapping.get(mode))));
175 public void setTargetTemperature(Double value) {
176 final Optional<HomekitTaggedItem> characteristic = getCharacteristic(
177 HomekitCharacteristicType.TARGET_TEMPERATURE);
178 if (characteristic.isPresent()) {
179 ((NumberItem) characteristic.get().getItem())
180 .send(new DecimalType(BigDecimal.valueOf(HomekitCharacteristicFactory.convertFromCelsius(value))));
182 logger.warn("Missing mandatory characteristic {}", HomekitCharacteristicType.TARGET_TEMPERATURE);
187 public double getMinTargetTemperature() {
188 return HomekitCharacteristicFactory
190 getAccessoryConfiguration(HomekitCharacteristicType.TARGET_TEMPERATURE,
191 HomekitTaggedItem.MIN_VALUE,
192 BigDecimal.valueOf(HomekitCharacteristicFactory
193 .convertFromCelsius(TargetTemperatureCharacteristic.DEFAULT_MIN_VALUE)))
198 public double getMaxTargetTemperature() {
199 return HomekitCharacteristicFactory
201 getAccessoryConfiguration(HomekitCharacteristicType.TARGET_TEMPERATURE,
202 HomekitTaggedItem.MAX_VALUE,
203 BigDecimal.valueOf(HomekitCharacteristicFactory
204 .convertFromCelsius(TargetTemperatureCharacteristic.DEFAULT_MAX_VALUE)))
209 public double getMinStepTargetTemperature() {
210 return getAccessoryConfiguration(HomekitCharacteristicType.TARGET_TEMPERATURE, HomekitTaggedItem.STEP,
211 BigDecimal.valueOf(TargetTemperatureCharacteristic.DEFAULT_STEP)).doubleValue();
215 public void subscribeCurrentState(HomekitCharacteristicChangeCallback callback) {
216 subscribe(CURRENT_HEATING_COOLING_STATE, callback);
220 public void subscribeCurrentTemperature(HomekitCharacteristicChangeCallback callback) {
221 subscribe(HomekitCharacteristicType.CURRENT_TEMPERATURE, callback);
225 public void subscribeTargetState(HomekitCharacteristicChangeCallback callback) {
226 subscribe(HomekitCharacteristicType.TARGET_HEATING_COOLING_STATE, callback);
230 public void subscribeTargetTemperature(HomekitCharacteristicChangeCallback callback) {
231 subscribe(HomekitCharacteristicType.TARGET_TEMPERATURE, callback);
235 public void subscribeTemperatureDisplayUnit(HomekitCharacteristicChangeCallback callback) {
236 // TODO: add support for display unit change
240 public void unsubscribeCurrentState() {
241 unsubscribe(CURRENT_HEATING_COOLING_STATE);
245 public void unsubscribeCurrentTemperature() {
246 unsubscribe(HomekitCharacteristicType.CURRENT_TEMPERATURE);
250 public void unsubscribeTemperatureDisplayUnit() {
251 // TODO: add support for display unit change
255 public void unsubscribeTargetState() {
256 unsubscribe(HomekitCharacteristicType.TARGET_HEATING_COOLING_STATE);
260 public void unsubscribeTargetTemperature() {
261 unsubscribe(HomekitCharacteristicType.TARGET_TEMPERATURE);