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.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;
42 * The {@link HomematicDeviceDiscoveryService} is used to discover devices that are connected to a Homematic gateway.
44 * @author Gerhard Riegler - Initial contribution
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;
51 private @NonNullByDefault({}) HomematicBridgeHandler bridgeHandler;
52 private Future<?> loadDevicesFuture;
53 private volatile boolean isInInstallMode = false;
54 private volatile Object installModeSync = new Object();
56 public HomematicDeviceDiscoveryService() {
57 super(Set.of(new ThingTypeUID(BINDING_ID, "-")), DISCOVER_TIMEOUT_SECONDS, false);
61 public void setThingHandler(@Nullable ThingHandler handler) {
62 if (handler instanceof HomematicBridgeHandler homematicBridgeHandler) {
63 this.bridgeHandler = homematicBridgeHandler;
64 this.bridgeHandler.setDiscoveryService(this);
69 public @Nullable ThingHandler getThingHandler() {
74 * Called on component activation.
77 public void activate() {
82 public void deactivate() {
87 protected void startScan() {
88 logger.debug("Starting Homematic discovery scan");
94 * Will set controller in <i>installMode==true</i>, but only if the bridge
95 * is ONLINE (e.g. not during INITIALIZATION).
97 private void enableInstallMode() {
99 HomematicGateway gateway = bridgeHandler.getGateway();
100 ThingStatus bridgeStatus = null;
102 if (bridgeHandler != null) {
103 Thing bridge = bridgeHandler.getThing();
104 bridgeStatus = bridge.getStatus();
106 if (ThingStatus.ONLINE == bridgeStatus) {
107 gateway.setInstallMode(true, getInstallModeDuration());
109 int remaining = gateway.getInstallMode();
111 setIsInInstallMode();
112 logger.debug("Successfully put controller in install mode. Remaining time: {} seconds", remaining);
114 logger.warn("Controller did not accept requested install mode");
117 logger.debug("Will not attempt to set controller in install mode, because bridge is not ONLINE.");
119 } catch (Exception ex) {
120 logger.warn("Failed to set Homematic controller in install mode", ex);
124 private int getInstallModeDuration() {
125 if (bridgeHandler != null) {
126 return bridgeHandler.getThing().getConfiguration().as(HomematicConfig.class).getInstallModeDuration();
128 return HomematicConfig.DEFAULT_INSTALL_MODE_DURATION;
132 public int getScanTimeout() {
133 return getInstallModeDuration();
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();
143 waitForScanFinishing();
147 private void disableInstallMode() {
149 synchronized (installModeSync) {
150 if (isInInstallMode) {
151 isInInstallMode = false;
152 installModeSync.notify();
153 bridgeHandler.getGateway().setInstallMode(false, 0);
157 } catch (Exception ex) {
158 logger.warn("Failed to disable Homematic controller's install mode", ex);
162 private void setIsInInstallMode() {
163 synchronized (installModeSync) {
164 isInInstallMode = true;
168 private void waitForInstallModeFinished(int timeout) throws InterruptedException {
169 synchronized (installModeSync) {
170 while (isInInstallMode) {
171 installModeSync.wait(timeout);
176 private void waitForLoadDevicesFinished() throws InterruptedException, ExecutionException {
177 if (loadDevicesFuture != null) {
178 loadDevicesFuture.get();
183 * Waits for the discovery scan to finish and then returns.
185 public void waitForScanFinishing() {
186 logger.debug("Waiting for finishing Homematic device discovery scan");
188 waitForInstallModeFinished(DISCOVER_TIMEOUT_SECONDS * 1000);
189 waitForLoadDevicesFinished();
190 } catch (ExecutionException | InterruptedException ex) {
192 } catch (Exception ex) {
193 logger.error("Error waiting for device discovery scan: {}", ex.getMessage(), ex);
195 String gatewayId = bridgeHandler != null && bridgeHandler.getGateway() != null
196 ? bridgeHandler.getGateway().getId()
198 logger.debug("Finished Homematic device discovery scan on gateway '{}'", gatewayId);
202 * Starts a thread which loads all Homematic devices connected to the gateway.
204 public void loadDevices() {
205 if (loadDevicesFuture == null && bridgeHandler.getGateway() != null) {
206 loadDevicesFuture = scheduler.submit(() -> {
208 final HomematicGateway gateway = bridgeHandler.getGateway();
209 gateway.loadAllDeviceMetadata();
210 bridgeHandler.getTypeGenerator().validateFirmwares();
211 } catch (Throwable ex) {
212 logger.error("{}", ex.getMessage(), ex);
214 loadDevicesFuture = null;
215 bridgeHandler.setOfflineStatus();
216 removeOlderResults(getTimestampOfLastScan());
220 logger.debug("Homematic devices discovery scan in progress");
225 * Removes the Homematic device.
227 public void deviceRemoved(HmDevice device) {
228 ThingUID thingUID = UidUtils.generateThingUID(device, bridgeHandler.getThing());
229 thingRemoved(thingUID);
233 * Generates the DiscoveryResult from a Homematic device.
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();
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);