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