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