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