]> git.basschouten.com Git - openhab-addons.git/blob
204e29dc458fc58f5102ccb26e72be520c3c57b6
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2021 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.homematic.internal.handler;
14
15 import static org.openhab.binding.homematic.internal.HomematicBindingConstants.*;
16 import static org.openhab.binding.homematic.internal.misc.HomematicConstants.*;
17
18 import java.io.IOException;
19 import java.math.BigDecimal;
20 import java.util.ArrayList;
21 import java.util.HashMap;
22 import java.util.Iterator;
23 import java.util.List;
24 import java.util.Map;
25 import java.util.Map.Entry;
26 import java.util.Objects;
27 import java.util.concurrent.Future;
28
29 import org.openhab.binding.homematic.internal.HomematicBindingConstants;
30 import org.openhab.binding.homematic.internal.common.HomematicConfig;
31 import org.openhab.binding.homematic.internal.communicator.HomematicGateway;
32 import org.openhab.binding.homematic.internal.converter.ConverterException;
33 import org.openhab.binding.homematic.internal.converter.ConverterFactory;
34 import org.openhab.binding.homematic.internal.converter.ConverterTypeException;
35 import org.openhab.binding.homematic.internal.converter.TypeConverter;
36 import org.openhab.binding.homematic.internal.misc.HomematicClientException;
37 import org.openhab.binding.homematic.internal.misc.HomematicConstants;
38 import org.openhab.binding.homematic.internal.model.HmChannel;
39 import org.openhab.binding.homematic.internal.model.HmDatapoint;
40 import org.openhab.binding.homematic.internal.model.HmDatapointConfig;
41 import org.openhab.binding.homematic.internal.model.HmDatapointInfo;
42 import org.openhab.binding.homematic.internal.model.HmDevice;
43 import org.openhab.binding.homematic.internal.model.HmParamsetType;
44 import org.openhab.binding.homematic.internal.type.HomematicChannelTypeProvider;
45 import org.openhab.binding.homematic.internal.type.HomematicTypeGeneratorImpl;
46 import org.openhab.binding.homematic.internal.type.MetadataUtils;
47 import org.openhab.binding.homematic.internal.type.UidUtils;
48 import org.openhab.core.config.core.Configuration;
49 import org.openhab.core.config.core.validation.ConfigValidationException;
50 import org.openhab.core.library.types.OnOffType;
51 import org.openhab.core.library.types.StopMoveType;
52 import org.openhab.core.thing.Bridge;
53 import org.openhab.core.thing.Channel;
54 import org.openhab.core.thing.ChannelUID;
55 import org.openhab.core.thing.Thing;
56 import org.openhab.core.thing.ThingStatus;
57 import org.openhab.core.thing.ThingStatusDetail;
58 import org.openhab.core.thing.binding.BaseThingHandler;
59 import org.openhab.core.thing.binding.ThingHandler;
60 import org.openhab.core.thing.binding.builder.ChannelBuilder;
61 import org.openhab.core.thing.type.ChannelType;
62 import org.openhab.core.thing.type.ChannelTypeUID;
63 import org.openhab.core.types.Command;
64 import org.openhab.core.types.RefreshType;
65 import org.openhab.core.types.State;
66 import org.slf4j.Logger;
67 import org.slf4j.LoggerFactory;
68
69 /**
70  * The {@link HomematicThingHandler} is responsible for handling commands, which are sent to one of the channels.
71  *
72  * @author Gerhard Riegler - Initial contribution
73  */
74 public class HomematicThingHandler extends BaseThingHandler {
75     private final Logger logger = LoggerFactory.getLogger(HomematicThingHandler.class);
76     private final HomematicChannelTypeProvider channelTypeProvider;
77     private Future<?> initFuture;
78     private final Object initLock = new Object();
79     private volatile boolean deviceDeletionPending = false;
80
81     public HomematicThingHandler(Thing thing, HomematicChannelTypeProvider channelTypeProvider) {
82         super(thing);
83         this.channelTypeProvider = channelTypeProvider;
84     }
85
86     @Override
87     public void initialize() {
88         if (initFuture != null) {
89             return;
90         }
91
92         initFuture = scheduler.submit(() -> {
93             initFuture = null;
94             try {
95                 synchronized (initLock) {
96                     doInitializeInBackground();
97                 }
98             } catch (HomematicClientException ex) {
99                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, ex.getMessage());
100             } catch (IOException ex) {
101                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, ex.getMessage());
102             } catch (GatewayNotAvailableException ex) {
103                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE, ex.getMessage());
104             } catch (Exception ex) {
105                 logger.error("{}", ex.getMessage(), ex);
106                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, ex.getMessage());
107             }
108         });
109     }
110
111     private void doInitializeInBackground() throws GatewayNotAvailableException, HomematicClientException, IOException {
112         HomematicGateway gateway = getHomematicGateway();
113         HmDevice device = gateway.getDevice(UidUtils.getHomematicAddress(getThing()));
114         HmChannel channelZero = device.getChannel(0);
115         loadHomematicChannelValues(channelZero);
116         updateStatus(device);
117         logger.debug("Initializing thing '{}' from gateway '{}'", getThing().getUID(), gateway.getId());
118
119         // update properties
120         Map<String, String> properties = editProperties();
121         setProperty(properties, channelZero, PROPERTY_BATTERY_TYPE, VIRTUAL_DATAPOINT_NAME_BATTERY_TYPE);
122         setProperty(properties, channelZero, Thing.PROPERTY_FIRMWARE_VERSION, VIRTUAL_DATAPOINT_NAME_FIRMWARE);
123         setProperty(properties, channelZero, Thing.PROPERTY_SERIAL_NUMBER, device.getAddress());
124         setProperty(properties, channelZero, PROPERTY_AES_KEY, DATAPOINT_NAME_AES_KEY);
125         updateProperties(properties);
126
127         // update data point list for reconfigurable channels
128         for (HmChannel channel : device.getChannels()) {
129             if (channel.isReconfigurable()) {
130                 loadHomematicChannelValues(channel);
131                 if (channel.checkForChannelFunctionChange()) {
132                     gateway.updateChannelValueDatapoints(channel);
133                 }
134             }
135         }
136
137         // update configurations
138         Configuration config = editConfiguration();
139         for (HmChannel channel : device.getChannels()) {
140             loadHomematicChannelValues(channel);
141             for (HmDatapoint dp : channel.getDatapoints()) {
142                 if (dp.getParamsetType() == HmParamsetType.MASTER) {
143                     config.put(MetadataUtils.getParameterName(dp),
144                             dp.isEnumType() ? dp.getOptionValue() : dp.getValue());
145                 }
146             }
147         }
148         updateConfiguration(config);
149
150         // update thing channel list for reconfigurable channels (relies on the new value of the
151         // CHANNEL_FUNCTION datapoint fetched during configuration update)
152         List<Channel> thingChannels = new ArrayList<>(getThing().getChannels());
153         if (updateDynamicChannelList(device, thingChannels)) {
154             updateThing(editThing().withChannels(thingChannels).build());
155         }
156
157         thingChannels.forEach(channel -> {
158             if (isLinked(channel.getUID())) {
159                 channelLinked(channel.getUID());
160             }
161         });
162     }
163
164     /**
165      * Update the given thing channel list to reflect the device's current datapoint set
166      *
167      * @return true if the list was modified, false if it was not modified
168      */
169     private boolean updateDynamicChannelList(HmDevice device, List<Channel> thingChannels) {
170         boolean changed = false;
171         for (HmChannel channel : device.getChannels()) {
172             if (!channel.isReconfigurable()) {
173                 continue;
174             }
175             final String expectedFunction = channel
176                     .getDatapoint(HmParamsetType.MASTER, HomematicConstants.DATAPOINT_NAME_CHANNEL_FUNCTION)
177                     .getOptionValue();
178             final String propertyName = String.format(PROPERTY_DYNAMIC_FUNCTION_FORMAT, channel.getNumber());
179
180             // remove thing channels that were configured for a different function
181             Iterator<Channel> channelIter = thingChannels.iterator();
182             while (channelIter.hasNext()) {
183                 Map<String, String> properties = channelIter.next().getProperties();
184                 String function = properties.get(propertyName);
185                 if (function != null && !function.equals(expectedFunction)) {
186                     channelIter.remove();
187                     changed = true;
188                 }
189             }
190             for (HmDatapoint dp : channel.getDatapoints()) {
191                 if (HomematicTypeGeneratorImpl.isIgnoredDatapoint(dp)
192                         || dp.getParamsetType() != HmParamsetType.VALUES) {
193                     continue;
194                 }
195                 ChannelUID channelUID = UidUtils.generateChannelUID(dp, getThing().getUID());
196                 if (containsChannel(thingChannels, channelUID)) {
197                     // Channel is already present -> channel configuration likely hasn't changed
198                     continue;
199                 }
200
201                 Map<String, String> channelProps = new HashMap<>();
202                 channelProps.put(propertyName, expectedFunction);
203
204                 ChannelTypeUID channelTypeUID = UidUtils.generateChannelTypeUID(dp);
205                 ChannelType channelType = channelTypeProvider.getInternalChannelType(channelTypeUID);
206                 if (channelType == null) {
207                     channelType = HomematicTypeGeneratorImpl.createChannelType(dp, channelTypeUID);
208                     channelTypeProvider.addChannelType(channelType);
209                 }
210
211                 Channel thingChannel = ChannelBuilder.create(channelUID, MetadataUtils.getItemType(dp))
212                         .withProperties(channelProps).withLabel(MetadataUtils.getLabel(dp))
213                         .withDescription(MetadataUtils.getDatapointDescription(dp)).withType(channelType.getUID())
214                         .build();
215                 thingChannels.add(thingChannel);
216                 changed = true;
217             }
218         }
219
220         return changed;
221     }
222
223     /**
224      * Checks whether the given list includes a channel with the given UID
225      */
226     private static boolean containsChannel(List<Channel> channels, ChannelUID channelUID) {
227         for (Channel channel : channels) {
228             ChannelUID uid = channel.getUID();
229             if (Objects.equals(channelUID.getGroupId(), uid.getGroupId())
230                     && Objects.equals(channelUID.getId(), uid.getId())) {
231                 return true;
232             }
233         }
234         return false;
235     }
236
237     /**
238      * Sets a thing property with a datapoint value.
239      */
240     private void setProperty(Map<String, String> properties, HmChannel channelZero, String propertyName,
241             String datapointName) {
242         HmDatapoint dp = channelZero
243                 .getDatapoint(new HmDatapointInfo(HmParamsetType.VALUES, channelZero, datapointName));
244         if (dp != null) {
245             properties.put(propertyName, Objects.toString(dp.getValue(), ""));
246         }
247     }
248
249     @Override
250     public void channelLinked(ChannelUID channelUID) {
251         handleRefresh(channelUID);
252     }
253
254     /**
255      * Updates the state of the given channel.
256      */
257     protected void handleRefresh(ChannelUID channelUID) {
258         try {
259             if (thing.getStatus() == ThingStatus.ONLINE) {
260                 logger.debug("Updating channel '{}' from thing id '{}'", channelUID, getThing().getUID().getId());
261                 updateChannelState(channelUID);
262             }
263         } catch (Exception ex) {
264             logger.warn("{}", ex.getMessage());
265         }
266     }
267
268     @Override
269     public void handleCommand(ChannelUID channelUID, Command command) {
270         logger.debug("Received command '{}' for channel '{}'", command, channelUID);
271         HmDatapoint dp = null;
272         try {
273             HomematicGateway gateway = getHomematicGateway();
274             HmDatapointInfo dpInfo = UidUtils.createHmDatapointInfo(channelUID);
275             if (RefreshType.REFRESH == command) {
276                 logger.debug("Refreshing {}", dpInfo);
277                 dpInfo = new HmDatapointInfo(dpInfo.getAddress(), HmParamsetType.VALUES, 0,
278                         VIRTUAL_DATAPOINT_NAME_RELOAD_FROM_GATEWAY);
279                 dp = gateway.getDatapoint(dpInfo);
280                 sendDatapoint(dp, new HmDatapointConfig(), Boolean.TRUE);
281             } else {
282                 Channel channel = getThing().getChannel(channelUID.getId());
283                 if (channel == null) {
284                     logger.warn("Channel '{}' not found in thing '{}' on gateway '{}'", channelUID, getThing().getUID(),
285                             gateway.getId());
286                 } else {
287                     if (StopMoveType.STOP == command && DATAPOINT_NAME_LEVEL.equals(dpInfo.getName())) {
288                         // special case with stop type (rollershutter)
289                         dpInfo.setName(DATAPOINT_NAME_STOP);
290                         HmDatapoint stopDp = gateway.getDatapoint(dpInfo);
291                         ChannelUID stopChannelUID = UidUtils.generateChannelUID(stopDp, getThing().getUID());
292                         handleCommand(stopChannelUID, OnOffType.ON);
293                     } else {
294                         dp = gateway.getDatapoint(dpInfo);
295                         TypeConverter<?> converter = ConverterFactory.createConverter(channel.getAcceptedItemType());
296                         Object newValue = converter.convertToBinding(command, dp);
297                         HmDatapointConfig config = getChannelConfig(channel, dp);
298                         sendDatapoint(dp, config, newValue);
299                     }
300                 }
301             }
302         } catch (HomematicClientException | GatewayNotAvailableException ex) {
303             logger.warn("{}", ex.getMessage());
304         } catch (IOException ex) {
305             if (dp != null && dp.getChannel().getDevice().isOffline()) {
306                 logger.warn("Device '{}' is OFFLINE, can't send command '{}' for channel '{}'",
307                         dp.getChannel().getDevice().getAddress(), command, channelUID);
308                 logger.trace("{}", ex.getMessage(), ex);
309             } else {
310                 logger.error("{}", ex.getMessage(), ex);
311             }
312         } catch (ConverterTypeException ex) {
313             logger.warn("{}, please check the item type and the commands in your scripts", ex.getMessage());
314         } catch (Exception ex) {
315             logger.error("{}", ex.getMessage(), ex);
316         }
317     }
318
319     private void sendDatapoint(HmDatapoint dp, HmDatapointConfig config, Object newValue)
320             throws IOException, HomematicClientException, GatewayNotAvailableException {
321         String rxMode = getRxModeForDatapointTransmission(dp.getName(), dp.getValue(), newValue);
322         getHomematicGateway().sendDatapoint(dp, config, newValue, rxMode);
323     }
324
325     /**
326      * Returns the rx mode that shall be used for transmitting a new value of a datapoint to the device. The
327      * HomematicThingHandler always uses the default rx mode; custom thing handlers can override this method to
328      * adjust the rx mode.
329      *
330      * @param datapointName The datapoint that will be updated on the device
331      * @param currentValue The current value of the datapoint
332      * @param newValue The value that will be sent to the device
333      * @return The rxMode ({@link HomematicBindingConstants#RX_BURST_MODE "BURST"} for burst mode,
334      *         {@link HomematicBindingConstants#RX_WAKEUP_MODE "WAKEUP"} for wakeup mode, or null for the default mode)
335      */
336     protected String getRxModeForDatapointTransmission(String datapointName, Object currentValue, Object newValue) {
337         return null;
338     }
339
340     /**
341      * Evaluates the channel and datapoint for this channelUID and updates the state of the channel.
342      */
343     private void updateChannelState(ChannelUID channelUID)
344             throws GatewayNotAvailableException, HomematicClientException, IOException, ConverterException {
345         HomematicGateway gateway = getHomematicGateway();
346         HmDatapointInfo dpInfo = UidUtils.createHmDatapointInfo(channelUID);
347         HmDatapoint dp = gateway.getDatapoint(dpInfo);
348         Channel channel = getThing().getChannel(channelUID.getId());
349         updateChannelState(dp, channel);
350     }
351
352     /**
353      * Sets the configuration or evaluates the channel for this datapoint and updates the state of the channel.
354      */
355     protected void updateDatapointState(HmDatapoint dp) {
356         try {
357             updateStatus(dp.getChannel().getDevice());
358
359             if (dp.getParamsetType() == HmParamsetType.MASTER) {
360                 // update configuration
361                 Configuration config = editConfiguration();
362                 config.put(MetadataUtils.getParameterName(dp), dp.isEnumType() ? dp.getOptionValue() : dp.getValue());
363                 updateConfiguration(config);
364             } else if (!HomematicTypeGeneratorImpl.isIgnoredDatapoint(dp)) {
365                 // update channel
366                 ChannelUID channelUID = UidUtils.generateChannelUID(dp, thing.getUID());
367                 Channel channel = thing.getChannel(channelUID.getId());
368                 if (channel != null) {
369                     updateChannelState(dp, channel);
370                 } else {
371                     logger.warn("Channel not found for datapoint '{}'", new HmDatapointInfo(dp));
372                 }
373             }
374         } catch (GatewayNotAvailableException ex) {
375             // ignore
376         } catch (Exception ex) {
377             logger.error("{}", ex.getMessage(), ex);
378         }
379     }
380
381     /**
382      * Converts the value of the datapoint to a State, updates the channel and also sets the thing status if necessary.
383      */
384     private void updateChannelState(final HmDatapoint dp, Channel channel)
385             throws IOException, GatewayNotAvailableException, ConverterException {
386         if (dp.isTrigger()) {
387             final Object value = dp.getValue();
388             if (value != null && !value.equals(dp.getPreviousValue())) {
389                 triggerChannel(channel.getUID(), value.toString());
390             }
391         } else if (isLinked(channel)) {
392             loadHomematicChannelValues(dp.getChannel());
393
394             TypeConverter<?> converter = ConverterFactory.createConverter(channel.getAcceptedItemType());
395             State state = converter.convertFromBinding(dp);
396             if (state != null) {
397                 updateState(channel.getUID(), state);
398             } else {
399                 logger.debug("Failed to get converted state from datapoint '{}'", dp.getName());
400             }
401         }
402     }
403
404     /**
405      * Loads all values for the given Homematic channel if it is not initialized.
406      */
407     private void loadHomematicChannelValues(HmChannel hmChannel) throws GatewayNotAvailableException, IOException {
408         if (!hmChannel.isInitialized()) {
409             synchronized (this) {
410                 if (!hmChannel.isInitialized()) {
411                     try {
412                         getHomematicGateway().loadChannelValues(hmChannel);
413                     } catch (IOException ex) {
414                         if (hmChannel.getDevice().isOffline()) {
415                             logger.warn("Device '{}' is OFFLINE, can't update channel '{}'",
416                                     hmChannel.getDevice().getAddress(), hmChannel.getNumber());
417                         } else {
418                             throw ex;
419                         }
420                     }
421                 }
422             }
423         }
424     }
425
426     /**
427      * Updates the thing status based on device status.
428      */
429     private void updateStatus(HmDevice device) throws GatewayNotAvailableException, IOException {
430         loadHomematicChannelValues(device.getChannel(0));
431
432         ThingStatus oldStatus = thing.getStatus();
433         if (oldStatus == ThingStatus.UNINITIALIZED) {
434             return;
435         }
436         ThingStatus newStatus = ThingStatus.ONLINE;
437         ThingStatusDetail newDetail = ThingStatusDetail.NONE;
438
439         if ((getBridge() != null) && (getBridge().getStatus() == ThingStatus.OFFLINE)) {
440             newStatus = ThingStatus.OFFLINE;
441             newDetail = ThingStatusDetail.BRIDGE_OFFLINE;
442         } else if (device.isFirmwareUpdating()) {
443             newStatus = ThingStatus.OFFLINE;
444             newDetail = ThingStatusDetail.FIRMWARE_UPDATING;
445         } else if (device.isUnreach()) {
446             newStatus = ThingStatus.OFFLINE;
447             newDetail = ThingStatusDetail.COMMUNICATION_ERROR;
448         } else if (device.isConfigPending() || device.isUpdatePending()) {
449             newDetail = ThingStatusDetail.CONFIGURATION_PENDING;
450         }
451
452         if (thing.getStatus() != newStatus || thing.getStatusInfo().getStatusDetail() != newDetail) {
453             updateStatus(newStatus, newDetail);
454         }
455         if (oldStatus == ThingStatus.OFFLINE && newStatus == ThingStatus.ONLINE) {
456             initialize();
457         }
458     }
459
460     /**
461      * Returns true, if the channel is linked at least to one item.
462      */
463     private boolean isLinked(Channel channel) {
464         return channel != null && super.isLinked(channel.getUID().getId());
465     }
466
467     /**
468      * Returns the channel config for the given datapoint.
469      */
470     protected HmDatapointConfig getChannelConfig(HmDatapoint dp) {
471         ChannelUID channelUid = UidUtils.generateChannelUID(dp, getThing().getUID());
472         Channel channel = getThing().getChannel(channelUid.getId());
473         return channel != null ? getChannelConfig(channel, dp) : new HmDatapointConfig();
474     }
475
476     /**
477      * Returns the config for a channel.
478      */
479     private HmDatapointConfig getChannelConfig(Channel channel, HmDatapoint dp) {
480         return channel.getConfiguration().as(HmDatapointConfig.class);
481     }
482
483     /**
484      * Returns the Homematic gateway if the bridge is available.
485      */
486     private HomematicGateway getHomematicGateway() throws GatewayNotAvailableException {
487         final Bridge bridge = getBridge();
488         if (bridge != null) {
489             HomematicBridgeHandler bridgeHandler = (HomematicBridgeHandler) bridge.getHandler();
490             if (bridgeHandler != null && bridgeHandler.getGateway() != null) {
491                 return bridgeHandler.getGateway();
492             }
493         }
494
495         throw new GatewayNotAvailableException("HomematicGateway not yet available!");
496     }
497
498     @Override
499     public void handleConfigurationUpdate(Map<String, Object> configurationParameters)
500             throws ConfigValidationException {
501         super.handleConfigurationUpdate(configurationParameters);
502
503         try {
504             HomematicGateway gateway = getHomematicGateway();
505             HmDevice device = gateway.getDevice(UidUtils.getHomematicAddress(getThing()));
506
507             for (Entry<String, Object> configurationParameter : configurationParameters.entrySet()) {
508                 String key = configurationParameter.getKey();
509                 Object newValue = configurationParameter.getValue();
510
511                 if (key.startsWith("HMP_")) {
512                     key = key.substring(4);
513                     int sepPos = key.indexOf("_");
514                     Integer channelNumber = Integer.valueOf(key.substring(0, sepPos));
515                     String dpName = key.substring(sepPos + 1);
516
517                     HmDatapointInfo dpInfo = new HmDatapointInfo(device.getAddress(), HmParamsetType.MASTER,
518                             channelNumber, dpName);
519                     HmDatapoint dp = device.getChannel(channelNumber).getDatapoint(dpInfo);
520
521                     if (dp != null) {
522                         try {
523                             if (newValue != null) {
524                                 if (newValue instanceof BigDecimal) {
525                                     final BigDecimal decimal = (BigDecimal) newValue;
526                                     if (dp.isIntegerType()) {
527                                         newValue = decimal.intValue();
528                                     } else if (dp.isFloatType()) {
529                                         newValue = decimal.doubleValue();
530                                     }
531                                 }
532                                 if (!Objects.equals(dp.isEnumType() ? dp.getOptionValue() : dp.getValue(), newValue)) {
533                                     sendDatapoint(dp, new HmDatapointConfig(), newValue);
534                                 }
535                             }
536                         } catch (IOException ex) {
537                             logger.error("Error setting thing property {}: {}", dpInfo, ex.getMessage());
538                         }
539                     } else {
540                         logger.error("Can't find datapoint for thing property {}", dpInfo);
541                     }
542                 }
543             }
544             gateway.triggerDeviceValuesReload(device);
545         } catch (HomematicClientException | GatewayNotAvailableException ex) {
546             logger.error("Error setting thing properties: {}", ex.getMessage(), ex);
547         }
548     }
549
550     @SuppressWarnings("null")
551     @Override
552     public synchronized void handleRemoval() {
553         final Bridge bridge;
554         final ThingHandler handler;
555
556         if ((bridge = getBridge()) == null || (handler = bridge.getHandler()) == null) {
557             super.handleRemoval();
558             return;
559         }
560
561         final HomematicConfig config = bridge.getConfiguration().as(HomematicConfig.class);
562         final boolean factoryResetOnDeletion = config.isFactoryResetOnDeletion();
563         final boolean unpairOnDeletion = factoryResetOnDeletion || config.isUnpairOnDeletion();
564
565         if (unpairOnDeletion) {
566             deviceDeletionPending = true;
567             ((HomematicBridgeHandler) handler).deleteFromGateway(UidUtils.getHomematicAddress(thing),
568                     factoryResetOnDeletion, false, true);
569         } else {
570             super.handleRemoval();
571         }
572     }
573
574     /**
575      * Called by the bridgeHandler when this device has been removed from the gateway.
576      */
577     public synchronized void deviceRemoved() {
578         deviceDeletionPending = false;
579         if (getThing().getStatus() == ThingStatus.REMOVING) {
580             // thing removal was initiated
581             updateStatus(ThingStatus.REMOVED);
582         } else {
583             // device removal was initiated on homematic side, thing is not removed
584             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.GONE);
585         }
586     }
587
588     /**
589      * Called by the bridgeHandler when the device for this thing has been added to the gateway.
590      * This is used to reconnect a device that was previously unpaired.
591      *
592      * @param device The device that has been added to the gateway
593      */
594     public void deviceLoaded(HmDevice device) {
595         try {
596             updateStatus(device);
597         } catch (GatewayNotAvailableException ex) {
598             // ignore
599         } catch (IOException ex) {
600             logger.warn("Could not reinitialize the device '{}': {}", device.getAddress(), ex.getMessage(), ex);
601         }
602     }
603
604     /**
605      * Returns whether the device deletion is pending.
606      *
607      * @return true, if the deletion of this device on its gateway has been triggered but has not yet completed
608      */
609     public synchronized boolean isDeletionPending() {
610         return deviceDeletionPending;
611     }
612 }