2 * Copyright (c) 2010-2023 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.boschshc.internal.discovery;
15 import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.BINDING_ID;
17 import java.time.Duration;
19 import java.util.concurrent.ExecutionException;
20 import java.util.concurrent.TimeoutException;
22 import javax.jmdns.ServiceInfo;
24 import org.eclipse.jdt.annotation.NonNullByDefault;
25 import org.eclipse.jdt.annotation.Nullable;
26 import org.eclipse.jetty.client.HttpClient;
27 import org.eclipse.jetty.client.api.ContentResponse;
28 import org.eclipse.jetty.http.HttpMethod;
29 import org.eclipse.jetty.http.HttpStatus;
30 import org.eclipse.jetty.util.ssl.SslContextFactory;
31 import org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants;
32 import org.openhab.binding.boschshc.internal.devices.bridge.BoschHttpClient;
33 import org.openhab.binding.boschshc.internal.devices.bridge.dto.PublicInformation;
34 import org.openhab.core.config.discovery.DiscoveryResult;
35 import org.openhab.core.config.discovery.DiscoveryResultBuilder;
36 import org.openhab.core.config.discovery.mdns.MDNSDiscoveryParticipant;
37 import org.openhab.core.io.net.http.HttpClientFactory;
38 import org.openhab.core.thing.ThingTypeUID;
39 import org.openhab.core.thing.ThingUID;
40 import org.osgi.service.component.annotations.Activate;
41 import org.osgi.service.component.annotations.Component;
42 import org.osgi.service.component.annotations.Reference;
43 import org.slf4j.Logger;
44 import org.slf4j.LoggerFactory;
46 import com.google.gson.Gson;
49 * The {@link BridgeDiscoveryParticipant} is responsible discovering the
50 * Bosch Smart Home Controller as a Bridge with the mDNS services.
52 * @author Gerd Zanker - Initial contribution
55 @Component(configurationPid = "discovery.boschsmarthomebridge")
56 public class BridgeDiscoveryParticipant implements MDNSDiscoveryParticipant {
57 private static final long TTL_SECONDS = Duration.ofHours(1).toSeconds();
58 public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(BoschSHCBindingConstants.THING_TYPE_SHC);
60 private final Logger logger = LoggerFactory.getLogger(BridgeDiscoveryParticipant.class);
61 private final HttpClient httpClient;
62 private final Gson gson = new Gson();
64 /// SHC Bridge Information, read via public REST API if bridge is detected. Otherwise, strings are empty.
65 private PublicInformation bridgeInformation = new PublicInformation();
68 public BridgeDiscoveryParticipant(@Reference HttpClientFactory httpClientFactory) {
69 // create http client upfront to later request public information from SHC
70 SslContextFactory sslContextFactory = new SslContextFactory.Client.Client(true); // Accept all certificates
71 sslContextFactory.setTrustAll(true);
72 sslContextFactory.setValidateCerts(false);
73 sslContextFactory.setValidatePeerCerts(false);
74 sslContextFactory.setEndpointIdentificationAlgorithm(null);
75 httpClient = httpClientFactory.createHttpClient(BINDING_ID, sslContextFactory);
78 protected BridgeDiscoveryParticipant(HttpClient customHttpClient) {
79 httpClient = customHttpClient;
83 public Set<ThingTypeUID> getSupportedThingTypeUIDs() {
84 return SUPPORTED_THING_TYPES_UIDS;
88 public String getServiceType() {
89 return "_http._tcp.local.";
93 public @Nullable DiscoveryResult createResult(ServiceInfo serviceInfo) {
94 logger.trace("Bridge Discovery started for {}", serviceInfo);
97 final ThingUID uid = getThingUID(serviceInfo);
102 logger.trace("Discovered Bosch Smart Home Controller at {}", bridgeInformation.shcIpAddress);
104 return DiscoveryResultBuilder.create(uid)
105 .withLabel("Bosch Smart Home Controller (" + bridgeInformation.shcIpAddress + ")")
106 .withProperty("ipAddress", bridgeInformation.shcIpAddress)
107 .withProperty("shcGeneration", bridgeInformation.shcGeneration)
108 .withProperty("apiVersions", bridgeInformation.apiVersions).withTTL(TTL_SECONDS).build();
112 public @Nullable ThingUID getThingUID(ServiceInfo serviceInfo) {
113 String ipAddress = discoverBridge(serviceInfo).shcIpAddress;
114 if (!ipAddress.isBlank()) {
115 return new ThingUID(BoschSHCBindingConstants.THING_TYPE_SHC, ipAddress.replace('.', '-'));
120 protected PublicInformation discoverBridge(ServiceInfo serviceInfo) {
121 logger.trace("Discovering serviceInfo {}", serviceInfo);
123 if (serviceInfo.getHostAddresses() != null && serviceInfo.getHostAddresses().length > 0
124 && !serviceInfo.getHostAddresses()[0].isEmpty()) {
125 String address = serviceInfo.getHostAddresses()[0];
126 logger.trace("Discovering InetAddress {}", address);
127 // store all information for later access
128 bridgeInformation = getPublicInformationFromPossibleBridgeAddress(address);
131 return bridgeInformation;
134 protected PublicInformation getPublicInformationFromPossibleBridgeAddress(String ipAddress) {
135 String url = BoschHttpClient.getPublicInformationUrl(ipAddress);
136 logger.trace("Discovering ipAddress {}", url);
139 ContentResponse contentResponse = httpClient.newRequest(url).method(HttpMethod.GET).send();
140 // check HTTP status code
141 if (!HttpStatus.getCode(contentResponse.getStatus()).isSuccess()) {
142 logger.debug("Discovering failed with status code: {}", contentResponse.getStatus());
143 return new PublicInformation();
145 // get content from response
146 String content = contentResponse.getContentAsString();
147 logger.trace("Discovered SHC - public info {}", content);
148 PublicInformation bridgeInfo = gson.fromJson(content, PublicInformation.class);
149 if (bridgeInfo.shcIpAddress != null) {
152 } catch (TimeoutException | ExecutionException e) {
153 logger.debug("Discovering failed with exception {}", e.getMessage());
154 } catch (InterruptedException e) {
155 Thread.currentThread().interrupt();
156 } catch (Exception e) {
157 logger.debug("Discovering failed during http client request {}", e.getMessage());
159 return new PublicInformation();