]> git.basschouten.com Git - openhab-addons.git/blob
fbbe9401c4258df1ce1e8278888ad6f09edb58f4
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2024 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
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
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.io.homekit.internal.accessories;
14
15 import static org.openhab.io.homekit.internal.HomekitCharacteristicType.*;
16
17 import java.util.Arrays;
18 import java.util.List;
19 import java.util.Optional;
20 import java.util.concurrent.CompletableFuture;
21 import java.util.concurrent.ExecutionException;
22
23 import org.eclipse.jdt.annotation.NonNullByDefault;
24 import org.eclipse.jdt.annotation.Nullable;
25 import org.openhab.core.items.GenericItem;
26 import org.openhab.io.homekit.internal.HomekitAccessoryUpdater;
27 import org.openhab.io.homekit.internal.HomekitException;
28 import org.openhab.io.homekit.internal.HomekitSettings;
29 import org.openhab.io.homekit.internal.HomekitTaggedItem;
30 import org.slf4j.Logger;
31 import org.slf4j.LoggerFactory;
32
33 import io.github.hapjava.characteristics.Characteristic;
34 import io.github.hapjava.characteristics.HomekitCharacteristicChangeCallback;
35 import io.github.hapjava.characteristics.impl.thermostat.CoolingThresholdTemperatureCharacteristic;
36 import io.github.hapjava.characteristics.impl.thermostat.CurrentHeatingCoolingStateCharacteristic;
37 import io.github.hapjava.characteristics.impl.thermostat.CurrentTemperatureCharacteristic;
38 import io.github.hapjava.characteristics.impl.thermostat.HeatingThresholdTemperatureCharacteristic;
39 import io.github.hapjava.characteristics.impl.thermostat.TargetHeatingCoolingStateCharacteristic;
40 import io.github.hapjava.characteristics.impl.thermostat.TargetHeatingCoolingStateEnum;
41 import io.github.hapjava.characteristics.impl.thermostat.TargetTemperatureCharacteristic;
42 import io.github.hapjava.characteristics.impl.thermostat.TemperatureDisplayUnitCharacteristic;
43 import io.github.hapjava.services.impl.ThermostatService;
44
45 /**
46  * Implements Thermostat as a GroupedAccessory made up of multiple items:
47  * <ul>
48  * <li>Current Temperature: Number type</li>
49  * <li>Target Temperature: Number type</li>
50  * <li>Current Heating/Cooling Mode: String type (see HomekitSettings.thermostat*Mode)</li>
51  * <li>Target Heating/Cooling Mode: String type (see HomekitSettings.thermostat*Mode)</li>
52  * </ul>
53  *
54  * @author Andy Lintner - Initial contribution
55  */
56 @NonNullByDefault
57 class HomekitThermostatImpl extends AbstractHomekitAccessoryImpl {
58     private final Logger logger = LoggerFactory.getLogger(HomekitThermostatImpl.class);
59     private @Nullable HomekitCharacteristicChangeCallback targetTemperatureCallback = null;
60
61     public HomekitThermostatImpl(HomekitTaggedItem taggedItem, List<HomekitTaggedItem> mandatoryCharacteristics,
62             List<Characteristic> mandatoryRawCharacteristics, HomekitAccessoryUpdater updater,
63             HomekitSettings settings) {
64         super(taggedItem, mandatoryCharacteristics, mandatoryRawCharacteristics, updater, settings);
65     }
66
67     @Override
68     public void init() throws HomekitException {
69         super.init();
70
71         var coolingThresholdTemperatureCharacteristic = getCharacteristic(
72                 CoolingThresholdTemperatureCharacteristic.class);
73         var heatingThresholdTemperatureCharacteristic = getCharacteristic(
74                 HeatingThresholdTemperatureCharacteristic.class);
75         var targetTemperatureCharacteristic = getCharacteristic(TargetTemperatureCharacteristic.class);
76
77         if (!coolingThresholdTemperatureCharacteristic.isPresent()
78                 && !heatingThresholdTemperatureCharacteristic.isPresent()
79                 && !targetTemperatureCharacteristic.isPresent()) {
80             throw new HomekitException(
81                     "Unable to create thermostat; at least one of TargetTemperature, CoolingThresholdTemperature, or HeatingThresholdTemperature is required.");
82         }
83
84         var targetHeatingCoolingStateCharacteristic = getCharacteristic(TargetHeatingCoolingStateCharacteristic.class)
85                 .get();
86
87         // TargetTemperature not provided; simulate by forwarding to HeatingThresholdTemperature and
88         // CoolingThresholdTemperature
89         // as appropriate
90         if (!targetTemperatureCharacteristic.isPresent()) {
91             if (Arrays.stream(targetHeatingCoolingStateCharacteristic.getValidValues())
92                     .anyMatch(v -> v.equals(TargetHeatingCoolingStateEnum.HEAT))
93                     && !heatingThresholdTemperatureCharacteristic.isPresent()) {
94                 throw new HomekitException(
95                         "HeatingThresholdTemperature must be provided if HEAT mode is allowed and TargetTemperature is not provided.");
96             }
97             if (Arrays.stream(targetHeatingCoolingStateCharacteristic.getValidValues())
98                     .anyMatch(v -> v.equals(TargetHeatingCoolingStateEnum.COOL))
99                     && !coolingThresholdTemperatureCharacteristic.isPresent()) {
100                 throw new HomekitException(
101                         "CoolingThresholdTemperature must be provided if COOL mode is allowed and TargetTemperature is not provided.");
102             }
103
104             double minValue, maxValue, minStep;
105             if (coolingThresholdTemperatureCharacteristic.isPresent()
106                     && heatingThresholdTemperatureCharacteristic.isPresent()) {
107                 minValue = Math.min(coolingThresholdTemperatureCharacteristic.get().getMinValue(),
108                         heatingThresholdTemperatureCharacteristic.get().getMinValue());
109                 maxValue = Math.max(coolingThresholdTemperatureCharacteristic.get().getMaxValue(),
110                         heatingThresholdTemperatureCharacteristic.get().getMaxValue());
111                 minStep = Math.min(coolingThresholdTemperatureCharacteristic.get().getMinStep(),
112                         heatingThresholdTemperatureCharacteristic.get().getMinStep());
113             } else if (coolingThresholdTemperatureCharacteristic.isPresent()) {
114                 minValue = coolingThresholdTemperatureCharacteristic.get().getMinValue();
115                 maxValue = coolingThresholdTemperatureCharacteristic.get().getMaxValue();
116                 minStep = coolingThresholdTemperatureCharacteristic.get().getMinStep();
117             } else {
118                 minValue = heatingThresholdTemperatureCharacteristic.get().getMinValue();
119                 maxValue = heatingThresholdTemperatureCharacteristic.get().getMaxValue();
120                 minStep = heatingThresholdTemperatureCharacteristic.get().getMinStep();
121             }
122             targetTemperatureCharacteristic = Optional
123                     .of(new TargetTemperatureCharacteristic(minValue, maxValue, minStep, () -> {
124                         // return the value from the characteristic corresponding to the current mode
125                         try {
126                             switch (targetHeatingCoolingStateCharacteristic.getEnumValue().get()) {
127                                 case HEAT:
128                                     return heatingThresholdTemperatureCharacteristic.get().getValue();
129                                 case COOL:
130                                     return coolingThresholdTemperatureCharacteristic.get().getValue();
131                                 default:
132                                     return CompletableFuture.completedFuture(
133                                             (heatingThresholdTemperatureCharacteristic.get().getValue().get()
134                                                     + coolingThresholdTemperatureCharacteristic.get().getValue().get())
135                                                     / 2);
136                             }
137                         } catch (InterruptedException | ExecutionException e) {
138                             return null;
139                         }
140                     }, value -> {
141                         try {
142                             // set the charactestic corresponding to the current mode
143                             switch (targetHeatingCoolingStateCharacteristic.getEnumValue().get()) {
144                                 case HEAT:
145                                     heatingThresholdTemperatureCharacteristic.get().setValue(value);
146                                     break;
147                                 case COOL:
148                                     coolingThresholdTemperatureCharacteristic.get().setValue(value);
149                                     break;
150                                 default:
151                                     // ignore
152                             }
153                         } catch (InterruptedException | ExecutionException e) {
154                             // can't happen, since the futures are synchronous
155                         }
156                     }, cb -> {
157                         targetTemperatureCallback = cb;
158                         if (heatingThresholdTemperatureCharacteristic.isPresent()) {
159                             getUpdater().subscribe(
160                                     (GenericItem) getCharacteristic(HEATING_THRESHOLD_TEMPERATURE).get().getItem(),
161                                     TARGET_TEMPERATURE.getTag(), this::thresholdTemperatureChanged);
162                         }
163                         if (coolingThresholdTemperatureCharacteristic.isPresent()) {
164                             getUpdater().subscribe(
165                                     (GenericItem) getCharacteristic(COOLING_THRESHOLD_TEMPERATURE).get().getItem(),
166                                     TARGET_TEMPERATURE.getTag(), this::thresholdTemperatureChanged);
167                         }
168                         getUpdater().subscribe(
169                                 (GenericItem) getCharacteristic(TARGET_HEATING_COOLING_STATE).get().getItem(),
170                                 TARGET_TEMPERATURE.getTag(), this::thresholdTemperatureChanged);
171                     }, () -> {
172                         if (heatingThresholdTemperatureCharacteristic.isPresent()) {
173                             getUpdater().unsubscribe(
174                                     (GenericItem) getCharacteristic(HEATING_THRESHOLD_TEMPERATURE).get().getItem(),
175                                     TARGET_TEMPERATURE.getTag());
176                         }
177                         if (coolingThresholdTemperatureCharacteristic.isPresent()) {
178                             getUpdater().unsubscribe(
179                                     (GenericItem) getCharacteristic(COOLING_THRESHOLD_TEMPERATURE).get().getItem(),
180                                     TARGET_TEMPERATURE.getTag());
181                         }
182                         getUpdater().unsubscribe(
183                                 (GenericItem) getCharacteristic(TARGET_HEATING_COOLING_STATE).get().getItem(),
184                                 TARGET_TEMPERATURE.getTag());
185                         targetTemperatureCallback = null;
186                     }));
187         }
188
189         // This characteristic is technically mandatory, but we provide a default if it's not provided
190         var displayUnitCharacteristic = getCharacteristic(TemperatureDisplayUnitCharacteristic.class)
191                 .orElseGet(() -> HomekitCharacteristicFactory.createSystemTemperatureDisplayUnitCharacteristic());
192
193         addService(new ThermostatService(getCharacteristic(CurrentHeatingCoolingStateCharacteristic.class).get(),
194                 targetHeatingCoolingStateCharacteristic,
195                 getCharacteristic(CurrentTemperatureCharacteristic.class).get(), targetTemperatureCharacteristic.get(),
196                 displayUnitCharacteristic));
197     }
198
199     private void thresholdTemperatureChanged() {
200         targetTemperatureCallback.changed();
201     }
202 }