2 * Copyright (c) 2010-2020 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 java.math.BigDecimal;
16 import java.util.List;
17 import java.util.Optional;
18 import java.util.concurrent.CompletableFuture;
20 import org.openhab.core.library.items.NumberItem;
21 import org.openhab.core.library.items.StringItem;
22 import org.openhab.core.library.types.DecimalType;
23 import org.openhab.core.library.types.StringType;
24 import org.openhab.io.homekit.internal.HomekitAccessoryUpdater;
25 import org.openhab.io.homekit.internal.HomekitCharacteristicType;
26 import org.openhab.io.homekit.internal.HomekitSettings;
27 import org.openhab.io.homekit.internal.HomekitTaggedItem;
28 import org.slf4j.Logger;
29 import org.slf4j.LoggerFactory;
31 import io.github.hapjava.accessories.ThermostatAccessory;
32 import io.github.hapjava.characteristics.HomekitCharacteristicChangeCallback;
33 import io.github.hapjava.characteristics.impl.thermostat.CurrentHeatingCoolingStateEnum;
34 import io.github.hapjava.characteristics.impl.thermostat.TargetHeatingCoolingStateEnum;
35 import io.github.hapjava.characteristics.impl.thermostat.TargetTemperatureCharacteristic;
36 import io.github.hapjava.characteristics.impl.thermostat.TemperatureDisplayUnitEnum;
37 import io.github.hapjava.services.impl.ThermostatService;
40 * Implements Thermostat as a GroupedAccessory made up of multiple items:
42 * <li>Current Temperature: Number type</li>
43 * <li>Target Temperature: Number type</li>
44 * <li>Current Heating/Cooling Mode: String type (see HomekitSettings.thermostat*Mode)</li>
45 * <li>Target Heating/Cooling Mode: String type (see HomekitSettings.thermostat*Mode)</li>
48 * @author Andy Lintner - Initial contribution
50 class HomekitThermostatImpl extends AbstractHomekitAccessoryImpl implements ThermostatAccessory {
51 private final Logger logger = LoggerFactory.getLogger(HomekitThermostatImpl.class);
53 public HomekitThermostatImpl(HomekitTaggedItem taggedItem, List<HomekitTaggedItem> mandatoryCharacteristics,
54 HomekitAccessoryUpdater updater, HomekitSettings settings) {
55 super(taggedItem, mandatoryCharacteristics, updater, settings);
56 this.getServices().add(new ThermostatService(this));
60 public CompletableFuture<CurrentHeatingCoolingStateEnum> getCurrentState() {
61 final HomekitSettings settings = getSettings();
62 String stringValue = settings.thermostatCurrentModeOff;
63 final Optional<HomekitTaggedItem> characteristic = getCharacteristic(
64 HomekitCharacteristicType.CURRENT_HEATING_COOLING_STATE);
65 if (characteristic.isPresent()) {
66 stringValue = characteristic.get().getItem().getState().toString();
68 logger.warn("Missing mandatory characteristic {}", HomekitCharacteristicType.CURRENT_HEATING_COOLING_STATE);
71 CurrentHeatingCoolingStateEnum mode;
73 if (stringValue.equalsIgnoreCase(settings.thermostatCurrentModeCooling)) {
74 mode = CurrentHeatingCoolingStateEnum.COOL;
75 } else if (stringValue.equalsIgnoreCase(settings.thermostatCurrentModeHeating)) {
76 mode = CurrentHeatingCoolingStateEnum.HEAT;
77 } else if (stringValue.equalsIgnoreCase(settings.thermostatCurrentModeOff)) {
78 mode = CurrentHeatingCoolingStateEnum.OFF;
79 } else if (stringValue.equals("UNDEF") || stringValue.equals("NULL")) {
80 logger.warn("Heating cooling current mode not available. Relaying value of OFF to Homekit");
81 mode = CurrentHeatingCoolingStateEnum.OFF;
83 logger.warn("Unrecognized heatingCoolingCurrentMode: {}. Expected {}, {}, or {} strings in value.",
84 stringValue, settings.thermostatCurrentModeCooling, settings.thermostatCurrentModeHeating,
85 settings.thermostatCurrentModeOff);
86 mode = CurrentHeatingCoolingStateEnum.OFF;
88 return CompletableFuture.completedFuture(mode);
92 public CompletableFuture<Double> getCurrentTemperature() {
93 DecimalType state = getStateAs(HomekitCharacteristicType.CURRENT_TEMPERATURE, DecimalType.class);
94 return CompletableFuture.completedFuture(state != null ? convertToCelsius(state.doubleValue()) : 0.0);
98 public double getMinCurrentTemperature() {
99 return getAccessoryConfiguration(HomekitCharacteristicType.CURRENT_TEMPERATURE, HomekitTaggedItem.MIN_VALUE,
100 BigDecimal.valueOf(TargetTemperatureCharacteristic.DEFAULT_MIN_VALUE)).doubleValue();
104 public double getMaxCurrentTemperature() {
105 return getAccessoryConfiguration(HomekitCharacteristicType.CURRENT_TEMPERATURE, HomekitTaggedItem.MAX_VALUE,
106 BigDecimal.valueOf(TargetTemperatureCharacteristic.DEFAULT_MAX_VALUE)).doubleValue();
110 public double getMinStepCurrentTemperature() {
111 return getAccessoryConfiguration(HomekitCharacteristicType.CURRENT_TEMPERATURE, HomekitTaggedItem.STEP,
112 BigDecimal.valueOf(TargetTemperatureCharacteristic.DEFAULT_STEP)).doubleValue();
116 public CompletableFuture<TargetHeatingCoolingStateEnum> getTargetState() {
117 final HomekitSettings settings = getSettings();
118 String stringValue = settings.thermostatTargetModeOff;
120 final Optional<HomekitTaggedItem> characteristic = getCharacteristic(
121 HomekitCharacteristicType.TARGET_HEATING_COOLING_STATE);
122 if (characteristic.isPresent()) {
123 stringValue = characteristic.get().getItem().getState().toString();
125 logger.warn("Missing mandatory characteristic {}", HomekitCharacteristicType.TARGET_HEATING_COOLING_STATE);
127 TargetHeatingCoolingStateEnum mode;
129 if (stringValue.equalsIgnoreCase(settings.thermostatTargetModeCool)) {
130 mode = TargetHeatingCoolingStateEnum.COOL;
131 } else if (stringValue.equalsIgnoreCase(settings.thermostatTargetModeHeat)) {
132 mode = TargetHeatingCoolingStateEnum.HEAT;
133 } else if (stringValue.equalsIgnoreCase(settings.thermostatTargetModeAuto)) {
134 mode = TargetHeatingCoolingStateEnum.AUTO;
135 } else if (stringValue.equalsIgnoreCase(settings.thermostatTargetModeOff)) {
136 mode = TargetHeatingCoolingStateEnum.OFF;
137 } else if (stringValue.equals("UNDEF") || stringValue.equals("NULL")) {
138 logger.warn("Heating cooling target mode not available. Relaying value of OFF to Homekit");
139 mode = TargetHeatingCoolingStateEnum.OFF;
141 logger.warn("Unrecognized heating cooling target mode: {}. Expected {}, {}, {}, or {} strings in value.",
142 stringValue, settings.thermostatTargetModeCool, settings.thermostatTargetModeHeat,
143 settings.thermostatTargetModeAuto, settings.thermostatTargetModeOff);
144 mode = TargetHeatingCoolingStateEnum.OFF;
146 return CompletableFuture.completedFuture(mode);
150 public CompletableFuture<TemperatureDisplayUnitEnum> getTemperatureDisplayUnit() {
151 return CompletableFuture
152 .completedFuture(getSettings().useFahrenheitTemperature ? 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(state != null ? convertToCelsius(state.doubleValue()) : 0.0);
168 public void setTargetState(TargetHeatingCoolingStateEnum mode) {
169 final HomekitSettings settings = getSettings();
170 String modeString = null;
173 modeString = settings.thermostatTargetModeAuto;
177 modeString = settings.thermostatTargetModeCool;
181 modeString = settings.thermostatTargetModeHeat;
185 modeString = settings.thermostatTargetModeOff;
188 final Optional<HomekitTaggedItem> characteristic = getCharacteristic(
189 HomekitCharacteristicType.TARGET_HEATING_COOLING_STATE);
190 if (characteristic.isPresent()) {
191 ((StringItem) characteristic.get().getItem()).send(new StringType(modeString));
193 logger.warn("Missing mandatory characteristic {}", HomekitCharacteristicType.TARGET_HEATING_COOLING_STATE);
198 public void setTargetTemperature(Double value) {
199 final Optional<HomekitTaggedItem> characteristic = getCharacteristic(
200 HomekitCharacteristicType.TARGET_TEMPERATURE);
201 if (characteristic.isPresent()) {
202 ((NumberItem) characteristic.get().getItem())
203 .send(new DecimalType(BigDecimal.valueOf(convertFromCelsius(value))));
205 logger.warn("Missing mandatory characteristic {}", HomekitCharacteristicType.TARGET_TEMPERATURE);
210 public double getMinTargetTemperature() {
211 return getAccessoryConfiguration(HomekitCharacteristicType.TARGET_TEMPERATURE, HomekitTaggedItem.MIN_VALUE,
212 BigDecimal.valueOf(TargetTemperatureCharacteristic.DEFAULT_MIN_VALUE)).doubleValue();
216 public double getMaxTargetTemperature() {
217 return getAccessoryConfiguration(HomekitCharacteristicType.TARGET_TEMPERATURE, HomekitTaggedItem.MAX_VALUE,
218 BigDecimal.valueOf(TargetTemperatureCharacteristic.DEFAULT_MAX_VALUE)).doubleValue();
222 public double getMinStepTargetTemperature() {
223 return getAccessoryConfiguration(HomekitCharacteristicType.TARGET_TEMPERATURE, HomekitTaggedItem.STEP,
224 BigDecimal.valueOf(TargetTemperatureCharacteristic.DEFAULT_STEP)).doubleValue();
228 public void subscribeCurrentState(HomekitCharacteristicChangeCallback callback) {
229 subscribe(HomekitCharacteristicType.CURRENT_HEATING_COOLING_STATE, callback);
233 public void subscribeCurrentTemperature(HomekitCharacteristicChangeCallback callback) {
234 subscribe(HomekitCharacteristicType.CURRENT_TEMPERATURE, callback);
238 public void subscribeTargetState(HomekitCharacteristicChangeCallback callback) {
239 subscribe(HomekitCharacteristicType.TARGET_HEATING_COOLING_STATE, callback);
243 public void subscribeTargetTemperature(HomekitCharacteristicChangeCallback callback) {
244 subscribe(HomekitCharacteristicType.TARGET_TEMPERATURE, callback);
248 public void subscribeTemperatureDisplayUnit(HomekitCharacteristicChangeCallback callback) {
249 // TODO: add support for display unit change
253 public void unsubscribeCurrentState() {
254 unsubscribe(HomekitCharacteristicType.CURRENT_HEATING_COOLING_STATE);
258 public void unsubscribeCurrentTemperature() {
259 unsubscribe(HomekitCharacteristicType.CURRENT_TEMPERATURE);
263 public void unsubscribeTemperatureDisplayUnit() {
264 // TODO: add support for display unit change
268 public void unsubscribeTargetState() {
269 unsubscribe(HomekitCharacteristicType.TARGET_HEATING_COOLING_STATE);
273 public void unsubscribeTargetTemperature() {
274 unsubscribe(HomekitCharacteristicType.TARGET_TEMPERATURE);