]> git.basschouten.com Git - openhab-addons.git/blob
1c035f75da46671f2acd39cd3b591fcdb063ab19
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2022 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.binding.tado.internal.handler;
14
15 import static org.openhab.binding.tado.internal.api.TadoApiTypeUtils.terminationConditionTemplateToTerminationCondition;
16
17 import java.io.IOException;
18 import java.util.concurrent.ScheduledFuture;
19 import java.util.concurrent.TimeUnit;
20
21 import javax.measure.quantity.Temperature;
22
23 import org.eclipse.jdt.annotation.Nullable;
24 import org.openhab.binding.tado.internal.TadoBindingConstants;
25 import org.openhab.binding.tado.internal.TadoBindingConstants.OperationMode;
26 import org.openhab.binding.tado.internal.TadoBindingConstants.TemperatureUnit;
27 import org.openhab.binding.tado.internal.TadoBindingConstants.ZoneType;
28 import org.openhab.binding.tado.internal.TadoHvacChange;
29 import org.openhab.binding.tado.internal.adapter.TadoZoneStateAdapter;
30 import org.openhab.binding.tado.internal.api.ApiException;
31 import org.openhab.binding.tado.internal.api.client.HomeApi;
32 import org.openhab.binding.tado.internal.api.model.GenericZoneCapabilities;
33 import org.openhab.binding.tado.internal.api.model.Overlay;
34 import org.openhab.binding.tado.internal.api.model.OverlayTemplate;
35 import org.openhab.binding.tado.internal.api.model.OverlayTerminationCondition;
36 import org.openhab.binding.tado.internal.api.model.Zone;
37 import org.openhab.binding.tado.internal.api.model.ZoneState;
38 import org.openhab.binding.tado.internal.config.TadoZoneConfig;
39 import org.openhab.core.library.types.DecimalType;
40 import org.openhab.core.library.types.OnOffType;
41 import org.openhab.core.library.types.QuantityType;
42 import org.openhab.core.library.types.StringType;
43 import org.openhab.core.library.unit.ImperialUnits;
44 import org.openhab.core.library.unit.SIUnits;
45 import org.openhab.core.thing.Bridge;
46 import org.openhab.core.thing.ChannelUID;
47 import org.openhab.core.thing.Thing;
48 import org.openhab.core.thing.ThingStatus;
49 import org.openhab.core.thing.ThingStatusDetail;
50 import org.openhab.core.thing.ThingStatusInfo;
51 import org.openhab.core.types.Command;
52 import org.openhab.core.types.RefreshType;
53 import org.openhab.core.types.State;
54 import org.slf4j.Logger;
55 import org.slf4j.LoggerFactory;
56
57 /**
58  * The {@link TadoZoneHandler} is responsible for handling commands of zones and update their state.
59  *
60  * @author Dennis Frommknecht - Initial contribution
61  * @author Andrew Fiddian-Green - Added Low Battery Alarm, A/C Power and Open Window channels
62  *
63  */
64 public class TadoZoneHandler extends BaseHomeThingHandler {
65     private Logger logger = LoggerFactory.getLogger(TadoZoneHandler.class);
66
67     private TadoZoneConfig configuration;
68     private ScheduledFuture<?> refreshTimer;
69     private ScheduledFuture<?> scheduledHvacChange;
70     private GenericZoneCapabilities capabilities;
71     TadoHvacChange pendingHvacChange;
72
73     public TadoZoneHandler(Thing thing) {
74         super(thing);
75     }
76
77     public long getZoneId() {
78         return this.configuration.id;
79     }
80
81     public int getFallbackTimerDuration() {
82         return this.configuration.fallbackTimerDuration;
83     }
84
85     public @Nullable ZoneType getZoneType() {
86         String zoneTypeStr = this.thing.getProperties().get(TadoBindingConstants.PROPERTY_ZONE_TYPE);
87         return zoneTypeStr != null ? ZoneType.valueOf(zoneTypeStr) : null;
88     }
89
90     public OverlayTerminationCondition getDefaultTerminationCondition() throws IOException, ApiException {
91         OverlayTemplate overlayTemplate = getApi().showZoneDefaultOverlay(getHomeId(), getZoneId());
92         return terminationConditionTemplateToTerminationCondition(overlayTemplate.getTerminationCondition());
93     }
94
95     public ZoneState getZoneState() throws IOException, ApiException {
96         HomeApi api = getApi();
97         return api != null ? api.showZoneState(getHomeId(), getZoneId()) : null;
98     }
99
100     public GenericZoneCapabilities getZoneCapabilities() {
101         return this.capabilities;
102     }
103
104     public TemperatureUnit getTemperatureUnit() {
105         return getHomeHandler().getTemperatureUnit();
106     }
107
108     public Overlay setOverlay(Overlay overlay) throws IOException, ApiException {
109         logger.debug("Setting overlay of home {} and zone {}", getHomeId(), getZoneId());
110         return getApi().updateZoneOverlay(getHomeId(), getZoneId(), overlay);
111     }
112
113     public void removeOverlay() throws IOException, ApiException {
114         logger.debug("Removing overlay of home {} and zone {}", getHomeId(), getZoneId());
115         getApi().deleteZoneOverlay(getHomeId(), getZoneId());
116     }
117
118     @Override
119     public void handleCommand(ChannelUID channelUID, Command command) {
120         String id = channelUID.getId();
121
122         if (command == RefreshType.REFRESH) {
123             updateZoneState(false);
124             return;
125         }
126
127         switch (id) {
128             case TadoBindingConstants.CHANNEL_ZONE_HVAC_MODE:
129                 pendingHvacChange.withHvacMode(((StringType) command).toFullString());
130                 scheduleHvacChange();
131                 break;
132             case TadoBindingConstants.CHANNEL_ZONE_TARGET_TEMPERATURE:
133                 QuantityType<Temperature> state = (QuantityType<Temperature>) command;
134                 QuantityType<Temperature> stateInTargetUnit = getTemperatureUnit() == TemperatureUnit.FAHRENHEIT
135                         ? state.toUnit(ImperialUnits.FAHRENHEIT)
136                         : state.toUnit(SIUnits.CELSIUS);
137
138                 if (stateInTargetUnit != null) {
139                     pendingHvacChange.withTemperature(stateInTargetUnit.floatValue());
140                     scheduleHvacChange();
141                 }
142
143                 break;
144             case TadoBindingConstants.CHANNEL_ZONE_SWING:
145                 pendingHvacChange.withSwing(((OnOffType) command) == OnOffType.ON);
146                 scheduleHvacChange();
147                 break;
148             case TadoBindingConstants.CHANNEL_ZONE_FAN_SPEED:
149                 pendingHvacChange.withFanSpeed(((StringType) command).toFullString());
150                 scheduleHvacChange();
151                 break;
152             case TadoBindingConstants.CHANNEL_ZONE_OPERATION_MODE:
153                 String operationMode = ((StringType) command).toFullString();
154                 pendingHvacChange.withOperationMode(OperationMode.valueOf(operationMode));
155                 scheduleHvacChange();
156                 break;
157             case TadoBindingConstants.CHANNEL_ZONE_TIMER_DURATION:
158                 pendingHvacChange.activeFor(((DecimalType) command).intValue());
159                 scheduleHvacChange();
160                 break;
161         }
162     }
163
164     @Override
165     public void initialize() {
166         configuration = getConfigAs(TadoZoneConfig.class);
167
168         if (configuration.refreshInterval <= 0) {
169             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Refresh interval of zone "
170                     + getZoneId() + " of home " + getHomeId() + " must be greater than zero");
171             return;
172         } else if (configuration.fallbackTimerDuration <= 0) {
173             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Fallback timer duration of zone "
174                     + getZoneId() + " of home " + getHomeId() + " must be greater than zero");
175             return;
176         } else if (configuration.hvacChangeDebounce <= 0) {
177             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "HVAC change debounce of zone "
178                     + getZoneId() + " of home " + getHomeId() + " must be greater than zero");
179             return;
180         }
181
182         Bridge bridge = getBridge();
183         if (bridge != null) {
184             bridgeStatusChanged(bridge.getStatusInfo());
185         }
186     }
187
188     @Override
189     public void dispose() {
190         cancelScheduledZoneStateUpdate();
191     }
192
193     @Override
194     public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
195         if (bridgeStatusInfo.getStatus() == ThingStatus.ONLINE) {
196             try {
197                 Zone zoneDetails = getApi().showZoneDetails(getHomeId(), getZoneId());
198                 GenericZoneCapabilities capabilities = getApi().showZoneCapabilities(getHomeId(), getZoneId());
199
200                 if (zoneDetails == null || capabilities == null) {
201                     updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
202                             "Can not access zone " + getZoneId() + " of home " + getHomeId());
203                     return;
204                 }
205
206                 updateProperty(TadoBindingConstants.PROPERTY_ZONE_NAME, zoneDetails.getName());
207                 updateProperty(TadoBindingConstants.PROPERTY_ZONE_TYPE, zoneDetails.getType().name());
208                 this.capabilities = capabilities;
209             } catch (IOException | ApiException e) {
210                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
211                         "Could not connect to server due to " + e.getMessage());
212                 cancelScheduledZoneStateUpdate();
213                 return;
214             }
215
216             scheduleZoneStateUpdate();
217             pendingHvacChange = new TadoHvacChange(getThing());
218
219             updateStatus(ThingStatus.ONLINE);
220         } else {
221             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
222             cancelScheduledZoneStateUpdate();
223         }
224     }
225
226     private void updateZoneState(boolean forceUpdate) {
227         TadoHomeHandler home = getHomeHandler();
228         if (home != null) {
229             home.updateHomeState();
230         }
231
232         // No update during HVAC change debounce
233         if (!forceUpdate && scheduledHvacChange != null && !scheduledHvacChange.isDone()) {
234             return;
235         }
236
237         try {
238             ZoneState zoneState = getZoneState();
239
240             if (zoneState == null) {
241                 return;
242             }
243
244             logger.debug("Updating state of home {} and zone {}", getHomeId(), getZoneId());
245
246             TadoZoneStateAdapter state = new TadoZoneStateAdapter(zoneState, getTemperatureUnit());
247             updateStateIfNotNull(TadoBindingConstants.CHANNEL_ZONE_CURRENT_TEMPERATURE, state.getInsideTemperature());
248             updateStateIfNotNull(TadoBindingConstants.CHANNEL_ZONE_HUMIDITY, state.getHumidity());
249
250             updateStateIfNotNull(TadoBindingConstants.CHANNEL_ZONE_HEATING_POWER, state.getHeatingPower());
251             updateStateIfNotNull(TadoBindingConstants.CHANNEL_ZONE_AC_POWER, state.getAcPower());
252
253             updateState(TadoBindingConstants.CHANNEL_ZONE_OPERATION_MODE, state.getOperationMode());
254
255             updateState(TadoBindingConstants.CHANNEL_ZONE_HVAC_MODE, state.getMode());
256             updateState(TadoBindingConstants.CHANNEL_ZONE_TARGET_TEMPERATURE, state.getTargetTemperature());
257             updateState(TadoBindingConstants.CHANNEL_ZONE_FAN_SPEED, state.getFanSpeed());
258             updateState(TadoBindingConstants.CHANNEL_ZONE_SWING, state.getSwing());
259
260             updateState(TadoBindingConstants.CHANNEL_ZONE_TIMER_DURATION, state.getRemainingTimerDuration());
261
262             updateState(TadoBindingConstants.CHANNEL_ZONE_OVERLAY_EXPIRY, state.getOverlayExpiration());
263
264             updateState(TadoBindingConstants.CHANNEL_ZONE_OPEN_WINDOW_DETECTED, state.getOpenWindowDetected());
265
266             onSuccessfulOperation();
267         } catch (IOException | ApiException e) {
268             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
269                     "Could not connect to server due to " + e.getMessage());
270         }
271
272         if (home != null) {
273             updateState(TadoBindingConstants.CHANNEL_ZONE_BATTERY_LOW_ALARM, home.getBatteryLowAlarm(getZoneId()));
274         }
275     }
276
277     private void scheduleZoneStateUpdate() {
278         if (refreshTimer == null || refreshTimer.isCancelled()) {
279             refreshTimer = scheduler.scheduleWithFixedDelay(new Runnable() {
280                 @Override
281                 public void run() {
282                     updateZoneState(false);
283                 }
284             }, 5, configuration.refreshInterval, TimeUnit.SECONDS);
285         }
286     }
287
288     private void cancelScheduledZoneStateUpdate() {
289         if (refreshTimer != null) {
290             refreshTimer.cancel(false);
291         }
292     }
293
294     private void scheduleHvacChange() {
295         if (scheduledHvacChange != null) {
296             scheduledHvacChange.cancel(false);
297         }
298
299         scheduledHvacChange = scheduler.schedule(() -> {
300             try {
301                 TadoHvacChange change = this.pendingHvacChange;
302                 this.pendingHvacChange = new TadoHvacChange(getThing());
303                 change.apply();
304             } catch (IOException e) {
305                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
306             } catch (ApiException e) {
307                 logger.warn("Could not apply HVAC change on home {} and zone {}: {}", getHomeId(), getZoneId(),
308                         e.getMessage(), e);
309             } finally {
310                 updateZoneState(true);
311             }
312         }, configuration.hvacChangeDebounce, TimeUnit.SECONDS);
313     }
314
315     private void updateStateIfNotNull(String channelID, State state) {
316         if (state != null) {
317             updateState(channelID, state);
318         }
319     }
320 }