]> git.basschouten.com Git - openhab-addons.git/blob
acc172422f0b0b7b4b63e4c71d2aac2738e8cee6
[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
127         } catch (Exception ex) {
128             logger.warn("Failed to disable Homematic controller's install mode", ex);
129         }
130     }
131
132     private void setIsInInstallMode() {
133         synchronized (installModeSync) {
134             isInInstallMode = true;
135         }
136     }
137
138     private void waitForInstallModeFinished(int timeout) throws InterruptedException {
139         synchronized (installModeSync) {
140             while (isInInstallMode) {
141                 installModeSync.wait(timeout);
142             }
143         }
144     }
145
146     private void waitForLoadDevicesFinished() throws InterruptedException, ExecutionException {
147         if (loadDevicesFuture != null) {
148             loadDevicesFuture.get();
149         }
150     }
151
152     /**
153      * Waits for the discovery scan to finish and then returns.
154      */
155     public void waitForScanFinishing() {
156         logger.debug("Waiting for finishing Homematic device discovery scan");
157         try {
158             waitForInstallModeFinished(DISCOVER_TIMEOUT_SECONDS * 1000);
159             waitForLoadDevicesFinished();
160         } catch (ExecutionException | InterruptedException ex) {
161             // ignore
162         } catch (Exception ex) {
163             logger.error("Error waiting for device discovery scan: {}", ex.getMessage(), ex);
164         }
165         HomematicBridgeHandler bridgeHandler = thingHandler;
166         String gatewayId = bridgeHandler != null && bridgeHandler.getGateway() != null
167                 ? bridgeHandler.getGateway().getId()
168                 : "UNKNOWN";
169         logger.debug("Finished Homematic device discovery scan on gateway '{}'", gatewayId);
170     }
171
172     /**
173      * Starts a thread which loads all Homematic devices connected to the gateway.
174      */
175     public void loadDevices() {
176         if (loadDevicesFuture == null && thingHandler.getGateway() != null) {
177             loadDevicesFuture = scheduler.submit(() -> {
178                 try {
179                     final HomematicGateway gateway = thingHandler.getGateway();
180                     gateway.loadAllDeviceMetadata();
181                     thingHandler.getTypeGenerator().validateFirmwares();
182                 } catch (Throwable ex) {
183                     logger.error("{}", ex.getMessage(), ex);
184                 } finally {
185                     loadDevicesFuture = null;
186                     thingHandler.setOfflineStatus();
187                     removeOlderResults(getTimestampOfLastScan());
188                 }
189             });
190         } else {
191             logger.debug("Homematic devices discovery scan in progress");
192         }
193     }
194
195     /**
196      * Removes the Homematic device.
197      */
198     public void deviceRemoved(HmDevice device) {
199         ThingUID thingUID = UidUtils.generateThingUID(device, thingHandler.getThing());
200         thingRemoved(thingUID);
201     }
202
203     /**
204      * Generates the DiscoveryResult from a Homematic device.
205      */
206     public void deviceDiscovered(HmDevice device) {
207         ThingUID bridgeUID = thingHandler.getThing().getUID();
208         ThingTypeUID typeUid = UidUtils.generateThingTypeUID(device);
209         ThingUID thingUID = new ThingUID(typeUid, bridgeUID, device.getAddress());
210         String label = device.getName() != null ? device.getName() : device.getAddress();
211         long timeToLive = thingHandler.getThing().getConfiguration().as(HomematicConfig.class).getDiscoveryTimeToLive();
212
213         DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withBridge(bridgeUID).withLabel(label)
214                 .withProperty(Thing.PROPERTY_SERIAL_NUMBER, device.getAddress())
215                 .withRepresentationProperty(Thing.PROPERTY_SERIAL_NUMBER).withTTL(timeToLive).build();
216         thingDiscovered(discoveryResult);
217     }
218 }