2 * Copyright (c) 2010-2024 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
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
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.homematic.internal.discovery;
15 import static org.openhab.binding.homematic.internal.HomematicBindingConstants.BINDING_ID;
18 import java.util.concurrent.ExecutionException;
19 import java.util.concurrent.Future;
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;
40 * The {@link HomematicDeviceDiscoveryService} is used to discover devices that are connected to a Homematic gateway.
42 * @author Gerhard Riegler - Initial contribution
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;
50 private Future<?> loadDevicesFuture;
51 private volatile boolean isInInstallMode = false;
52 private volatile Object installModeSync = new Object();
54 public HomematicDeviceDiscoveryService() {
55 super(HomematicBridgeHandler.class, Set.of(new ThingTypeUID(BINDING_ID, "-")), DISCOVER_TIMEOUT_SECONDS, false);
59 public void initialize() {
60 thingHandler.setDiscoveryService(this);
65 protected void startScan() {
66 logger.debug("Starting Homematic discovery scan");
72 * Will set controller in <i>installMode==true</i>, but only if the bridge
73 * is ONLINE (e.g. not during INITIALIZATION).
75 private void enableInstallMode() {
77 HomematicGateway gateway = thingHandler.getGateway();
78 Thing bridge = thingHandler.getThing();
79 ThingStatus bridgeStatus = bridge.getStatus();
81 if (ThingStatus.ONLINE == bridgeStatus) {
82 gateway.setInstallMode(true, getInstallModeDuration());
84 int remaining = gateway.getInstallMode();
87 logger.debug("Successfully put controller in install mode. Remaining time: {} seconds", remaining);
89 logger.warn("Controller did not accept requested install mode");
92 logger.debug("Will not attempt to set controller in install mode, because bridge is not ONLINE.");
94 } catch (Exception ex) {
95 logger.warn("Failed to set Homematic controller in install mode", ex);
99 private int getInstallModeDuration() {
100 return thingHandler.getThing().getConfiguration().as(HomematicConfig.class).getInstallModeDuration();
104 public int getScanTimeout() {
105 return getInstallModeDuration();
109 public synchronized void stopScan() {
110 logger.debug("Stopping Homematic discovery scan");
111 disableInstallMode();
112 thingHandler.getGateway().cancelLoadAllDeviceMetadata();
113 waitForScanFinishing();
117 private void disableInstallMode() {
119 synchronized (installModeSync) {
120 if (isInInstallMode) {
121 isInInstallMode = false;
122 installModeSync.notify();
123 thingHandler.getGateway().setInstallMode(false, 0);
127 } catch (Exception ex) {
128 logger.warn("Failed to disable Homematic controller's install mode", ex);
132 private void setIsInInstallMode() {
133 synchronized (installModeSync) {
134 isInInstallMode = true;
138 private void waitForInstallModeFinished(int timeout) throws InterruptedException {
139 synchronized (installModeSync) {
140 while (isInInstallMode) {
141 installModeSync.wait(timeout);
146 private void waitForLoadDevicesFinished() throws InterruptedException, ExecutionException {
147 if (loadDevicesFuture != null) {
148 loadDevicesFuture.get();
153 * Waits for the discovery scan to finish and then returns.
155 public void waitForScanFinishing() {
156 logger.debug("Waiting for finishing Homematic device discovery scan");
158 waitForInstallModeFinished(DISCOVER_TIMEOUT_SECONDS * 1000);
159 waitForLoadDevicesFinished();
160 } catch (ExecutionException | InterruptedException ex) {
162 } catch (Exception ex) {
163 logger.error("Error waiting for device discovery scan: {}", ex.getMessage(), ex);
165 HomematicBridgeHandler bridgeHandler = thingHandler;
166 String gatewayId = bridgeHandler != null && bridgeHandler.getGateway() != null
167 ? bridgeHandler.getGateway().getId()
169 logger.debug("Finished Homematic device discovery scan on gateway '{}'", gatewayId);
173 * Starts a thread which loads all Homematic devices connected to the gateway.
175 public void loadDevices() {
176 if (loadDevicesFuture == null && thingHandler.getGateway() != null) {
177 loadDevicesFuture = scheduler.submit(() -> {
179 final HomematicGateway gateway = thingHandler.getGateway();
180 gateway.loadAllDeviceMetadata();
181 thingHandler.getTypeGenerator().validateFirmwares();
182 } catch (Throwable ex) {
183 logger.error("{}", ex.getMessage(), ex);
185 loadDevicesFuture = null;
186 thingHandler.setOfflineStatus();
187 removeOlderResults(getTimestampOfLastScan());
191 logger.debug("Homematic devices discovery scan in progress");
196 * Removes the Homematic device.
198 public void deviceRemoved(HmDevice device) {
199 ThingUID thingUID = UidUtils.generateThingUID(device, thingHandler.getThing());
200 thingRemoved(thingUID);
204 * Generates the DiscoveryResult from a Homematic device.
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();
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);