]> git.basschouten.com Git - openhab-addons.git/blob
22693d61518efe18350f21582eff1f6296b1de14
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 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.discovery;
14
15 import static org.openhab.binding.homematic.internal.HomematicBindingConstants.BINDING_ID;
16
17 import java.util.Collections;
18 import java.util.concurrent.ExecutionException;
19 import java.util.concurrent.Future;
20
21 import org.eclipse.jdt.annotation.NonNullByDefault;
22 import org.eclipse.jdt.annotation.Nullable;
23 import org.openhab.binding.homematic.internal.common.HomematicConfig;
24 import org.openhab.binding.homematic.internal.communicator.HomematicGateway;
25 import org.openhab.binding.homematic.internal.handler.HomematicBridgeHandler;
26 import org.openhab.binding.homematic.internal.model.HmDevice;
27 import org.openhab.binding.homematic.internal.type.UidUtils;
28 import org.openhab.core.config.discovery.AbstractDiscoveryService;
29 import org.openhab.core.config.discovery.DiscoveryResult;
30 import org.openhab.core.config.discovery.DiscoveryResultBuilder;
31 import org.openhab.core.config.discovery.DiscoveryService;
32 import org.openhab.core.thing.Thing;
33 import org.openhab.core.thing.ThingStatus;
34 import org.openhab.core.thing.ThingTypeUID;
35 import org.openhab.core.thing.ThingUID;
36 import org.openhab.core.thing.binding.ThingHandler;
37 import org.openhab.core.thing.binding.ThingHandlerService;
38 import org.slf4j.Logger;
39 import org.slf4j.LoggerFactory;
40
41 /**
42  * The {@link HomematicDeviceDiscoveryService} is used to discover devices that are connected to a Homematic gateway.
43  *
44  * @author Gerhard Riegler - Initial contribution
45  */
46 public class HomematicDeviceDiscoveryService extends AbstractDiscoveryService
47         implements DiscoveryService, ThingHandlerService {
48     private final Logger logger = LoggerFactory.getLogger(HomematicDeviceDiscoveryService.class);
49     private static final int DISCOVER_TIMEOUT_SECONDS = 300;
50
51     private @NonNullByDefault({}) HomematicBridgeHandler bridgeHandler;
52     private Future<?> loadDevicesFuture;
53     private volatile boolean isInInstallMode = false;
54     private volatile Object installModeSync = new Object();
55
56     public HomematicDeviceDiscoveryService() {
57         super(Collections.singleton(new ThingTypeUID(BINDING_ID, "-")), DISCOVER_TIMEOUT_SECONDS, false);
58     }
59
60     @Override
61     public void setThingHandler(@Nullable ThingHandler handler) {
62         if (handler instanceof HomematicBridgeHandler) {
63             this.bridgeHandler = (HomematicBridgeHandler) handler;
64             this.bridgeHandler.setDiscoveryService(this);
65         }
66     }
67
68     @Override
69     public @Nullable ThingHandler getThingHandler() {
70         return bridgeHandler;
71     }
72
73     /**
74      * Called on component activation.
75      */
76     @Override
77     public void activate() {
78         super.activate(null);
79     }
80
81     @Override
82     public void deactivate() {
83         super.deactivate();
84     }
85
86     @Override
87     protected void startScan() {
88         logger.debug("Starting Homematic discovery scan");
89         enableInstallMode();
90         loadDevices();
91     }
92
93     /**
94      * Will set controller in <i>installMode==true</i>, but only if the bridge
95      * is ONLINE (e.g. not during INITIALIZATION).
96      */
97     private void enableInstallMode() {
98         try {
99             HomematicGateway gateway = bridgeHandler.getGateway();
100             ThingStatus bridgeStatus = null;
101
102             if (bridgeHandler != null) {
103                 Thing bridge = bridgeHandler.getThing();
104                 bridgeStatus = bridge.getStatus();
105             }
106             if (ThingStatus.ONLINE == bridgeStatus) {
107                 gateway.setInstallMode(true, getInstallModeDuration());
108
109                 int remaining = gateway.getInstallMode();
110                 if (remaining > 0) {
111                     setIsInInstallMode();
112                     logger.debug("Successfully put controller in install mode. Remaining time: {} seconds", remaining);
113                 } else {
114                     logger.warn("Controller did not accept requested install mode");
115                 }
116             } else {
117                 logger.debug("Will not attempt to set controller in install mode, because bridge is not ONLINE.");
118             }
119         } catch (Exception ex) {
120             logger.warn("Failed to set Homematic controller in install mode", ex);
121         }
122     }
123
124     private int getInstallModeDuration() {
125         if (bridgeHandler != null) {
126             return bridgeHandler.getThing().getConfiguration().as(HomematicConfig.class).getInstallModeDuration();
127         }
128         return HomematicConfig.DEFAULT_INSTALL_MODE_DURATION;
129     }
130
131     @Override
132     public int getScanTimeout() {
133         return getInstallModeDuration();
134     }
135
136     @Override
137     public synchronized void stopScan() {
138         logger.debug("Stopping Homematic discovery scan");
139         if (bridgeHandler != null && bridgeHandler.getGateway() != null) {
140             disableInstallMode();
141             bridgeHandler.getGateway().cancelLoadAllDeviceMetadata();
142         }
143         waitForScanFinishing();
144         super.stopScan();
145     }
146
147     private void disableInstallMode() {
148         try {
149             synchronized (installModeSync) {
150                 if (isInInstallMode) {
151                     isInInstallMode = false;
152                     installModeSync.notify();
153                     bridgeHandler.getGateway().setInstallMode(false, 0);
154                 }
155             }
156
157         } catch (Exception ex) {
158             logger.warn("Failed to disable Homematic controller's install mode", ex);
159         }
160     }
161
162     private void setIsInInstallMode() {
163         synchronized (installModeSync) {
164             isInInstallMode = true;
165         }
166     }
167
168     private void waitForInstallModeFinished(int timeout) throws InterruptedException {
169         synchronized (installModeSync) {
170             while (isInInstallMode) {
171                 installModeSync.wait(timeout);
172             }
173         }
174     }
175
176     private void waitForLoadDevicesFinished() throws InterruptedException, ExecutionException {
177         if (loadDevicesFuture != null) {
178             loadDevicesFuture.get();
179         }
180     }
181
182     /**
183      * Waits for the discovery scan to finish and then returns.
184      */
185     public void waitForScanFinishing() {
186         logger.debug("Waiting for finishing Homematic device discovery scan");
187         try {
188             waitForInstallModeFinished(DISCOVER_TIMEOUT_SECONDS * 1000);
189             waitForLoadDevicesFinished();
190         } catch (ExecutionException | InterruptedException ex) {
191             // ignore
192         } catch (Exception ex) {
193             logger.error("Error waiting for device discovery scan: {}", ex.getMessage(), ex);
194         }
195         String gatewayId = bridgeHandler != null && bridgeHandler.getGateway() != null
196                 ? bridgeHandler.getGateway().getId()
197                 : "UNKNOWN";
198         logger.debug("Finished Homematic device discovery scan on gateway '{}'", gatewayId);
199     }
200
201     /**
202      * Starts a thread which loads all Homematic devices connected to the gateway.
203      */
204     public void loadDevices() {
205         if (loadDevicesFuture == null && bridgeHandler.getGateway() != null) {
206             loadDevicesFuture = scheduler.submit(() -> {
207                 try {
208                     final HomematicGateway gateway = bridgeHandler.getGateway();
209                     gateway.loadAllDeviceMetadata();
210                     bridgeHandler.getTypeGenerator().validateFirmwares();
211                 } catch (Throwable ex) {
212                     logger.error("{}", ex.getMessage(), ex);
213                 } finally {
214                     loadDevicesFuture = null;
215                     bridgeHandler.setOfflineStatus();
216                     removeOlderResults(getTimestampOfLastScan());
217                 }
218             });
219         } else {
220             logger.debug("Homematic devices discovery scan in progress");
221         }
222     }
223
224     /**
225      * Removes the Homematic device.
226      */
227     public void deviceRemoved(HmDevice device) {
228         ThingUID thingUID = UidUtils.generateThingUID(device, bridgeHandler.getThing());
229         thingRemoved(thingUID);
230     }
231
232     /**
233      * Generates the DiscoveryResult from a Homematic device.
234      */
235     public void deviceDiscovered(HmDevice device) {
236         ThingUID bridgeUID = bridgeHandler.getThing().getUID();
237         ThingTypeUID typeUid = UidUtils.generateThingTypeUID(device);
238         ThingUID thingUID = new ThingUID(typeUid, bridgeUID, device.getAddress());
239         String label = device.getName() != null ? device.getName() : device.getAddress();
240         long timeToLive = bridgeHandler.getThing().getConfiguration().as(HomematicConfig.class)
241                 .getDiscoveryTimeToLive();
242
243         DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withBridge(bridgeUID).withLabel(label)
244                 .withProperty(Thing.PROPERTY_SERIAL_NUMBER, device.getAddress())
245                 .withRepresentationProperty(Thing.PROPERTY_SERIAL_NUMBER).withTTL(timeToLive).build();
246         thingDiscovered(discoveryResult);
247     }
248 }