]> git.basschouten.com Git - openhab-addons.git/blob
87db99569961fa1ee33defad751f01c86442335e
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2020 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 bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
190         if (bridgeStatusInfo.getStatus() == ThingStatus.ONLINE) {
191             try {
192                 Zone zoneDetails = getApi().showZoneDetails(getHomeId(), getZoneId());
193                 GenericZoneCapabilities capabilities = getApi().showZoneCapabilities(getHomeId(), getZoneId());
194
195                 if (zoneDetails == null || capabilities == null) {
196                     updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
197                             "Can not access zone " + getZoneId() + " of home " + getHomeId());
198                     return;
199                 }
200
201                 updateProperty(TadoBindingConstants.PROPERTY_ZONE_NAME, zoneDetails.getName());
202                 updateProperty(TadoBindingConstants.PROPERTY_ZONE_TYPE, zoneDetails.getType().name());
203                 this.capabilities = capabilities;
204             } catch (IOException | ApiException e) {
205                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
206                         "Could not connect to server due to " + e.getMessage());
207                 cancelScheduledZoneStateUpdate();
208                 return;
209             }
210
211             scheduleZoneStateUpdate();
212             pendingHvacChange = new TadoHvacChange(getThing());
213
214             updateStatus(ThingStatus.ONLINE);
215         } else {
216             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
217             cancelScheduledZoneStateUpdate();
218         }
219     }
220
221     private void updateZoneState(boolean forceUpdate) {
222         TadoHomeHandler home = getHomeHandler();
223         if (home != null) {
224             home.updateHomeState();
225         }
226
227         // No update during HVAC change debounce
228         if (!forceUpdate && scheduledHvacChange != null && !scheduledHvacChange.isDone()) {
229             return;
230         }
231
232         try {
233             ZoneState zoneState = getZoneState();
234
235             if (zoneState == null) {
236                 return;
237             }
238
239             logger.debug("Updating state of home {} and zone {}", getHomeId(), getZoneId());
240
241             TadoZoneStateAdapter state = new TadoZoneStateAdapter(zoneState, getTemperatureUnit());
242             updateStateIfNotNull(TadoBindingConstants.CHANNEL_ZONE_CURRENT_TEMPERATURE, state.getInsideTemperature());
243             updateStateIfNotNull(TadoBindingConstants.CHANNEL_ZONE_HUMIDITY, state.getHumidity());
244
245             updateStateIfNotNull(TadoBindingConstants.CHANNEL_ZONE_HEATING_POWER, state.getHeatingPower());
246             updateStateIfNotNull(TadoBindingConstants.CHANNEL_ZONE_AC_POWER, state.getAcPower());
247
248             updateState(TadoBindingConstants.CHANNEL_ZONE_OPERATION_MODE, state.getOperationMode());
249
250             updateState(TadoBindingConstants.CHANNEL_ZONE_HVAC_MODE, state.getMode());
251             updateState(TadoBindingConstants.CHANNEL_ZONE_TARGET_TEMPERATURE, state.getTargetTemperature());
252             updateState(TadoBindingConstants.CHANNEL_ZONE_FAN_SPEED, state.getFanSpeed());
253             updateState(TadoBindingConstants.CHANNEL_ZONE_SWING, state.getSwing());
254
255             updateState(TadoBindingConstants.CHANNEL_ZONE_TIMER_DURATION, state.getRemainingTimerDuration());
256
257             updateState(TadoBindingConstants.CHANNEL_ZONE_OVERLAY_EXPIRY, state.getOverlayExpiration());
258
259             updateState(TadoBindingConstants.CHANNEL_ZONE_OPEN_WINDOW_DETECTED, state.getOpenWindowDetected());
260
261             onSuccessfulOperation();
262         } catch (IOException | ApiException e) {
263             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
264                     "Could not connect to server due to " + e.getMessage());
265         }
266
267         if (home != null) {
268             updateState(TadoBindingConstants.CHANNEL_ZONE_BATTERY_LOW_ALARM, home.getBatteryLowAlarm(getZoneId()));
269         }
270     }
271
272     private void scheduleZoneStateUpdate() {
273         if (refreshTimer == null || refreshTimer.isCancelled()) {
274             refreshTimer = scheduler.scheduleWithFixedDelay(new Runnable() {
275                 @Override
276                 public void run() {
277                     updateZoneState(false);
278                 }
279             }, 5, configuration.refreshInterval, TimeUnit.SECONDS);
280         }
281     }
282
283     private void cancelScheduledZoneStateUpdate() {
284         if (refreshTimer != null) {
285             refreshTimer.cancel(false);
286         }
287     }
288
289     private void scheduleHvacChange() {
290         if (scheduledHvacChange != null) {
291             scheduledHvacChange.cancel(false);
292         }
293
294         scheduledHvacChange = scheduler.schedule(() -> {
295             try {
296                 TadoHvacChange change = this.pendingHvacChange;
297                 this.pendingHvacChange = new TadoHvacChange(getThing());
298                 change.apply();
299             } catch (IOException e) {
300                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
301             } catch (ApiException e) {
302                 logger.warn("Could not apply HVAC change on home {} and zone {}: {}", getHomeId(), getZoneId(),
303                         e.getMessage(), e);
304             } finally {
305                 updateZoneState(true);
306             }
307         }, configuration.hvacChangeDebounce, TimeUnit.SECONDS);
308     }
309
310     private void updateStateIfNotNull(String channelID, State state) {
311         if (state != null) {
312             updateState(channelID, state);
313         }
314     }
315 }