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