]> git.basschouten.com Git - openhab-addons.git/blob
37078ed17a75ebdcc5abc210481b85afeabcb65e
[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.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.ArrayList;
19 import java.util.List;
20 import java.util.StringJoiner;
21 import java.util.concurrent.ScheduledFuture;
22 import java.util.concurrent.TimeUnit;
23 import java.util.stream.Collectors;
24
25 import javax.measure.quantity.Temperature;
26
27 import org.eclipse.jdt.annotation.NonNullByDefault;
28 import org.eclipse.jdt.annotation.Nullable;
29 import org.openhab.binding.tado.internal.CapabilitiesSupport;
30 import org.openhab.binding.tado.internal.TadoBindingConstants;
31 import org.openhab.binding.tado.internal.TadoBindingConstants.FanLevel;
32 import org.openhab.binding.tado.internal.TadoBindingConstants.HorizontalSwing;
33 import org.openhab.binding.tado.internal.TadoBindingConstants.OperationMode;
34 import org.openhab.binding.tado.internal.TadoBindingConstants.TemperatureUnit;
35 import org.openhab.binding.tado.internal.TadoBindingConstants.VerticalSwing;
36 import org.openhab.binding.tado.internal.TadoBindingConstants.ZoneType;
37 import org.openhab.binding.tado.internal.TadoHvacChange;
38 import org.openhab.binding.tado.internal.adapter.TadoZoneStateAdapter;
39 import org.openhab.binding.tado.internal.api.TadoApiTypeUtils;
40 import org.openhab.binding.tado.internal.config.TadoZoneConfig;
41 import org.openhab.binding.tado.swagger.codegen.api.ApiException;
42 import org.openhab.binding.tado.swagger.codegen.api.GsonBuilderFactory;
43 import org.openhab.binding.tado.swagger.codegen.api.model.ACFanLevel;
44 import org.openhab.binding.tado.swagger.codegen.api.model.ACHorizontalSwing;
45 import org.openhab.binding.tado.swagger.codegen.api.model.ACVerticalSwing;
46 import org.openhab.binding.tado.swagger.codegen.api.model.AcMode;
47 import org.openhab.binding.tado.swagger.codegen.api.model.AcModeCapabilities;
48 import org.openhab.binding.tado.swagger.codegen.api.model.CoolingZoneSetting;
49 import org.openhab.binding.tado.swagger.codegen.api.model.GenericZoneCapabilities;
50 import org.openhab.binding.tado.swagger.codegen.api.model.GenericZoneSetting;
51 import org.openhab.binding.tado.swagger.codegen.api.model.Overlay;
52 import org.openhab.binding.tado.swagger.codegen.api.model.OverlayTemplate;
53 import org.openhab.binding.tado.swagger.codegen.api.model.OverlayTerminationCondition;
54 import org.openhab.binding.tado.swagger.codegen.api.model.TadoSystemType;
55 import org.openhab.binding.tado.swagger.codegen.api.model.Zone;
56 import org.openhab.binding.tado.swagger.codegen.api.model.ZoneState;
57 import org.openhab.core.library.types.DecimalType;
58 import org.openhab.core.library.types.OnOffType;
59 import org.openhab.core.library.types.QuantityType;
60 import org.openhab.core.library.types.StringType;
61 import org.openhab.core.library.unit.ImperialUnits;
62 import org.openhab.core.library.unit.SIUnits;
63 import org.openhab.core.thing.Bridge;
64 import org.openhab.core.thing.Channel;
65 import org.openhab.core.thing.ChannelUID;
66 import org.openhab.core.thing.Thing;
67 import org.openhab.core.thing.ThingStatus;
68 import org.openhab.core.thing.ThingStatusDetail;
69 import org.openhab.core.thing.ThingStatusInfo;
70 import org.openhab.core.types.Command;
71 import org.openhab.core.types.RefreshType;
72 import org.openhab.core.types.StateOption;
73 import org.slf4j.Logger;
74 import org.slf4j.LoggerFactory;
75
76 import com.google.gson.Gson;
77
78 /**
79  * The {@link TadoZoneHandler} is responsible for handling commands of zones and update their state.
80  *
81  * @author Dennis Frommknecht - Initial contribution
82  * @author Andrew Fiddian-Green - Added Low Battery Alarm, A/C Power and Open Window channels
83  *
84  */
85 @NonNullByDefault
86 public class TadoZoneHandler extends BaseHomeThingHandler {
87     private Logger logger = LoggerFactory.getLogger(TadoZoneHandler.class);
88
89     private final TadoStateDescriptionProvider stateDescriptionProvider;
90     private TadoZoneConfig configuration;
91
92     private @Nullable ScheduledFuture<?> refreshTimer;
93     private @Nullable ScheduledFuture<?> scheduledHvacChange;
94     private @Nullable GenericZoneCapabilities capabilities;
95     private @Nullable TadoHvacChange pendingHvacChange;
96
97     private boolean disposing = false;
98     private @Nullable Gson gson;
99
100     public TadoZoneHandler(Thing thing, TadoStateDescriptionProvider stateDescriptionProvider) {
101         super(thing);
102         this.stateDescriptionProvider = stateDescriptionProvider;
103         configuration = getConfigAs(TadoZoneConfig.class);
104     }
105
106     public long getZoneId() {
107         return configuration.id;
108     }
109
110     public int getFallbackTimerDuration() {
111         return configuration.fallbackTimerDuration;
112     }
113
114     public ZoneType getZoneType() {
115         String zoneTypeStr = thing.getProperties().get(TadoBindingConstants.PROPERTY_ZONE_TYPE);
116         if (zoneTypeStr == null) {
117             throw new IllegalStateException("Zone type not initialized");
118         }
119         return ZoneType.valueOf(zoneTypeStr);
120     }
121
122     public OverlayTerminationCondition getDefaultTerminationCondition() throws IOException, ApiException {
123         OverlayTemplate overlayTemplate = getApi().showZoneDefaultOverlay(getHomeId(), getZoneId());
124         logApiTransaction(overlayTemplate, false);
125         return terminationConditionTemplateToTerminationCondition(overlayTemplate.getTerminationCondition());
126     }
127
128     public ZoneState getZoneState() throws IOException, ApiException {
129         ZoneState zoneState = getApi().showZoneState(getHomeId(), getZoneId());
130         logApiTransaction(zoneState, false);
131         return zoneState;
132     }
133
134     public GenericZoneCapabilities getZoneCapabilities() {
135         GenericZoneCapabilities capabilities = this.capabilities;
136         if (capabilities == null) {
137             throw new IllegalStateException("Zone capabilities not initialized");
138         }
139         return capabilities;
140     }
141
142     public TemperatureUnit getTemperatureUnit() {
143         return getHomeHandler().getTemperatureUnit();
144     }
145
146     public Overlay setOverlay(Overlay overlay) throws IOException, ApiException {
147         try {
148             logApiTransaction(overlay, true);
149             Overlay newOverlay = getApi().updateZoneOverlay(getHomeId(), getZoneId(), overlay);
150             logApiTransaction(newOverlay, false);
151             return newOverlay;
152         } catch (ApiException e) {
153             if (!logger.isTraceEnabled()) {
154                 logger.warn("ApiException sending JSON content:\n{}", convertToJsonString(overlay));
155             }
156             throw e;
157         }
158     }
159
160     public void removeOverlay() throws IOException, ApiException {
161         logger.debug("Removing overlay of home {} and zone {}", getHomeId(), getZoneId());
162         getApi().deleteZoneOverlay(getHomeId(), getZoneId());
163     }
164
165     @Override
166     public void handleCommand(ChannelUID channelUID, Command command) {
167         String id = channelUID.getId();
168
169         if (command == RefreshType.REFRESH) {
170             updateZoneState(false);
171             return;
172         }
173
174         synchronized (this) {
175             TadoHvacChange pendingHvacChange = this.pendingHvacChange;
176             if (pendingHvacChange == null) {
177                 throw new IllegalStateException("Zone pendingHvacChange not initialized");
178             }
179
180             switch (id) {
181                 case TadoBindingConstants.CHANNEL_ZONE_HVAC_MODE:
182                     pendingHvacChange.withHvacMode(((StringType) command).toFullString());
183                     scheduleHvacChange();
184                     break;
185                 case TadoBindingConstants.CHANNEL_ZONE_TARGET_TEMPERATURE:
186                     if (command instanceof QuantityType<?>) {
187                         @SuppressWarnings("unchecked")
188                         QuantityType<Temperature> state = (QuantityType<Temperature>) command;
189                         QuantityType<Temperature> stateInTargetUnit = getTemperatureUnit() == TemperatureUnit.FAHRENHEIT
190                                 ? state.toUnit(ImperialUnits.FAHRENHEIT)
191                                 : state.toUnit(SIUnits.CELSIUS);
192
193                         if (stateInTargetUnit != null) {
194                             pendingHvacChange.withTemperature(stateInTargetUnit.floatValue());
195                             scheduleHvacChange();
196                         }
197                     }
198                     break;
199                 case TadoBindingConstants.CHANNEL_ZONE_SWING:
200                     pendingHvacChange.withSwing(((OnOffType) command) == OnOffType.ON);
201                     scheduleHvacChange();
202                     break;
203                 case TadoBindingConstants.CHANNEL_ZONE_LIGHT:
204                     pendingHvacChange.withLight(((OnOffType) command) == OnOffType.ON);
205                     scheduleHvacChange();
206                     break;
207                 case TadoBindingConstants.CHANNEL_ZONE_FAN_SPEED:
208                     pendingHvacChange.withFanSpeed(((StringType) command).toFullString());
209                     scheduleHvacChange();
210                     break;
211                 case TadoBindingConstants.CHANNEL_ZONE_FAN_LEVEL:
212                     String fanLevelString = ((StringType) command).toFullString();
213                     pendingHvacChange.withFanLevel(FanLevel.valueOf(fanLevelString.toUpperCase()));
214                     scheduleHvacChange();
215                     break;
216                 case TadoBindingConstants.CHANNEL_ZONE_HORIZONTAL_SWING:
217                     String horizontalSwingString = ((StringType) command).toFullString();
218                     pendingHvacChange.withHorizontalSwing(HorizontalSwing.valueOf(horizontalSwingString.toUpperCase()));
219                     scheduleHvacChange();
220                     break;
221                 case TadoBindingConstants.CHANNEL_ZONE_VERTICAL_SWING:
222                     String verticalSwingString = ((StringType) command).toFullString();
223                     pendingHvacChange.withVerticalSwing(VerticalSwing.valueOf(verticalSwingString.toUpperCase()));
224                     scheduleHvacChange();
225                     break;
226                 case TadoBindingConstants.CHANNEL_ZONE_OPERATION_MODE:
227                     String operationMode = ((StringType) command).toFullString();
228                     pendingHvacChange.withOperationMode(OperationMode.valueOf(operationMode));
229                     scheduleHvacChange();
230                     break;
231                 case TadoBindingConstants.CHANNEL_ZONE_TIMER_DURATION:
232                     pendingHvacChange.activeForMinutes(((DecimalType) command).intValue());
233                     scheduleHvacChange();
234                     break;
235             }
236         }
237     }
238
239     @Override
240     public void initialize() {
241         disposing = false;
242         configuration = getConfigAs(TadoZoneConfig.class);
243         if (configuration.refreshInterval <= 0) {
244             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Refresh interval of zone "
245                     + getZoneId() + " of home " + getHomeId() + " must be greater than zero");
246             return;
247         } else if (configuration.fallbackTimerDuration <= 0) {
248             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Fallback timer duration of zone "
249                     + getZoneId() + " of home " + getHomeId() + " must be greater than zero");
250             return;
251         } else if (configuration.hvacChangeDebounce <= 0) {
252             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "HVAC change debounce of zone "
253                     + getZoneId() + " of home " + getHomeId() + " must be greater than zero");
254             return;
255         }
256
257         Bridge bridge = getBridge();
258         if (bridge != null) {
259             bridgeStatusChanged(bridge.getStatusInfo());
260         }
261     }
262
263     @Override
264     public void dispose() {
265         disposing = true;
266         cancelScheduledZoneStateUpdate();
267     }
268
269     @Override
270     public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
271         if (bridgeStatusInfo.getStatus() == ThingStatus.ONLINE) {
272             try {
273                 Zone zoneDetails = getApi().showZoneDetails(getHomeId(), getZoneId());
274                 logApiTransaction(zoneDetails, false);
275
276                 GenericZoneCapabilities capabilities = getApi().showZoneCapabilities(getHomeId(), getZoneId());
277                 logApiTransaction(capabilities, false);
278
279                 if (zoneDetails == null || capabilities == null) {
280                     updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
281                             "Can not access zone " + getZoneId() + " of home " + getHomeId());
282                     return;
283                 }
284
285                 updateProperty(TadoBindingConstants.PROPERTY_ZONE_NAME, zoneDetails.getName());
286                 updateProperty(TadoBindingConstants.PROPERTY_ZONE_TYPE, zoneDetails.getType().name());
287
288                 this.capabilities = capabilities;
289
290                 CapabilitiesSupport capabilitiesSupport = new CapabilitiesSupport(capabilities,
291                         getHomeHandler().getBatteryChecker().getZone(getZoneId()));
292
293                 updateDynamicChannels(capabilitiesSupport);
294             } catch (IOException | ApiException e) {
295                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
296                         "Could not connect to server due to " + e.getMessage());
297                 cancelScheduledZoneStateUpdate();
298                 return;
299             }
300
301             scheduleZoneStateUpdate();
302             pendingHvacChange = new TadoHvacChange(getThing());
303
304             updateStatus(ThingStatus.ONLINE);
305         } else {
306             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
307             cancelScheduledZoneStateUpdate();
308         }
309     }
310
311     private void updateZoneState(boolean forceUpdate) {
312         if ((thing.getStatus() != ThingStatus.ONLINE) || disposing) {
313             return;
314         }
315
316         getHomeHandler().updateHomeState();
317
318         // No update during HVAC change debounce
319         ScheduledFuture<?> scheduledHvacChange = this.scheduledHvacChange;
320         if (!forceUpdate && scheduledHvacChange != null && !scheduledHvacChange.isDone()) {
321             return;
322         }
323
324         try {
325             ZoneState zoneState = getZoneState();
326
327             logger.debug("Updating state of home {} and zone {}", getHomeId(), getZoneId());
328
329             TadoZoneStateAdapter state = new TadoZoneStateAdapter(zoneState, getTemperatureUnit());
330             updateState(TadoBindingConstants.CHANNEL_ZONE_CURRENT_TEMPERATURE, state.getInsideTemperature());
331             updateState(TadoBindingConstants.CHANNEL_ZONE_HUMIDITY, state.getHumidity());
332
333             updateState(TadoBindingConstants.CHANNEL_ZONE_HEATING_POWER, state.getHeatingPower());
334             updateState(TadoBindingConstants.CHANNEL_ZONE_AC_POWER, state.getAcPower());
335
336             updateState(TadoBindingConstants.CHANNEL_ZONE_OPERATION_MODE, state.getOperationMode());
337
338             updateState(TadoBindingConstants.CHANNEL_ZONE_HVAC_MODE, state.getMode());
339             updateState(TadoBindingConstants.CHANNEL_ZONE_TARGET_TEMPERATURE, state.getTargetTemperature());
340             updateState(TadoBindingConstants.CHANNEL_ZONE_FAN_SPEED, state.getFanSpeed());
341             updateState(TadoBindingConstants.CHANNEL_ZONE_SWING, state.getSwing());
342             updateState(TadoBindingConstants.CHANNEL_ZONE_LIGHT, state.getLight());
343             updateState(TadoBindingConstants.CHANNEL_ZONE_FAN_LEVEL, state.getFanLevel());
344             updateState(TadoBindingConstants.CHANNEL_ZONE_HORIZONTAL_SWING, state.getHorizontalSwing());
345             updateState(TadoBindingConstants.CHANNEL_ZONE_VERTICAL_SWING, state.getVerticalSwing());
346
347             updateState(TadoBindingConstants.CHANNEL_ZONE_TIMER_DURATION, state.getRemainingTimerDuration());
348
349             updateState(TadoBindingConstants.CHANNEL_ZONE_OVERLAY_EXPIRY, state.getOverlayExpiration());
350
351             updateState(TadoBindingConstants.CHANNEL_ZONE_OPEN_WINDOW_DETECTED, state.getOpenWindowDetected());
352
353             updateDynamicStateDescriptions(zoneState);
354
355             onSuccessfulOperation();
356         } catch (IOException | ApiException e) {
357             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
358                     "Could not connect to server due to " + e.getMessage());
359         }
360
361         updateState(TadoBindingConstants.CHANNEL_ZONE_BATTERY_LOW_ALARM,
362                 getHomeHandler().getBatteryChecker().getBatteryLowAlarm(getZoneId()));
363     }
364
365     /**
366      * Update the dynamic state descriptions for any channels which support an unknown sub- range of enumerator setting
367      * values, based on the list of capabilities reported by the respective zone.
368      *
369      * Note: currently this only applies to A/C devices that support fanLevel, horizontalSwing, or verticalSwing.
370      *
371      * @param zoneState the current zone Thing's state
372      */
373     private void updateDynamicStateDescriptions(ZoneState zoneState) {
374         GenericZoneSetting setting = zoneState.getSetting();
375         if (setting.getType() != TadoSystemType.AIR_CONDITIONING) {
376             return;
377         }
378
379         AcMode acMode = ((CoolingZoneSetting) setting).getMode();
380         AcModeCapabilities acModeCapabilities = acMode == null ? new AcModeCapabilities()
381                 : TadoApiTypeUtils.getModeCapabilities(acMode, capabilities);
382
383         // update the options list of supported fan levels
384         Channel channel = thing.getChannel(TadoBindingConstants.CHANNEL_ZONE_FAN_LEVEL);
385         if (channel != null) {
386             List<ACFanLevel> fanLevels = acModeCapabilities.getFanLevel();
387             if (fanLevels != null) {
388                 stateDescriptionProvider.setStateOptions(channel.getUID(),
389                         fanLevels.stream().map(u -> new StateOption(u.name(), u.name())).collect(Collectors.toList()));
390             }
391         }
392
393         // update the options list of supported horizontal swing settings
394         channel = thing.getChannel(TadoBindingConstants.CHANNEL_ZONE_HORIZONTAL_SWING);
395         if (channel != null) {
396             List<ACHorizontalSwing> horizontalSwings = acModeCapabilities.getHorizontalSwing();
397             if (horizontalSwings != null) {
398                 stateDescriptionProvider.setStateOptions(channel.getUID(), horizontalSwings.stream()
399                         .map(u -> new StateOption(u.name(), u.name())).collect(Collectors.toList()));
400             }
401         }
402
403         // update the options list of supported vertical swing settings
404         channel = thing.getChannel(TadoBindingConstants.CHANNEL_ZONE_VERTICAL_SWING);
405         if (channel != null) {
406             List<ACVerticalSwing> verticalSwings = acModeCapabilities.getVerticalSwing();
407             if (verticalSwings != null) {
408                 stateDescriptionProvider.setStateOptions(channel.getUID(), verticalSwings.stream()
409                         .map(u -> new StateOption(u.name(), u.name())).collect(Collectors.toList()));
410             }
411         }
412     }
413
414     private void scheduleZoneStateUpdate() {
415         ScheduledFuture<?> refreshTimer = this.refreshTimer;
416         if (refreshTimer == null || refreshTimer.isCancelled()) {
417             this.refreshTimer = scheduler.scheduleWithFixedDelay(new Runnable() {
418                 @Override
419                 public void run() {
420                     updateZoneState(false);
421                 }
422             }, 5, configuration.refreshInterval, TimeUnit.SECONDS);
423         }
424     }
425
426     private void cancelScheduledZoneStateUpdate() {
427         ScheduledFuture<?> refreshTimer = this.refreshTimer;
428         if (refreshTimer != null) {
429             refreshTimer.cancel(false);
430         }
431     }
432
433     private void scheduleHvacChange() {
434         ScheduledFuture<?> scheduledHvacChange = this.scheduledHvacChange;
435         if (scheduledHvacChange != null) {
436             scheduledHvacChange.cancel(false);
437         }
438         this.scheduledHvacChange = scheduler.schedule(() -> {
439             try {
440                 synchronized (this) {
441                     TadoHvacChange pendingHvacChange = this.pendingHvacChange;
442                     this.pendingHvacChange = new TadoHvacChange(getThing());
443                     if (pendingHvacChange != null) {
444                         pendingHvacChange.apply();
445                     }
446                 }
447             } catch (IOException e) {
448                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
449             } catch (ApiException e) {
450                 logger.warn("Could not apply HVAC change on home {} and zone {}: {}", getHomeId(), getZoneId(),
451                         e.getMessage(), e);
452             } finally {
453                 updateZoneState(true);
454             }
455         }, configuration.hvacChangeDebounce, TimeUnit.SECONDS);
456     }
457
458     /**
459      * Helper method to log an API transaction on the given object.
460      * If the logger level is 'debug', the transaction is simply logged.
461      * If the logger level is 'trace, the object's JSON serial contents are included.
462      *
463      * @param object the object to be logged.
464      * @param isCommand marks whether the transaction is a command to, or a response from, the server.
465      */
466     private void logApiTransaction(Object object, boolean isCommand) {
467         if (logger.isDebugEnabled() || logger.isTraceEnabled()) {
468             String logType = isCommand ? "command" : "response";
469             if (logger.isTraceEnabled()) {
470                 logger.trace("Api {}: homeId:{}, zoneId:{}, objectId:{}, content:\n{}", logType, getHomeId(),
471                         getZoneId(), object.getClass().getSimpleName(), convertToJsonString(object));
472             } else if (logger.isDebugEnabled()) {
473                 logger.debug("Api {}: homeId:{}, zoneId:{}, objectId:{}", logType, getHomeId(), getZoneId(),
474                         object.getClass().getSimpleName());
475             }
476         }
477     }
478
479     private synchronized String convertToJsonString(Object object) {
480         Gson gson = this.gson;
481         if (gson == null) {
482             gson = this.gson = GsonBuilderFactory.defaultGsonBuilder().setPrettyPrinting().create();
483         }
484         return gson.toJson(object);
485     }
486
487     /**
488      * If the given channel exists in the thing, but is NOT required in the thing, then add it to a list of channels to
489      * be removed. Or if the channel does NOT exist in the thing, but is required in the thing, then log a warning.
490      *
491      * @param removeList the list of channels to be removed from the thing.
492      * @param channelId the id of the channel to be (eventually) removed.
493      * @param channelRequired true if the thing requires this channel.
494      */
495     private void removeListProcessChannel(List<Channel> removeList, String channelId, boolean channelRequired) {
496         Channel channel = thing.getChannel(channelId);
497         if (!channelRequired && channel != null) {
498             removeList.add(channel);
499         } else if (channelRequired && channel == null) {
500             logger.warn("Thing {} does not have a '{}' channel => please reinitialize it", thing.getUID(), channelId);
501         }
502     }
503
504     /**
505      * Remove previously statically created channels if the device does not support them.
506      *
507      * @param capabilitiesSupport a CapabilitiesSupport instance which summarizes the device's capabilities.
508      * @throws IllegalStateException if any of the channel builders failed.
509      */
510     private void updateDynamicChannels(CapabilitiesSupport capabilitiesSupport) {
511         List<Channel> removeList = new ArrayList<>();
512
513         removeListProcessChannel(removeList, TadoBindingConstants.CHANNEL_ZONE_BATTERY_LOW_ALARM,
514                 capabilitiesSupport.batteryLowAlarm());
515         removeListProcessChannel(removeList, TadoBindingConstants.CHANNEL_ZONE_OPEN_WINDOW_DETECTED,
516                 capabilitiesSupport.openWindow());
517         removeListProcessChannel(removeList, TadoBindingConstants.CHANNEL_ZONE_LIGHT, capabilitiesSupport.light());
518         removeListProcessChannel(removeList, TadoBindingConstants.CHANNEL_ZONE_HORIZONTAL_SWING,
519                 capabilitiesSupport.horizontalSwing());
520         removeListProcessChannel(removeList, TadoBindingConstants.CHANNEL_ZONE_VERTICAL_SWING,
521                 capabilitiesSupport.verticalSwing());
522         removeListProcessChannel(removeList, TadoBindingConstants.CHANNEL_ZONE_SWING, capabilitiesSupport.swing());
523         removeListProcessChannel(removeList, TadoBindingConstants.CHANNEL_ZONE_FAN_SPEED,
524                 capabilitiesSupport.fanSpeed());
525         removeListProcessChannel(removeList, TadoBindingConstants.CHANNEL_ZONE_FAN_LEVEL,
526                 capabilitiesSupport.fanLevel());
527         removeListProcessChannel(removeList, TadoBindingConstants.CHANNEL_ZONE_AC_POWER, capabilitiesSupport.acPower());
528         removeListProcessChannel(removeList, TadoBindingConstants.CHANNEL_ZONE_HEATING_POWER,
529                 capabilitiesSupport.heatingPower());
530         removeListProcessChannel(removeList, TadoBindingConstants.CHANNEL_ZONE_HUMIDITY,
531                 capabilitiesSupport.humidity());
532         removeListProcessChannel(removeList, TadoBindingConstants.CHANNEL_ZONE_CURRENT_TEMPERATURE,
533                 capabilitiesSupport.currentTemperature());
534
535         if (!removeList.isEmpty()) {
536             if (logger.isDebugEnabled()) {
537                 StringJoiner joiner = new StringJoiner(", ");
538                 removeList.forEach(c -> joiner.add(c.getUID().getId()));
539                 logger.debug("Removing unsupported channels for {}: {}", thing.getUID(), joiner.toString());
540             }
541             updateThing(editThing().withoutChannels(removeList).build());
542         }
543     }
544 }