Skip to content

Commit

Permalink
#14195 Bridge and Device Discovery
Browse files Browse the repository at this point in the history
Bridge discovery is implemented via mDNS, local IP addresses are checked.
If a GET returns the public SHC information,
then this shcIpAddress is reported as a discovered bridge.

Devices are always discovered after successful pairing, but a manual scan is also possible.

Signed-off-by: Gerd Zanker <[email protected]>
  • Loading branch information
GerdZanker committed Jan 9, 2023
1 parent 0304d74 commit 914b314
Show file tree
Hide file tree
Showing 9 changed files with 900 additions and 76 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,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");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -316,11 +316,12 @@ public <TContent> TContent sendRequest(Request request, Class<TContent> response
if (errorResponseHandler != null) {
throw errorResponseHandler.apply(statusCode, textContent);
} else {
throw new ExecutionException(String.format("Request failed with status code %s", statusCode), null);
throw new ExecutionException(String.format("Send request failed with status code %s", statusCode),
null);
}
}

logger.debug("Received response: {} - status: {}", textContent, statusCode);
logger.debug("Send request completed with success: {} - status code: {}", textContent, statusCode);

try {
@Nullable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@

import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Objects;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
Expand All @@ -33,6 +36,7 @@
import org.openhab.binding.boschshc.internal.devices.bridge.dto.DeviceServiceData;
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.LongPollingFailedException;
import org.openhab.binding.boschshc.internal.exceptions.PairingFailedException;
Expand All @@ -45,6 +49,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;
Expand Down Expand Up @@ -93,6 +98,11 @@ public BridgeHandler(Bridge bridge) {
this.longPolling = new LongPolling(this.scheduler, this::handleLongPollResult, this::handleLongPollFailure);
}

@Override
public Collection<Class<? extends ThingHandlerService>> getServices() {
return Collections.singleton(ThingDiscoveryService.class);
}

@Override
public void initialize() {
logger.debug("Initialize {} Version {}", FrameworkUtil.getBundle(getClass()).getSymbolicName(),
Expand Down Expand Up @@ -220,12 +230,8 @@ private void initialAccess(BoschHttpClient httpClient) {
return;
}

// SHC is online and access is possible
// print rooms and devices
boolean thingReachable = true;
thingReachable &= this.getRooms();
thingReachable &= this.getDevices();
if (!thingReachable) {
// SHC is online and access should possible
if (!checkBridgeAccess()) {
this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
"@text/offline.not-reachable");
// restart initial access
Expand All @@ -247,54 +253,114 @@ private void initialAccess(BoschHttpClient httpClient) {
}
}

/**
* Check the bridge access by sending an HTTP request.
* Does not throw any exception in case the request fails.
*/
public boolean checkBridgeAccess() {
@Nullable
BoschHttpClient httpClient = this.httpClient;

if (httpClient == null) {
return false;
}

try {
logger.debug("Sending http request to BoschSHC to check access: {}", httpClient);
String url = httpClient.getBoschSmartHomeUrl("devices");
ContentResponse contentResponse = httpClient.createRequest(url, GET).send();

// check HTTP status code
if (!HttpStatus.getCode(contentResponse.getStatus()).isSuccess()) {
logger.debug("Access check failed with status code: {}", contentResponse.getStatus());
return false;
}

// Access OK
return true;
} catch (TimeoutException | ExecutionException | InterruptedException e) {
logger.warn("Access check failed because of {}!", e.getMessage());
return false;
}
}

/**
* Get a list of connected devices from the Smart-Home Controller
*
* @throws InterruptedException in case bridge is stopped
*/
private boolean getDevices() throws InterruptedException {
public ArrayList<Device> getDevices() throws InterruptedException {
ArrayList<Device> emptyDevices = new ArrayList<Device>();

@Nullable
BoschHttpClient httpClient = this.httpClient;
if (httpClient == null) {
return false;
return emptyDevices;
}

try {
logger.debug("Sending http request to Bosch to request devices: {}", httpClient);
logger.trace("Sending http request to Bosch to request devices: {}", httpClient);
String url = httpClient.getBoschSmartHomeUrl("devices");
ContentResponse contentResponse = httpClient.createRequest(url, GET).send();

// check HTTP status code
if (!HttpStatus.getCode(contentResponse.getStatus()).isSuccess()) {
logger.debug("Request devices failed with status code: {}", contentResponse.getStatus());
return false;
return emptyDevices;
}

String content = contentResponse.getContentAsString();
logger.debug("Request devices completed with success: {} - status code: {}", content,
logger.trace("Request devices completed with success: {} - status code: {}", content,
contentResponse.getStatus());

Type collectionType = new TypeToken<ArrayList<Device>>() {
}.getType();
ArrayList<Device> 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.deviceServiceIds != null) {
for (String s : d.deviceServiceIds) {
logger.info(".... service: {}", s);
}
}
}
}
@Nullable
ArrayList<Device> nullableDevices = gson.fromJson(content, collectionType);
return Objects.requireNonNullElse(nullableDevices, emptyDevices);
} catch (TimeoutException | ExecutionException e) {
logger.warn("Request devices failed because of {}!", e.getMessage());
return false;
logger.debug("Request devices failed because of {}!", e.getMessage());
return emptyDevices;
}
}

/**
* Get a list of rooms from the Smart-Home controller
*
* @throws InterruptedException in case bridge is stopped
*/
public ArrayList<Room> getRooms() throws InterruptedException {
ArrayList<Room> emptyRooms = new ArrayList<>();
@Nullable
BoschHttpClient httpClient = this.httpClient;
if (httpClient != null) {
try {
logger.trace("Sending http request to Bosch to request rooms");
String url = httpClient.getBoschSmartHomeUrl("rooms");
ContentResponse contentResponse = httpClient.createRequest(url, GET).send();

// check HTTP status code
if (!HttpStatus.getCode(contentResponse.getStatus()).isSuccess()) {
logger.debug("Request rooms failed with status code: {}", contentResponse.getStatus());
return emptyRooms;
}

String content = contentResponse.getContentAsString();
logger.trace("Request rooms completed with success: {} - status code: {}", content,
contentResponse.getStatus());

Type collectionType = new TypeToken<ArrayList<Room>>() {
}.getType();

return true;
ArrayList<Room> rooms = gson.fromJson(content, collectionType);
return Objects.requireNonNullElse(rooms, emptyRooms);
} catch (TimeoutException | ExecutionException e) {
logger.debug("Request rooms failed because of {}!", e.getMessage());
return emptyRooms;
}
} else {
return emptyRooms;
}
}

/**
Expand Down Expand Up @@ -415,51 +481,6 @@ private void handleLongPollFailure(Throwable e) {
scheduleInitialAccess(httpClient);
}

/**
* Get a list of rooms from the Smart-Home controller
*
* @throws InterruptedException in case bridge is stopped
*/
private boolean getRooms() throws InterruptedException {
@Nullable
BoschHttpClient httpClient = this.httpClient;
if (httpClient != null) {
try {
logger.debug("Sending http request to Bosch to request rooms");
String url = httpClient.getBoschSmartHomeUrl("rooms");
ContentResponse contentResponse = httpClient.createRequest(url, GET).send();

// check HTTP status code
if (!HttpStatus.getCode(contentResponse.getStatus()).isSuccess()) {
logger.debug("Request rooms failed with status code: {}", contentResponse.getStatus());
return false;
}

String content = contentResponse.getContentAsString();
logger.debug("Request rooms completed with success: {} - status code: {}", content,
contentResponse.getStatus());

Type collectionType = new TypeToken<ArrayList<Room>>() {
}.getType();

ArrayList<Room> rooms = gson.fromJson(content, collectionType);

if (rooms != null) {
for (Room r : rooms) {
logger.info("Found room: {}", r.name);
}
}

return true;
} catch (TimeoutException | ExecutionException e) {
logger.warn("Request rooms failed because of {}!", e.getMessage());
return false;
}
} else {
return false;
}
}

public Device getDeviceInfo(String deviceId)
throws BoschSHCException, InterruptedException, TimeoutException, ExecutionException {
@Nullable
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/**
* Copyright (c) 2010-2023 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.devices.bridge.dto;

/**
* Public Information of the controller.
*
* Currently, only the ipAddress is used for discovery. More fields can be added on demand.
*
* Json example:
* {
* "apiVersions":["1.2","2.1"],
* ...
* "shcIpAddress":"192.168.1.2",
* ...
* }
*
* @author Gerd Zanker - Initial contribution
*/
public class PublicInformation {

public String shcIpAddress;
}
Loading

0 comments on commit 914b314

Please sign in to comment.