]> git.basschouten.com Git - openhab-addons.git/blob
e0b8ac7fc16499d82e9698625f7b4c6508ff9ff3
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 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.boschshc.internal.discovery;
14
15 import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.BINDING_ID;
16
17 import java.time.Duration;
18 import java.util.Set;
19 import java.util.concurrent.ExecutionException;
20 import java.util.concurrent.TimeoutException;
21
22 import javax.jmdns.ServiceInfo;
23
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;
45
46 import com.google.gson.Gson;
47
48 /**
49  * The {@link BridgeDiscoveryParticipant} is responsible discovering the
50  * Bosch Smart Home Controller as a Bridge with the mDNS services.
51  *
52  * @author Gerd Zanker - Initial contribution
53  */
54 @NonNullByDefault
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);
59
60     private final Logger logger = LoggerFactory.getLogger(BridgeDiscoveryParticipant.class);
61     private final HttpClient httpClient;
62     private final Gson gson = new Gson();
63
64     /// SHC Bridge Information, read via public REST API if bridge is detected. Otherwise, strings are empty.
65     private PublicInformation bridgeInformation = new PublicInformation();
66
67     @Activate
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);
76     }
77
78     protected BridgeDiscoveryParticipant(HttpClient customHttpClient) {
79         httpClient = customHttpClient;
80     }
81
82     @Override
83     public Set<ThingTypeUID> getSupportedThingTypeUIDs() {
84         return SUPPORTED_THING_TYPES_UIDS;
85     }
86
87     @Override
88     public String getServiceType() {
89         return "_http._tcp.local.";
90     }
91
92     @Override
93     public @Nullable DiscoveryResult createResult(ServiceInfo serviceInfo) {
94         logger.trace("Bridge Discovery started for {}", serviceInfo);
95
96         @Nullable
97         final ThingUID uid = getThingUID(serviceInfo);
98         if (uid == null) {
99             return null;
100         }
101
102         logger.trace("Discovered Bosch Smart Home Controller at {}", bridgeInformation.shcIpAddress);
103
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();
109     }
110
111     @Override
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('.', '-'));
116         }
117         return null;
118     }
119
120     protected PublicInformation discoverBridge(ServiceInfo serviceInfo) {
121         logger.trace("Discovering serviceInfo {}", serviceInfo);
122
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);
129         }
130
131         return bridgeInformation;
132     }
133
134     protected PublicInformation getPublicInformationFromPossibleBridgeAddress(String ipAddress) {
135         String url = BoschHttpClient.getPublicInformationUrl(ipAddress);
136         logger.trace("Discovering ipAddress {}", url);
137         try {
138             httpClient.start();
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();
144             }
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) {
150                 return bridgeInfo;
151             }
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());
158         }
159         return new PublicInformation();
160     }
161 }