## Build
-To only build the Bosch SHC binding code execute
+To only build the Bosch Smart Home binding code execute
mvn -pl :org.openhab.binding.boschshc install
It should also be reloaded automatically when the jar changed.
-To reload the bundle manually you need to execute:
+To reload the bundle manually you need to execute in the openhab console:
- bundle:update "openHAB Add-ons :: Bundles :: BoschSHC Binding"
+ bundle:update "openHAB Add-ons :: Bundles :: Bosch Smart Home Binding"
or get the ID and update the bundle using the ID:
bundle:list
- -> Get ID for "openHAB Add-ons :: Bundles :: BoschSHC Binding"
+ -> Get ID for "openHAB Add-ons :: Bundles :: Bosch Smart Home Binding"
bundle:update <ID>
## Debugging
-To get debug output and traces of the Bosch SHC binding code
+To get debug output and traces of the Bosch Smart Home binding code
add the following lines into ``userdata/etc/log4j2.xml`` Loggers XML section.
<!-- Bosch SHC for debugging -->
<Logger level="TRACE" name="org.openhab.binding.boschshc"/>
+or use the openhab console to change the log level
+
+ log:set TRACE org.openhab.binding.boschshc
+
## Pairing and Certificates
-We need secured and paired connection from the openHAB binding instance to the Bosch SHC.
+We need secured and paired connection from the openHAB binding instance to the Bosch Smart Home Controller (SHC).
Read more about the pairing process in [register a new client to the bosch smart home controller](https://github.com/BoschSmartHome/bosch-shc-api-docs/tree/master/postman#register-a-new-client-to-the-bosch-smart-home-controller)
-# BoschSHC Binding
+# Bosch Smart Home Binding
-Binding for the Bosch Smart Home Controller.
+Binding for the Bosch Smart Home.
-- [BoschSHC Binding](#boschshc-binding)
+- [Bosch Smart Home Binding](#bosch-smart-home-binding)
- [Supported Things](#supported-things)
- [Bosch In-Wall switches & Bosch Smart Plugs](#bosch-in-wall-switches--bosch-smart-plugs)
- [Bosch TwinGuard smoke detector](#bosch-twinguard-smoke-detector)
- [Bosch Climate Control](#bosch-climate-control)
- [Limitations](#limitations)
- [Discovery](#discovery)
- - [Binding Configuration](#binding-configuration)
+ - [Bridge Configuration](#bridge-configuration)
- [Getting the device IDs](#getting-the-device-ids)
- [Thing Configuration](#thing-configuration)
- [Item Configuration](#item-configuration)
The IP address of the controller is visible in the Bosch Smart Home Mobile App (More -> System -> Smart Home Controller) or in your network router UI.
The system password is set by you during your initial registration steps in the _Bosch Smart Home App_.
-A keystore file with a self signed certificate is created automatically.
-This certificate is used for pairing between the Bridge and the Bosch SHC.
+A keystore file with a self-signed certificate is created automatically.
+This certificate is used for pairing between the Bridge and the Bosch Smart Home Controller.
*Press and hold the Bosch Smart Home Controller Bridge button until the LED starts blinking after you save your settings for pairing*.
<artifactId>org.openhab.binding.boschshc</artifactId>
- <name>openHAB Add-ons :: Bundles :: BoschSHC Binding</name>
+ <name>openHAB Add-ons :: Bundles :: Bosch Smart Home Binding</name>
<dependencies>
<dependency>
<features name="org.openhab.binding.boschshc-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
<repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
- <feature name="openhab-binding-boschshc" description="BoschSHC Binding" version="${project.version}">
+ <feature name="openhab-binding-boschshc" description="Bosch Smart Home Binding" version="${project.version}">
<feature>openhab-runtime-base</feature>
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.boschshc/${project.version}</bundle>
</feature>
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.client.util.StringContentProvider;
import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
this.systemPassword = systemPassword;
}
+ /**
+ * Returns the public information URL for the Bosch SHC clients, using port 8446.
+ * See https://github.com/BoschSmartHome/bosch-shc-api-docs/blob/master/postman/README.md
+ *
+ * @return URL for public information
+ */
+ public String getPublicInformationUrl() {
+ return String.format("https://%s:8446/smarthome/public/information", this.ipAddress);
+ }
+
/**
* Returns the pairing URL for the Bosch SHC clients, using port 8443.
* See https://github.com/BoschSmartHome/bosch-shc-api-docs/blob/master/postman/README.md
return this.getBoschSmartHomeUrl(String.format("devices/%s/services/%s/state", deviceId, serviceName));
}
+ /**
+ * Checks if the Bosch SHC is online.
+ *
+ * The HTTP server could be offline (Timeout of request).
+ * Or during boot-up the server can response e.g. with SERVICE_UNAVAILABLE_503
+ *
+ * Will return true, if the server responds with the "public information".
+ *
+ *
+ * @return true if HTTP server is online
+ * @throws InterruptedException in case of an interrupt
+ */
+ public boolean isOnline() throws InterruptedException {
+ try {
+ String url = this.getPublicInformationUrl();
+ Request request = this.createRequest(url, GET);
+ ContentResponse contentResponse = request.send();
+ if (HttpStatus.getCode(contentResponse.getStatus()).isSuccess()) {
+ String content = contentResponse.getContentAsString();
+ logger.debug("Online check completed with success: {} - status code: {}", content,
+ contentResponse.getStatus());
+ return true;
+ } else {
+ logger.debug("Online check failed with status code: {}", contentResponse.getStatus());
+ return false;
+ }
+ } catch (TimeoutException | ExecutionException | NullPointerException e) {
+ logger.debug("Online check failed because of {}!", e.getMessage());
+ return false;
+ }
+ }
+
/**
* Checks if the Bosch SHC can be accessed.
- *
- * @return true if HTTP access was successful
+ *
+ * @return true if HTTP access to SHC devices was successful
* @throws InterruptedException in case of an interrupt
*/
public boolean isAccessPossible() throws InterruptedException {
String url = this.getBoschSmartHomeUrl("devices");
Request request = this.createRequest(url, GET);
ContentResponse contentResponse = request.send();
- String content = contentResponse.getContentAsString();
- logger.debug("Access check response complete: {} - return code: {}", content, contentResponse.getStatus());
- return true;
+ if (HttpStatus.getCode(contentResponse.getStatus()).isSuccess()) {
+ String content = contentResponse.getContentAsString();
+ logger.debug("Access check completed with success: {} - status code: {}", content,
+ contentResponse.getStatus());
+ return true;
+ } else {
+ logger.debug("Access check failed with status code: {}", contentResponse.getStatus());
+ return false;
+ }
} catch (TimeoutException | ExecutionException | NullPointerException e) {
- logger.debug("Access check response failed because of {}!", e.getMessage());
+ logger.debug("Access check failed because of {}!", e.getMessage());
return false;
}
}
* @throws InterruptedException in case of an interrupt
*/
public boolean doPairing() throws InterruptedException {
- logger.trace("Starting pairing openHAB Client with Bosch SmartHomeController!");
- logger.trace("Please press the Bosch SHC button until LED starts blinking");
+ 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");
ContentResponse contentResponse;
try {
// javax.net.ssl.SSLHandshakeException: General SSLEngine problem
// => usually the pairing failed, because hardware button was not pressed.
logger.trace("Pairing failed - Details: {}", e.getMessage());
- logger.warn("Pairing failed. Was the Bosch SHC button pressed?");
+ logger.warn("Pairing failed. Was the Bosch Smart Home Controller button pressed?");
return false;
}
}
* @return created HTTP request instance
*/
public Request createRequest(String url, HttpMethod method, @Nullable Object content) {
- Request request = this.newRequest(url).method(method).header("Content-Type", "application/json");
+ logger.trace("Create request for http client {}", this.toString());
+
+ Request request = this.newRequest(url).method(method).header("Content-Type", "application/json")
+ .header("api-version", "2.1") // see https://github.com/BoschSmartHome/bosch-shc-api-docs/issues/46
+ .timeout(10, TimeUnit.SECONDS); // Set default timeout
+
if (content != null) {
String body = GSON.toJson(content);
logger.trace("create request for {} and content {}", url, body);
logger.trace("create request for {}", url);
}
- // Set default timeout
- request.timeout(10, TimeUnit.SECONDS);
-
return request;
}
*/
public <TContent> TContent sendRequest(Request request, Class<TContent> responseContentClass)
throws InterruptedException, TimeoutException, ExecutionException {
+ logger.trace("Send request: {}", request.toString());
+
ContentResponse contentResponse = request.send();
- logger.debug("BoschHttpClient: response complete: {} - return code: {}", contentResponse.getContentAsString(),
+ logger.debug("Received response: {} - status: {}", contentResponse.getContentAsString(),
contentResponse.getStatus());
try {
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.client.api.Response;
+import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.openhab.binding.boschshc.internal.devices.BoschSHCHandler;
import org.openhab.binding.boschshc.internal.devices.bridge.dto.*;
import org.openhab.core.thing.binding.BaseBridgeHandler;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.types.Command;
+import org.osgi.framework.FrameworkUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Override
public void initialize() {
+ logger.debug("Initialize {} Version {}", FrameworkUtil.getBundle(getClass()).getSymbolicName(),
+ FrameworkUtil.getBundle(getClass()).getVersion());
+
// Read configuration
BoschSHCBridgeConfiguration config = getConfigAs(BoschSHCBridgeConfiguration.class);
- if (config.ipAddress.isEmpty()) {
- this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "No IP address set");
+ String ipAddress = config.ipAddress.trim();
+ if (ipAddress.isEmpty()) {
+ this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
+ "@text/offline.conf-error-empty-ip");
return;
}
- if (config.password.isEmpty()) {
- updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "No system password set");
+ String password = config.password.trim();
+ if (password.isEmpty()) {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
+ "@text/offline.conf-error-empty-password");
return;
}
SslContextFactory factory;
try {
// prepare SSL key and certificates
- factory = new BoschSslUtil(config.ipAddress).getSslContextFactory();
+ factory = new BoschSslUtil(ipAddress).getSslContextFactory();
} catch (PairingFailedException e) {
this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR,
"@text/offline.conf-error-ssl");
}
// Instantiate HttpClient with the SslContextFactory
- BoschHttpClient httpClient = this.httpClient = new BoschHttpClient(config.ipAddress, config.password, factory);
+ BoschHttpClient httpClient = this.httpClient = new BoschHttpClient(ipAddress, password, factory);
// Start http client
try {
return;
}
+ // general checks are OK, therefore set the status to unknown and wait for initial access
+ this.updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.UNKNOWN.NONE);
+
// Initialize bridge in the background.
// Start initial access the first time
scheduleInitialAccess(httpClient);
@Override
public void dispose() {
// Cancel scheduled pairing.
+ @Nullable
ScheduledFuture<?> scheduledPairing = this.scheduledPairing;
if (scheduledPairing != null) {
scheduledPairing.cancel(true);
// Stop long polling.
this.longPolling.stop();
+ @Nullable
BoschHttpClient httpClient = this.httpClient;
if (httpClient != null) {
try {
* and starts the first log poll.
*/
private void initialAccess(BoschHttpClient httpClient) {
- logger.debug("Initializing Bosch SHC Bridge: {} - HTTP client is: {} - version: 2020-04-05", this, httpClient);
+ logger.debug("Initializing Bosch SHC Bridge: {} - HTTP client is: {}", this, httpClient);
try {
- // check access and pair if necessary
- if (!httpClient.isAccessPossible()) {
+ // check if SCH is offline
+ if (!httpClient.isOnline()) {
// update status already if access is not possible
+ this.updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.UNKNOWN.NONE,
+ "@text/offline.conf-error-offline");
+ // restart later initial access
+ scheduleInitialAccess(httpClient);
+ return;
+ }
+
+ // SHC is online
+ // check if SHC access is not possible and pairing necessary
+ if (!httpClient.isAccessPossible()) {
+ // update status description to show pairing test
this.updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.UNKNOWN.NONE,
"@text/offline.conf-error-pairing");
if (!httpClient.doPairing()) {
}
// restart initial access - needed also in case of successful pairing to check access again
scheduleInitialAccess(httpClient);
- } else {
- // print rooms and devices if things are reachable
- boolean thingReachable = true;
- thingReachable &= this.getRooms();
- thingReachable &= this.getDevices();
-
- if (thingReachable) {
- this.updateStatus(ThingStatus.ONLINE);
-
- // Start long polling
- try {
- this.longPolling.start(httpClient);
- } catch (LongPollingFailedException e) {
- this.handleLongPollFailure(e);
- }
- } else {
- this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
- "@text/offline.not-reachable");
- // restart initial access
- scheduleInitialAccess(httpClient);
- }
+ return;
+ }
+
+ // SHC is online and access is possible
+ // print rooms and devices
+ boolean thingReachable = true;
+ thingReachable &= this.getRooms();
+ thingReachable &= this.getDevices();
+ if (!thingReachable) {
+ this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
+ "@text/offline.not-reachable");
+ // restart initial access
+ scheduleInitialAccess(httpClient);
+ return;
+ }
+
+ // start long polling loop
+ this.updateStatus(ThingStatus.ONLINE);
+ try {
+ this.longPolling.start(httpClient);
+ } catch (LongPollingFailedException e) {
+ this.handleLongPollFailure(e);
}
+
} catch (InterruptedException e) {
- this.updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.UNKNOWN.NONE,
- String.format("Pairing was interrupted: %s", e.getMessage()));
+ this.updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.UNKNOWN.NONE, "@text/offline.interrupted");
}
}
/**
* Get a list of connected devices from the Smart-Home Controller
*
- * @throws InterruptedException
+ * @throws InterruptedException in case bridge is stopped
*/
private boolean getDevices() throws InterruptedException {
+ @Nullable
BoschHttpClient httpClient = this.httpClient;
if (httpClient == null) {
return false;
}
try {
- logger.debug("Sending http request to Bosch to request clients: {}", httpClient);
+ logger.debug("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;
+ }
+
String content = contentResponse.getContentAsString();
- logger.debug("Response complete: {} - return code: {}", content, contentResponse.getStatus());
+ logger.debug("Request devices completed with success: {} - status code: {}", content,
+ contentResponse.getStatus());
Type collectionType = new TypeToken<ArrayList<Device>>() {
}.getType();
}
}
} catch (TimeoutException | ExecutionException e) {
- logger.debug("HTTP request failed with exception {}", e.getMessage());
+ logger.warn("Request devices failed because of {}!", e.getMessage());
return false;
}
return true;
}
+ /**
+ * Bridge callback handler for the results of long polls.
+ *
+ * It will check the result and
+ * forward the received to the bosch thing handlers.
+ *
+ * @param result Results from Long Polling
+ */
private void handleLongPollResult(LongPollResult result) {
for (DeviceStatusUpdate update : result.result) {
if (update != null && update.state != null) {
Bridge bridge = this.getThing();
for (Thing childThing : bridge.getThings()) {
// All children of this should implement BoschSHCHandler
+ @Nullable
ThingHandler baseHandler = childThing.getHandler();
if (baseHandler != null && baseHandler instanceof BoschSHCHandler) {
BoschSHCHandler handler = (BoschSHCHandler) baseHandler;
+ @Nullable
String deviceId = handler.getBoschID();
handled = true;
}
}
+ /**
+ * Bridge callback handler for the failures during long polls.
+ *
+ * It will update the bridge status and try to access the SHC again.
+ *
+ * @param e error during long polling
+ */
private void handleLongPollFailure(Throwable e) {
- logger.warn("Long polling failed", e);
- updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, "Long polling failed");
+ logger.warn("Long polling failed, will try to reconnect", e);
+ @Nullable
+ BoschHttpClient httpClient = this.httpClient;
+ if (httpClient == null) {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
+ "@text/offline.long-polling-failed.http-client-null");
+ return;
+ }
+
+ this.updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.UNKNOWN.NONE,
+ "@text/offline.long-polling-failed.trying-to-reconnect");
+ scheduleInitialAccess(httpClient);
}
/**
* Get a list of rooms from the Smart-Home controller
*
- * @throws InterruptedException
+ * @throws InterruptedException in case bridge is stopped
*/
private boolean getRooms() throws InterruptedException {
+ @Nullable
BoschHttpClient httpClient = this.httpClient;
if (httpClient != null) {
try {
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("Response complete: {} - return code: {}", content, contentResponse.getStatus());
+ logger.debug("Request rooms completed with success: {} - status code: {}", content,
+ contentResponse.getStatus());
Type collectionType = new TypeToken<ArrayList<Room>>() {
}.getType();
return true;
} catch (TimeoutException | ExecutionException e) {
- logger.warn("HTTP request failed: {}", e.getMessage());
+ logger.warn("Request rooms failed because of {}!", e.getMessage());
return false;
}
} else {
*/
public <T extends BoschSHCServiceState> @Nullable T getState(String deviceId, String stateName, Class<T> stateClass)
throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
+ @Nullable
BoschHttpClient httpClient = this.httpClient;
if (httpClient == null) {
logger.warn("HttpClient not initialized");
*/
public <T extends BoschSHCServiceState> @Nullable Response putState(String deviceId, String serviceName, T state)
throws InterruptedException, TimeoutException, ExecutionException {
+ @Nullable
BoschHttpClient httpClient = this.httpClient;
if (httpClient == null) {
logger.warn("HttpClient not initialized");
Request request = httpClient.createRequest(url, PUT, state);
// Send request
- Response response = request.send();
- return response;
+ return request.send();
}
}
String subscriptionId = response.getResult();
return subscriptionId;
} catch (TimeoutException | ExecutionException | InterruptedException e) {
- throw new LongPollingFailedException("Error on subscribe request", e);
+ throw new LongPollingFailedException(
+ String.format("Error on subscribe (Http client: %s): %s", httpClient.toString(), e.getMessage()),
+ e);
+ }
+ }
+
+ /**
+ * Create a new subscription for long polling.
+ *
+ * @param httpClient Http client to send requests to
+ */
+ private void resubscribe(BoschHttpClient httpClient) {
+ try {
+ String subscriptionId = this.subscribe(httpClient);
+ this.executeLongPoll(httpClient, subscriptionId);
+ } catch (LongPollingFailedException e) {
+ this.handleFailure.accept(e);
+ return;
}
}
request.send(new BufferingResponseListener() {
@Override
public void onComplete(@Nullable Result result) {
- Throwable failure = result != null ? result.getFailure() : null;
- if (failure != null) {
- if (failure instanceof ExecutionException) {
- if (failure.getCause() instanceof AbortLongPolling) {
- logger.debug("Canceling long polling for subscription id {} because it was aborted",
- subscriptionId);
- } else {
- longPolling.handleFailure.accept(new LongPollingFailedException(
- "Unexpected exception during long polling request", failure));
- }
- } else {
- longPolling.handleFailure.accept(new LongPollingFailedException(
- "Unexpected exception during long polling request", failure));
- }
- } else {
- longPolling.onLongPollResponse(httpClient, subscriptionId, this.getContentAsString());
- }
+ // NOTE: This handler runs inside the HTTP thread, so we schedule the response handling in a new thread
+ // because the HTTP thread is terminated after the timeout expires.
+ scheduler.execute(() -> longPolling.onLongPollComplete(httpClient, subscriptionId, result,
+ this.getContentAsString()));
}
});
}
- private void onLongPollResponse(BoschHttpClient httpClient, String subscriptionId, String content) {
+ /**
+ * This is the handler for responses of long poll requests.
+ *
+ * @param httpClient HTTP client which received the response
+ * @param subscriptionId Id of subscription the response is for
+ * @param result Complete result of the response
+ * @param content Content of the response
+ */
+ private void onLongPollComplete(BoschHttpClient httpClient, String subscriptionId, @Nullable Result result,
+ String content) {
// Check if thing is still online
if (this.aborted) {
logger.debug("Canceling long polling for subscription id {} because it was aborted", subscriptionId);
return;
}
- logger.debug("Long poll response: {}", content);
+ // Check if response was failure or success
+ Throwable failure = result != null ? result.getFailure() : null;
+ if (failure != null) {
+ if (failure instanceof ExecutionException) {
+ if (failure.getCause() instanceof AbortLongPolling) {
+ logger.debug("Canceling long polling for subscription id {} because it was aborted",
+ subscriptionId);
+ } else {
+ this.handleFailure.accept(new LongPollingFailedException(
+ "Unexpected exception during long polling request", failure));
+ }
+ } else {
+ this.handleFailure.accept(
+ new LongPollingFailedException("Unexpected exception during long polling request", failure));
+ }
+ } else {
+ logger.debug("Long poll response: {}", content);
- String nextSubscriptionId = subscriptionId;
+ String nextSubscriptionId = subscriptionId;
- LongPollResult longPollResult = gson.fromJson(content, LongPollResult.class);
- if (longPollResult != null && longPollResult.result != null) {
- this.handleResult.accept(longPollResult);
- } else {
- logger.warn("Long poll response contained no results: {}", content);
+ LongPollResult longPollResult = gson.fromJson(content, LongPollResult.class);
+ if (longPollResult != null && longPollResult.result != null) {
+ this.handleResult.accept(longPollResult);
+ } else {
+ logger.debug("Long poll response contained no result: {}", content);
- // Check if we got a proper result from the SHC
- LongPollError longPollError = gson.fromJson(content, LongPollError.class);
+ // Check if we got a proper result from the SHC
+ LongPollError longPollError = gson.fromJson(content, LongPollError.class);
- if (longPollError != null && longPollError.error != null) {
- logger.warn("Got long poll error: {} (code: {})", longPollError.error.message,
- longPollError.error.code);
+ if (longPollError != null && longPollError.error != null) {
+ logger.debug("Got long poll error: {} (code: {})", longPollError.error.message,
+ longPollError.error.code);
- if (longPollError.error.code == LongPollError.SUBSCRIPTION_INVALID) {
- logger.warn("Subscription {} became invalid, subscribing again", subscriptionId);
- try {
- nextSubscriptionId = this.subscribe(httpClient);
- } catch (LongPollingFailedException e) {
- this.handleFailure.accept(e);
+ if (longPollError.error.code == LongPollError.SUBSCRIPTION_INVALID) {
+ logger.debug("Subscription {} became invalid, subscribing again", subscriptionId);
+ this.resubscribe(httpClient);
return;
}
}
}
- }
- // Execute next run.
- this.executeLongPoll(httpClient, nextSubscriptionId);
+ // Execute next run
+ this.longPoll(httpClient, nextSubscriptionId);
+ }
}
@SuppressWarnings("serial")
void updateAirQualityState(AirQualityLevelState state) {
updateState(CHANNEL_TEMPERATURE, new QuantityType<Temperature>(state.temperature, SIUnits.CELSIUS));
updateState(CHANNEL_TEMPERATURE_RATING, new StringType(state.temperatureRating));
- updateState(CHANNEL_HUMIDITY, new QuantityType<Dimensionless>(state.humidity, Units.ONE));
+ updateState(CHANNEL_HUMIDITY, new QuantityType<Dimensionless>(state.humidity, Units.PERCENT));
updateState(CHANNEL_HUMIDITY_RATING, new StringType(state.humidityRating));
- updateState(CHANNEL_PURITY, new QuantityType<Dimensionless>(state.purity, Units.ONE));
+ updateState(CHANNEL_PURITY, new QuantityType<Dimensionless>(state.purity, Units.PARTS_PER_MILLION));
updateState(CHANNEL_AIR_DESCRIPTION, new StringType(state.description));
updateState(CHANNEL_PURITY_RATING, new StringType(state.purityRating));
updateState(CHANNEL_COMBINED_RATING, new StringType(state.combinedRating));
xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0 https://openhab.org/schemas/binding-1.0.0.xsd">
<name>Bosch Smart Home Binding</name>
- <description>This is the binding for Bosch Smart Home Controller.</description>
+ <description>This is the binding for Bosch Smart Home.</description>
<author>Stefan Kästle</author>
</binding:binding>
+
# Thing status offline descriptions
+offline.conf-error-empty-ip = No network address set.
+offline.conf-error-empty-password = No system password set.
+offline.conf-error-offline = The Bosch Smart Home Controller is offline or network address is wrong.
offline.conf-error-pairing = Press pairing button on the Bosch Smart Home Controller.
-offline.not-reachable = Smart Home Controller is not reachable.
+offline.not-reachable = The Bosch Smart Home Controller is not reachable.
offline.conf-error-ssl = The SSL connection to the Bosch Smart Home Controller is not possible.
+offline.long-polling-failed.http-client-null = Long polling failed and could not be restarted because http client is null.
+offline.long-polling-failed.trying-to-reconnect = Long polling failed, will try to reconnect.
+offline.interrupted = Conneting to Bosch Smart Home Controller was interrupted.
-# binding
-binding.boschshc.name = Bosch Smart Home Controller Binding
-binding.boschshc.description = Dieses Binding integriert das Bosch Smart Home System. Durch diese können die Bosch Smart Home Geräte verwendet werden.
+# binding.xml strings
+binding.boschshc.name = Bosch Smart Home Binding
+binding.boschshc.description = Dieses Binding integriert das Bosch Smart Home System. Durch diese können die Bosch Smart Home Geräte verwendet werden.
# Thing status offline descriptions
offline.conf-error-pairing = Bitte betätigen Sie den Taster am Bosch Smart Home Controller zum automatischen Verbinden.
<!-- Bosch Bridge -->
<bridge-type id="shc">
<label>Smart Home Controller</label>
- <description>The Bosch SHC Bridge representing the Bosch Smart Home Controller.</description>
+ <description>The Bosch Smart Home Bridge representing the Bosch Smart Home Controller.</description>
<config-description-ref uri="thing-type:boschshc:bridge"/>
</bridge-type>
assertNotNull(httpClient);
}
+ @Test
+ void getPublicInformationUrl() {
+ assertEquals("https://127.0.0.1:8446/smarthome/public/information", httpClient.getPublicInformationUrl());
+ }
+
@Test
void getPairingUrl() {
assertEquals("https://127.0.0.1:8443/smarthome/clients", httpClient.getPairingUrl());
assertFalse(httpClient.isAccessPossible());
}
+ @Test
+ void isOnline() throws InterruptedException {
+ assertFalse(httpClient.isOnline());
+ }
+
@Test
void doPairing() throws InterruptedException {
assertFalse(httpClient.doPairing());