From aa4fefd510823665105a4d5a7e71e97642a15454 Mon Sep 17 00:00:00 2001 From: Gerd Zanker Date: Sun, 14 Feb 2021 18:59:02 +0100 Subject: [PATCH] issue #27 initial version of device discovery Signed-off-by: Gerd Zanker --- .../devices/BoschSHCBindingConstants.java | 2 +- .../devices/bridge/BoschHttpClient.java | 11 +- .../internal/devices/bridge/BoschSslUtil.java | 10 +- .../devices/bridge/BridgeHandler.java | 73 ++++--- .../devices/bridge/dto/PublicInformation.java | 1 - .../discovery/BridgeDiscoveryParticipant.java | 44 ++-- .../discovery/ThingDiscoveryService.java | 191 ++++++++++++++++++ .../exceptions/KeystoreException.java | 35 ++++ .../devices/bridge/BoschHttpClientTest.java | 8 +- .../devices/bridge/BoschSslUtilTest.java | 4 +- 10 files changed, 318 insertions(+), 61 deletions(-) create mode 100644 bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/discovery/ThingDiscoveryService.java create mode 100644 bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/exceptions/KeystoreException.java diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/BoschSHCBindingConstants.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/BoschSHCBindingConstants.java index 34f0947b11e70..6101166a29de6 100644 --- a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/BoschSHCBindingConstants.java +++ b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/BoschSHCBindingConstants.java @@ -26,7 +26,7 @@ @NonNullByDefault public class BoschSHCBindingConstants { - private static final String BINDING_ID = "boschshc"; + public static final String BINDING_ID = "boschshc"; // List of all Thing Type UIDs public static final ThingTypeUID THING_TYPE_SHC = new ThingTypeUID(BINDING_ID, "shc"); diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/BoschHttpClient.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/BoschHttpClient.java index f05fe5cfefc5f..37e4044b5f7b7 100644 --- a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/BoschHttpClient.java +++ b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/BoschHttpClient.java @@ -56,12 +56,10 @@ public class BoschHttpClient extends HttpClient { private final Logger logger = LoggerFactory.getLogger(BoschHttpClient.class); private final String ipAddress; - private final String systemPassword; - public BoschHttpClient(String ipAddress, String systemPassword, SslContextFactory sslContextFactory) { + public BoschHttpClient(String ipAddress, SslContextFactory sslContextFactory) { super(sslContextFactory); this.ipAddress = ipAddress; - this.systemPassword = systemPassword; } /** @@ -181,7 +179,7 @@ public boolean isAccessPossible() throws InterruptedException { * @return true if pairing was successful, otherwise false * @throws InterruptedException in case of an interrupt */ - public boolean doPairing() throws InterruptedException { + public boolean doPairing(String systemPassword) throws InterruptedException { logger.trace("Starting pairing openHAB Client with Bosch Smart Home Controller!"); logger.trace("Please press the Bosch Smart Home Controller button until LED starts blinking"); @@ -201,7 +199,7 @@ public boolean doPairing() throws InterruptedException { String url = this.getPairingUrl(); Request request = this.createRequest(url, HttpMethod.POST, items).header("Systempassword", - Base64.getEncoder().encodeToString(this.systemPassword.getBytes(StandardCharsets.UTF_8))); + Base64.getEncoder().encodeToString(systemPassword.getBytes(StandardCharsets.UTF_8))); contentResponse = request.send(); @@ -290,7 +288,8 @@ public TContent sendRequest(Request request, Class response if (errorResponseHandler != null) { throw errorResponseHandler.apply(statusCode, textContent); } else { - throw new ExecutionException(String.format("Send request failed with status code %s", statusCode), null); + throw new ExecutionException(String.format("Send request failed with status code %s", statusCode), + null); } } diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/BoschSslUtil.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/BoschSslUtil.java index f23918f69278f..445f398cfbf6f 100644 --- a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/BoschSslUtil.java +++ b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/BoschSslUtil.java @@ -43,7 +43,7 @@ import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jetty.util.ssl.SslContextFactory; -import org.openhab.binding.boschshc.internal.exceptions.PairingFailedException; +import org.openhab.binding.boschshc.internal.exceptions.KeystoreException; import org.openhab.core.OpenHAB; import org.openhab.core.id.InstanceUUID; import org.slf4j.Logger; @@ -104,7 +104,7 @@ public String getKeystorePath() { return Paths.get(OpenHAB.getUserDataFolder(), "etc", getBoschShcServerId() + ".jks").toString(); } - public SslContextFactory getSslContextFactory() throws PairingFailedException { + public SslContextFactory getSslContextFactory() throws KeystoreException { // Instantiate and configure the SslContextFactory SslContextFactory sslContextFactory = new SslContextFactory.Client.Client(true); // Accept all certificates @@ -125,7 +125,7 @@ public SslContextFactory getSslContextFactory() throws PairingFailedException { return sslContextFactory; } - public KeyStore getKeyStoreAndCreateIfNecessary() throws PairingFailedException { + public KeyStore getKeyStoreAndCreateIfNecessary() throws KeystoreException { try { File file = new File(keystorePath); if (!file.exists()) { @@ -143,8 +143,8 @@ public KeyStore getKeyStoreAndCreateIfNecessary() throws PairingFailedException } } catch (OperatorCreationException | GeneralSecurityException | IOException e) { logger.debug("Exception during keystore creation {}", e.getMessage()); - throw new PairingFailedException("Can not create or load keystore file: " + keystorePath - + ". Check path, write access and JKS content.", e); + throw new KeystoreException("Can not create or load keystore file: " + keystorePath + + ". Check path, write access and JavaKeyStore content.", e); } } diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/BridgeHandler.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/BridgeHandler.java index f13ae38ee55b9..b1f7f9bf9015c 100644 --- a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/BridgeHandler.java +++ b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/BridgeHandler.java @@ -16,6 +16,8 @@ import java.lang.reflect.Type; import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; import java.util.concurrent.ExecutionException; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; @@ -33,9 +35,10 @@ import org.openhab.binding.boschshc.internal.devices.bridge.dto.DeviceStatusUpdate; import org.openhab.binding.boschshc.internal.devices.bridge.dto.LongPollResult; import org.openhab.binding.boschshc.internal.devices.bridge.dto.Room; +import org.openhab.binding.boschshc.internal.discovery.ThingDiscoveryService; import org.openhab.binding.boschshc.internal.exceptions.BoschSHCException; +import org.openhab.binding.boschshc.internal.exceptions.KeystoreException; import org.openhab.binding.boschshc.internal.exceptions.LongPollingFailedException; -import org.openhab.binding.boschshc.internal.exceptions.PairingFailedException; import org.openhab.binding.boschshc.internal.services.dto.BoschSHCServiceState; import org.openhab.binding.boschshc.internal.services.dto.JsonRestExceptionResponse; import org.openhab.core.thing.Bridge; @@ -45,6 +48,7 @@ import org.openhab.core.thing.ThingStatusDetail; import org.openhab.core.thing.binding.BaseBridgeHandler; import org.openhab.core.thing.binding.ThingHandler; +import org.openhab.core.thing.binding.ThingHandlerService; import org.openhab.core.types.Command; import org.osgi.framework.FrameworkUtil; import org.slf4j.Logger; @@ -75,16 +79,32 @@ public class BridgeHandler extends BaseBridgeHandler { */ private final LongPolling longPolling; + /** + * HTTP Client for Bosch SHC rest calls and long polling. + */ private @Nullable BoschHttpClient httpClient; + /** + * Future result to handle successful or failed pairing between bridge and SHC. + */ private @Nullable ScheduledFuture scheduledPairing; + /** + * Bosch SHC system password + */ + private String password; + public BridgeHandler(Bridge bridge) { super(bridge); - + this.password = ""; this.longPolling = new LongPolling(this.scheduler, this::handleLongPollResult, this::handleLongPollFailure); } + @Override + public Collection> getServices() { + return Collections.singleton(ThingDiscoveryService.class); + } + @Override public void initialize() { logger.debug("Initialize {} Version {}", FrameworkUtil.getBundle(getClass()).getSymbolicName(), @@ -100,8 +120,8 @@ public void initialize() { return; } - String password = config.password.trim(); - if (password.isEmpty()) { + this.password = config.password.trim(); + if (this.password.isEmpty()) { updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "@text/offline.conf-error-empty-password"); return; @@ -111,14 +131,14 @@ public void initialize() { try { // prepare SSL key and certificates factory = new BoschSslUtil(ipAddress).getSslContextFactory(); - } catch (PairingFailedException e) { + } catch (KeystoreException e) { this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR, "@text/offline.conf-error-ssl"); return; } // Instantiate HttpClient with the SslContextFactory - BoschHttpClient httpClient = this.httpClient = new BoschHttpClient(ipAddress, password, factory); + BoschHttpClient httpClient = this.httpClient = new BoschHttpClient(ipAddress, factory); // Start http client try { @@ -203,7 +223,7 @@ private void initialAccess(BoschHttpClient httpClient) { // update status description to show pairing test this.updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.UNKNOWN.NONE, "@text/offline.conf-error-pairing"); - if (!httpClient.doPairing()) { + if (!httpClient.doPairing(this.password)) { this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR, "@text/offline.conf-error-pairing"); } @@ -216,7 +236,7 @@ private void initialAccess(BoschHttpClient httpClient) { // print rooms and devices boolean thingReachable = true; thingReachable &= this.getRooms(); - thingReachable &= this.getDevices(); + thingReachable &= (this.getDevices() != null); if (!thingReachable) { this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, "@text/offline.not-reachable"); @@ -232,7 +252,6 @@ private void initialAccess(BoschHttpClient httpClient) { } catch (LongPollingFailedException e) { this.handleLongPollFailure(e); } - } catch (InterruptedException e) { this.updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.UNKNOWN.NONE, "@text/offline.interrupted"); Thread.currentThread().interrupt(); @@ -244,11 +263,11 @@ private void initialAccess(BoschHttpClient httpClient) { * * @throws InterruptedException in case bridge is stopped */ - private boolean getDevices() throws InterruptedException { + public @Nullable ArrayList getDevices() throws InterruptedException { @Nullable BoschHttpClient httpClient = this.httpClient; if (httpClient == null) { - return false; + return null; } try { @@ -259,7 +278,7 @@ private boolean getDevices() throws InterruptedException { // check HTTP status code if (!HttpStatus.getCode(contentResponse.getStatus()).isSuccess()) { logger.debug("Request devices failed with status code: {}", contentResponse.getStatus()); - return false; + return null; } String content = contentResponse.getContentAsString(); @@ -270,23 +289,23 @@ private boolean getDevices() throws InterruptedException { }.getType(); ArrayList devices = gson.fromJson(content, collectionType); - if (devices != null) { - for (Device d : devices) { - // Write found devices into openhab.log until we have implemented auto discovery - logger.info("Found device: name={} id={}", d.name, d.id); - if (d.deviceSerivceIDs != null) { - for (String s : d.deviceSerivceIDs) { - logger.info(".... service: {}", s); - } - } - } - } + // if (devices != null) { + // for (Device d : devices) { + // // Write found devices into openhab.log until we have implemented auto discovery + // logger.info("Found device: name={} id={}", d.name, d.id); + // if (d.deviceSerivceIDs != null) { + // for (String s : d.deviceSerivceIDs) { + // logger.info(".... service: {}", s); + // } + // } + // } + // } + + return devices; } catch (TimeoutException | ExecutionException e) { logger.warn("Request devices failed because of {}!", e.getMessage()); - return false; + return null; } - - return true; } /** @@ -386,7 +405,7 @@ private boolean getRooms() throws InterruptedException { if (rooms != null) { for (Room r : rooms) { - logger.info("Found room: {}", r.name); + logger.info("Found room: {} (ID={})", r.name, r.id); } } diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/dto/PublicInformation.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/dto/PublicInformation.java index 2f00d925f0edb..2b27dd5f15220 100644 --- a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/dto/PublicInformation.java +++ b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/dto/PublicInformation.java @@ -12,7 +12,6 @@ */ package org.openhab.binding.boschshc.internal.devices.bridge.dto; - /** * Public Information of the controller. * diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/discovery/BridgeDiscoveryParticipant.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/discovery/BridgeDiscoveryParticipant.java index 20fa2cedd131f..39e39483b21ca 100644 --- a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/discovery/BridgeDiscoveryParticipant.java +++ b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/discovery/BridgeDiscoveryParticipant.java @@ -21,12 +21,12 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.eclipse.jetty.client.HttpClient; import org.eclipse.jetty.client.api.ContentResponse; import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.util.ssl.SslContextFactory; import org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants; +import org.openhab.binding.boschshc.internal.devices.bridge.BoschHttpClient; import org.openhab.binding.boschshc.internal.devices.bridge.dto.PublicInformation; import org.openhab.core.config.discovery.DiscoveryResult; import org.openhab.core.config.discovery.DiscoveryResultBuilder; @@ -53,16 +53,9 @@ public class BridgeDiscoveryParticipant implements MDNSDiscoveryParticipant { private final Gson gson = new Gson(); private final Logger logger = LoggerFactory.getLogger(BridgeDiscoveryParticipant.class); - private final HttpClient httpClient; + private String cachedIpAddress = ""; public BridgeDiscoveryParticipant() { - // create http client upfront to later get public information from SHC - SslContextFactory sslContextFactory = new SslContextFactory.Client.Client(true); // Accept all certificates - sslContextFactory.setTrustAll(true); - sslContextFactory.setValidateCerts(false); - sslContextFactory.setValidatePeerCerts(false); - sslContextFactory.setEndpointIdentificationAlgorithm(null); - httpClient = new HttpClient(sslContextFactory); } @Override @@ -118,12 +111,29 @@ public String getServiceType() { } private @Nullable String getBridgeAddress(String ipAddress) { - - String url = String.format("https://%s:8446/smarthome/public/information", ipAddress); - logger.trace("Discovering ipAddress {}", url); + logger.trace("Discovering ipAddress {}", ipAddress); try { + // return a cached IP address to avoid many REST calls + // the BridgeDiscovery is executed every 5s and this will be too many request for the SHC + if (!cachedIpAddress.isEmpty()) { + logger.debug("Discovered SHC - returning cached IP address {} from first successful discovery", + cachedIpAddress); + return cachedIpAddress; + } + // prepare ssl content and http client + SslContextFactory sslContextFactory = new SslContextFactory.Client.Client(true); // Accept all certificates + sslContextFactory.setTrustAll(true); + sslContextFactory.setValidateCerts(false); + sslContextFactory.setValidatePeerCerts(false); + sslContextFactory.setEndpointIdentificationAlgorithm(null); + + BoschHttpClient httpClient = new BoschHttpClient(ipAddress, sslContextFactory); httpClient.start(); - ContentResponse contentResponse = httpClient.newRequest(url).method(HttpMethod.GET).send(); + // do rest to get public information including actual IP address of SHC + ContentResponse contentResponse = httpClient.newRequest(httpClient.getPublicInformationUrl()) + .method(HttpMethod.GET).send(); + httpClient.stop(); + // check HTTP status code if (!HttpStatus.getCode(contentResponse.getStatus()).isSuccess()) { logger.debug("Discovering failed with status code: {}", contentResponse.getStatus()); @@ -135,15 +145,19 @@ public String getServiceType() { @Nullable PublicInformation versionInfo = gson.fromJson(content, PublicInformation.class); if (versionInfo != null) { + cachedIpAddress = versionInfo.shcIpAddress; return versionInfo.shcIpAddress; } + return null; } catch (InterruptedException | TimeoutException | ExecutionException e) { - logger.trace("Discovering failed with exception", e); + logger.debug("Discovering failed with exception {}", e.getMessage()); + logger.trace("Discovering failed with exception ", e); return null; } catch (Exception e) { - logger.trace("Discovering failed in http client start", e); + logger.debug("Discovering failed in httpClient start {}", e.getMessage()); + logger.trace("Discovering failed in httpClient start", e); return null; } } diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/discovery/ThingDiscoveryService.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/discovery/ThingDiscoveryService.java new file mode 100644 index 0000000000000..5c70c12ce4134 --- /dev/null +++ b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/discovery/ThingDiscoveryService.java @@ -0,0 +1,191 @@ +/** + * Copyright (c) 2010-2021 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.boschshc.internal.discovery; + +import java.util.*; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants; +import org.openhab.binding.boschshc.internal.devices.bridge.BridgeHandler; +import org.openhab.binding.boschshc.internal.devices.bridge.dto.Device; +import org.openhab.core.config.discovery.AbstractDiscoveryService; +import org.openhab.core.config.discovery.DiscoveryResult; +import org.openhab.core.config.discovery.DiscoveryResultBuilder; +import org.openhab.core.config.discovery.DiscoveryService; +import org.openhab.core.thing.ThingTypeUID; +import org.openhab.core.thing.ThingUID; +import org.openhab.core.thing.binding.ThingHandler; +import org.openhab.core.thing.binding.ThingHandlerService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link ThingDiscoveryService} is responsible discover Bosch Smart Home things. + * + * @author Gerd Zanker - Initial contribution + */ +@NonNullByDefault +public class ThingDiscoveryService extends AbstractDiscoveryService implements DiscoveryService, ThingHandlerService { + private static final int SEARCH_TIME = 10; + + private final Logger logger = LoggerFactory.getLogger(ThingDiscoveryService.class); + private @Nullable BridgeHandler bridgeHandler; + + public static final Set SUPPORTED_THING_TYPES = Set.of( + BoschSHCBindingConstants.THING_TYPE_INWALL_SWITCH, BoschSHCBindingConstants.THING_TYPE_TWINGUARD, + BoschSHCBindingConstants.THING_TYPE_WINDOW_CONTACT, BoschSHCBindingConstants.THING_TYPE_MOTION_DETECTOR, + BoschSHCBindingConstants.THING_TYPE_SHUTTER_CONTROL, BoschSHCBindingConstants.THING_TYPE_THERMOSTAT, + BoschSHCBindingConstants.THING_TYPE_CLIMATE_CONTROL, BoschSHCBindingConstants.THING_TYPE_WALL_THERMOSTAT); + + // @formatter:off + private static final Map DEVICEMODEL_TO_THING_MAP = Map.ofEntries( + new AbstractMap.SimpleEntry<>("BBL", BoschSHCBindingConstants.THING_TYPE_SHUTTER_CONTROL.getId()), + new AbstractMap.SimpleEntry<>("TWINGUARD", BoschSHCBindingConstants.THING_TYPE_TWINGUARD.getId()), + new AbstractMap.SimpleEntry<>("PSM", BoschSHCBindingConstants.THING_TYPE_INWALL_SWITCH.getId()), + new AbstractMap.SimpleEntry<>("PLUG_COMPACT", BoschSHCBindingConstants.THING_TYPE_INWALL_SWITCH.getId()), + new AbstractMap.SimpleEntry<>("ROOM_CLIMATE_CONTROL", BoschSHCBindingConstants.THING_TYPE_CLIMATE_CONTROL.getId()), + new AbstractMap.SimpleEntry<>("BWTH", BoschSHCBindingConstants.THING_TYPE_WALL_THERMOSTAT.getId()) + +// FIXME: map all supported openhab Binding Things to the unknown Bosch SHC names +// new AbstractMap.SimpleEntry<>("???", BoschSHCBindingConstants.THING_TYPE_WINDOW_CONTACT.getId()) +// new AbstractMap.SimpleEntry<>("???", BoschSHCBindingConstants.THING_TYPE_MOTION_DETECTOR.getId()) +// new AbstractMap.SimpleEntry<>("???", BoschSHCBindingConstants.THING_TYPE_THERMOSTAT.getId()) + + +// FUTURE IMPLEMENTATION: map all Bosch SHC Names to currently not implemented Openhab Binding Things + +// new AbstractMap.SimpleEntry<>("CAMERA_EYES", ?), // Eyes 360 outdoor camera +// new AbstractMap.SimpleEntry<>("SD", ?), // smoke detector + +// new AbstractMap.SimpleEntry<>("VENTILATION_SERVICE", ?), +// new AbstractMap.SimpleEntry<>("SMOKE_DETECTION_SYSTEM", ?), +// new AbstractMap.SimpleEntry<>("PRESENCE_SIMULATION_SERVICE", ?), + +// new AbstractMap.SimpleEntry<>("HUE_BRIDGE", ?) +// new AbstractMap.SimpleEntry<>("HUE_BRIDGE_MANAGER", ?) +// new AbstractMap.SimpleEntry<>("HUE_LIGHT", ?) +// new AbstractMap.SimpleEntry<>("HUE_LIGHT_ROOM_CONTROL", ?) + + ); + // @formatter:on + + public ThingDiscoveryService() { + super(SUPPORTED_THING_TYPES, SEARCH_TIME); + } + + @Override + public void activate() { + logger.trace("activate"); + // TODO: Preparation for ongoing discovery while bridge is online + // final BridgeHandler handler = this.bridgeHandler; + // if (handler != null) { + // handler.registerDiscoveryListener(this); + // } + } + + @Override + public void deactivate() { + logger.trace("DEactivate"); + // TODO: Preparation for ongoing discovery while bridge is online + // final BridgeHandler handler = this.bridgeHandler; + // if (handler != null) { + // removeOlderResults(new Date().getTime(), handler.getThing().getUID()); + // handler.unregisterDiscoveryListener(); + // } else { + // logger.debug("DEactivate called, but can't removeOlderResults because no UID available!!!"); + // } + } + + @Override + protected void startScan() { + // use shcBridgeHandler to getDevices() + try { + final BridgeHandler bridgeHandler; + if (this.bridgeHandler != null) { + bridgeHandler = this.bridgeHandler; + @Nullable + ArrayList devices = bridgeHandler.getDevices(); + if (devices != null) { + for (Device device : devices) { + addDevice(device); + } + } + } + } catch (InterruptedException e) { + logger.trace("scan was interrupted"); + } + } + + private void addDevice(Device device) { + logger.debug("Discovering device {}", device.name); + logger.trace(" Details for device {}:\n" + "- id: {}\n" + "- manufacturer: {}\n" + "- roomId: {}\n" + + "- deviceModel: {}\n" + "- serial: {}\n" + "- name: {}\n" + "- status: {}\n" + "- childDeviceIds: {}", + device.name, device.id, device.manufacturer, device.roomId, device.deviceModel, device.serial, + device.name, device.status, device.childDeviceIds); + + final BridgeHandler bridgeHandler; + if (this.bridgeHandler != null) { + bridgeHandler = this.bridgeHandler; + ThingUID bridgeUID = bridgeHandler.getThing().getUID(); + @Nullable + ThingTypeUID thingTypeUID = getThingTypeUID(device); + if (thingTypeUID == null) + return; + + ThingUID thingUID = getThingUID(device, bridgeUID, thingTypeUID); + + DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withThingType(thingTypeUID) + .withProperty("id", device.id).withBridge(bridgeUID) + // TODO add openhab "Location" based on the SHC "rooms", + // look necessary via the SHC id for rooms like "hz_2" + // .withRepresentationProperty(UNIQUE_ID) + .withLabel(device.name).build(); + + logger.debug("Discovered device '{}' with thingUID={}, thingTypeUID={}, id={}", device.name, thingUID, + thingTypeUID, device.id); + + thingDiscovered(discoveryResult); + } + } + + private ThingUID getThingUID(Device device, ThingUID bridgeUid, ThingTypeUID thingTypeUID) { + logger.trace("got getThingUID {} for device {}", device.id, device); + return new ThingUID(thingTypeUID, bridgeUid, device.id.replace(':', '_')); + } + + private @Nullable ThingTypeUID getThingTypeUID(Device device) { + String thingTypeId = DEVICEMODEL_TO_THING_MAP.get(device.deviceModel); + if (thingTypeId != null) { + logger.trace("got thingTypeID {} for deviceModel {}", thingTypeId, device.deviceModel); + return new ThingTypeUID(BoschSHCBindingConstants.BINDING_ID, thingTypeId); + } + logger.info( + "The Bosch Smart Home binding doesn't support this deviceModel {}. Please request support for it in github.", + device.deviceModel); + return null; + } + + @Override + public void setThingHandler(@Nullable ThingHandler handler) { + if (handler instanceof BridgeHandler) { + logger.trace("Set bridge handler {}", handler); + bridgeHandler = (BridgeHandler) handler; + } + } + + @Override + public @Nullable ThingHandler getThingHandler() { + return bridgeHandler; + } +} diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/exceptions/KeystoreException.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/exceptions/KeystoreException.java new file mode 100644 index 0000000000000..5e6faf29b6f97 --- /dev/null +++ b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/exceptions/KeystoreException.java @@ -0,0 +1,35 @@ +/** + * Copyright (c) 2010-2021 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.boschshc.internal.exceptions; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * Thrown if the keystore for SSL keys & certificate cant be created or accessed. + * + * @author Gerd Zanker - Initial contribution + */ +@SuppressWarnings("serial") +@NonNullByDefault +public class KeystoreException extends BoschSHCException { + public KeystoreException() { + } + + public KeystoreException(String message) { + super(message); + } + + public KeystoreException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/devices/bridge/BoschHttpClientTest.java b/bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/devices/bridge/BoschHttpClientTest.java index 4972d1f715d86..4d2d71ecee4fc 100644 --- a/bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/devices/bridge/BoschHttpClientTest.java +++ b/bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/devices/bridge/BoschHttpClientTest.java @@ -23,7 +23,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.openhab.binding.boschshc.internal.devices.bridge.dto.SubscribeResult; -import org.openhab.binding.boschshc.internal.exceptions.PairingFailedException; +import org.openhab.binding.boschshc.internal.exceptions.KeystoreException; /** * Tests cases for {@link BoschHttpClient}. @@ -42,9 +42,9 @@ static void beforeAll() { } @BeforeEach - void beforeEach() throws PairingFailedException { + void beforeEach() throws KeystoreException { SslContextFactory sslFactory = new BoschSslUtil("127.0.0.1").getSslContextFactory(); - httpClient = new BoschHttpClient("127.0.0.1", "dummy", sslFactory); + httpClient = new BoschHttpClient("127.0.0.1", sslFactory); assertNotNull(httpClient); } @@ -87,7 +87,7 @@ void isOnline() throws InterruptedException { @Test void doPairing() throws InterruptedException { - assertFalse(httpClient.doPairing()); + assertFalse(httpClient.doPairing("unknownPassword")); } @Test diff --git a/bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/devices/bridge/BoschSslUtilTest.java b/bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/devices/bridge/BoschSslUtilTest.java index 3da600fc8db36..64cfafa3e4165 100644 --- a/bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/devices/bridge/BoschSslUtilTest.java +++ b/bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/devices/bridge/BoschSslUtilTest.java @@ -22,7 +22,7 @@ import org.eclipse.jetty.util.ssl.SslContextFactory; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -import org.openhab.binding.boschshc.internal.exceptions.PairingFailedException; +import org.openhab.binding.boschshc.internal.exceptions.KeystoreException; /** * Tests cases for {@link BoschSslUtil}. @@ -72,7 +72,7 @@ void getKeystorePath() { * Test if the keyStore can be created if it doesn't exist. */ @Test - void keyStoreAndFactory() throws PairingFailedException { + void keyStoreAndFactory() throws KeystoreException { BoschSslUtil sslUtil1 = new BoschSslUtil("127.0.0.1"); // remote old, existing jks