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