diff --git a/bundles/org.openhab.binding.boschshc/README.md b/bundles/org.openhab.binding.boschshc/README.md index 979f54659..f5ba3b261 100644 --- a/bundles/org.openhab.binding.boschshc/README.md +++ b/bundles/org.openhab.binding.boschshc/README.md @@ -49,7 +49,7 @@ A compact smart plug with energy monitoring capabilities. | power-consumption | Number:Power | ☐ | Current power consumption (W) of the device. | | energy-consumption | Number:Energy | ☐ | Cumulated energy consumption (Wh) of the device. | -### Twinguard smoke detector +### Twinguard Smoke Detector The Twinguard smoke detector warns you in case of fire and constantly monitors the air. @@ -65,6 +65,8 @@ The Twinguard smoke detector warns you in case of fire and constantly monitors t | purity-rating | String | ☐ | Rating of current measured purity. | | air-description | String | ☐ | Overall description of the air quality. | | combined-rating | String | ☐ | Combined rating of the air quality. | +| battery-level | Number | ☐ | Current battery level percentage as integer number. Bosch-specific battery levels are mapped to numbers as follows: `OK`: 100, `LOW_BATTERY`: 10, `CRITICAL_LOW`: 1, `CRITICALLY_LOW_BATTERY`: 1, `NOT_AVAILABLE`: `UNDEF`. | +| low-battery | Switch | ☐ | Indicates whether the battery is low (`ON`) or OK (`OFF`). | ### Door/Window Contact @@ -75,6 +77,8 @@ Detects open windows and doors. | Channel Type ID | Item Type | Writable | Description | | --------------- | --------- | :------: | ---------------------------- | | contact | Contact | ☐ | Contact state of the device. | +| battery-level | Number | ☐ | Current battery level percentage as integer number. Bosch-specific battery levels are mapped to numbers as follows: `OK`: 100, `LOW_BATTERY`: 10, `CRITICAL_LOW`: 1, `CRITICALLY_LOW_BATTERY`: 1, `NOT_AVAILABLE`: `UNDEF`. | +| low-battery | Switch | ☐ | Indicates whether the battery is low (`ON`) or OK (`OFF`). | ### Motion Detector @@ -85,6 +89,8 @@ Detects every movement through an intelligent combination of passive infra-red t | Channel Type ID | Item Type | Writable | Description | | --------------- | --------- | :------: | ------------------------------ | | latest-motion | DateTime | ☐ | The date of the latest motion. | +| battery-level | Number | ☐ | Current battery level percentage as integer number. Bosch-specific battery levels are mapped to numbers as follows: `OK`: 100, `LOW_BATTERY`: 10, `CRITICAL_LOW`: 1, `CRITICALLY_LOW_BATTERY`: 1, `NOT_AVAILABLE`: `UNDEF`. | +| low-battery | Switch | ☐ | Indicates whether the battery is low (`ON`) or OK (`OFF`). | ### Shutter Control @@ -107,6 +113,8 @@ Radiator thermostat | temperature | Number:Temperature | ☐ | Current measured temperature. | | valve-tappet-position | Number:Dimensionless | ☐ | Current open ratio of valve tappet (0 to 100). | | child-lock | Switch | ☑ | Indicates if child lock is active. | +| battery-level | Number | ☐ | Current battery level percentage as integer number. Bosch-specific battery levels are mapped to numbers as follows: `OK`: 100, `LOW_BATTERY`: 10, `CRITICAL_LOW`: 1, `CRITICALLY_LOW_BATTERY`: 1, `NOT_AVAILABLE`: `UNDEF`. | +| low-battery | Switch | ☐ | Indicates whether the battery is low (`ON`) or OK (`OFF`). | ### Climate Control @@ -129,6 +137,8 @@ Display of the current room temperature as well as the relative humidity in the | --------------- | -------------------- | :------: | ------------------------------------- | | temperature | Number:Temperature | ☐ | Current measured temperature. | | humidity | Number:Dimensionless | ☐ | Current measured humidity (0 to 100). | +| battery-level | Number | ☐ | Current battery level percentage as integer number. Bosch-specific battery levels are mapped to numbers as follows: `OK`: 100, `LOW_BATTERY`: 10, `CRITICAL_LOW`: 1, `CRITICALLY_LOW_BATTERY`: 1, `NOT_AVAILABLE`: `UNDEF`. | +| low-battery | Switch | ☐ | Indicates whether the battery is low (`ON`) or OK (`OFF`). | ### Security Camera 360 diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/AbstractBatteryPoweredDeviceHandler.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/AbstractBatteryPoweredDeviceHandler.java new file mode 100644 index 000000000..4e87b1084 --- /dev/null +++ b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/AbstractBatteryPoweredDeviceHandler.java @@ -0,0 +1,58 @@ +/** + * Copyright (c) 2010-2022 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; + +import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.*; + +import java.util.List; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.boschshc.internal.devices.bridge.dto.DeviceServiceData; +import org.openhab.binding.boschshc.internal.exceptions.BoschSHCException; +import org.openhab.binding.boschshc.internal.services.batterylevel.BatteryLevel; +import org.openhab.binding.boschshc.internal.services.batterylevel.BatteryLevelService; +import org.openhab.core.thing.Thing; + +/** + * Abstract implementation for battery-powered devices. + * + * @author David Pace - Initial contribution + * + */ +@NonNullByDefault +public class AbstractBatteryPoweredDeviceHandler extends BoschSHCDeviceHandler { + + /** + * Service to monitor the battery level of the device + */ + private final BatteryLevelService batteryLevelService; + + public AbstractBatteryPoweredDeviceHandler(Thing thing) { + super(thing); + this.batteryLevelService = new BatteryLevelService(); + } + + @Override + protected void initializeServices() throws BoschSHCException { + super.initializeServices(); + + registerService(batteryLevelService, this::updateChannels, List.of(CHANNEL_BATTERY_LEVEL, CHANNEL_LOW_BATTERY), + true); + } + + private void updateChannels(DeviceServiceData deviceServiceData) { + BatteryLevel batteryLevel = BatteryLevel.fromDeviceServiceData(deviceServiceData); + super.updateState(CHANNEL_BATTERY_LEVEL, batteryLevel.toState()); + super.updateState(CHANNEL_LOW_BATTERY, batteryLevel.toLowBatteryState()); + } +} 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 7097deba8..56f61ce5c 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 @@ -74,6 +74,8 @@ public class BoschSHCBindingConstants { public static final String CHANNEL_ARM_ACTION = "arm-action"; public static final String CHANNEL_DISARM_ACTION = "disarm-action"; public static final String CHANNEL_MUTE_ACTION = "mute-action"; + public static final String CHANNEL_BATTERY_LEVEL = "battery-level"; + public static final String CHANNEL_LOW_BATTERY = "low-battery"; // static device/service names public static final String SERVICE_INTRUSION_DETECTION = "intrusionDetectionSystem"; diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/BoschSHCHandler.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/BoschSHCHandler.java index 9e3d79836..a4c8e5966 100644 --- a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/BoschSHCHandler.java +++ b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/BoschSHCHandler.java @@ -161,7 +161,7 @@ public abstract class BoschSHCHandler extends BaseThingHandler { * @param serviceName Name of service the update came from. * @param stateData Current state of device service. Serialized as JSON. */ - public void processUpdate(String serviceName, JsonElement stateData) { + public void processUpdate(String serviceName, @Nullable JsonElement stateData) { // Check services of device to correctly for (DeviceService extends BoschSHCServiceState> deviceService : this.services) { BoschSHCService extends BoschSHCServiceState> service = deviceService.service; 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 fd372f5f9..7c214a7de 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 @@ -77,7 +77,7 @@ public class BoschHttpClient extends HttpClient { /** * 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 URL for pairing */ public String getPairingUrl() { @@ -86,7 +86,7 @@ public class BoschHttpClient extends HttpClient { /** * Returns a Bosch SHC URL for the endpoint, using port 8444. - * + * * @param endpoint a endpoint, see https://apidocs.bosch-smarthome.com/local/index.html * @return Bosch SHC URL for passed endpoint */ @@ -96,7 +96,7 @@ public class BoschHttpClient extends HttpClient { /** * Returns a SmartHome URL for the endpoint - shortcut of {@link BoschSslUtil::getBoschShcUrl()} - * + * * @param endpoint a endpoint, see https://apidocs.bosch-smarthome.com/local/index.html * @return SmartHome URL for passed endpoint */ @@ -105,15 +105,41 @@ public class BoschHttpClient extends HttpClient { } /** - * Returns a device & service URL. + * Returns a URL to get or put a service state. + *
+ * Example: + * + *
+ * https://localhost:8444/smarthome/devices/hdm:ZigBee:000d6f0016d1cdae/services/AirQualityLevel/state + *+ * * see https://apidocs.bosch-smarthome.com/local/index.html - * + * * @param serviceName the name of the service * @param deviceId the device identifier - * @return SmartHome URL for passed endpoint + * @return a URL to get or put a service state + */ + public String getServiceStateUrl(String serviceName, String deviceId) { + return this.getBoschSmartHomeUrl(String.format("devices/%s/services/%s/state", deviceId, serviceName)); + } + + /** + * Returns a URL to get general information about a service. + *
+ * Example: + * + *
+ * https://localhost:8444/smarthome/devices/hdm:ZigBee:000d6f0016d1cdae/services/BatteryLevel + *+ * + * In some cases this URL has to be used to get the service state, for example for battery levels. + * + * @param serviceName the name of the service + * @param deviceId the device identifier + * @return a URL to retrieve general service information */ public String getServiceUrl(String serviceName, String deviceId) { - return this.getBoschSmartHomeUrl(String.format("devices/%s/services/%s/state", deviceId, serviceName)); + return this.getBoschSmartHomeUrl(String.format("devices/%s/services/%s", deviceId, serviceName)); } /** @@ -177,7 +203,7 @@ public class BoschHttpClient extends HttpClient { /** * Pairs this client with the Bosch SHC. * Press pairing button on the Bosch Smart Home Controller! - * + * * @return true if pairing was successful, otherwise false * @throws InterruptedException in case of an interrupt */ @@ -228,7 +254,7 @@ public class BoschHttpClient extends HttpClient { /** * Creates a HTTP request. - * + * * @param url for the HTTP request * @param method for the HTTP request * @return created HTTP request instance @@ -239,7 +265,7 @@ public class BoschHttpClient extends HttpClient { /** * Creates a HTTP request. - * + * * @param url for the HTTP request * @param method for the HTTP request * @param content for the HTTP request @@ -265,7 +291,7 @@ public class BoschHttpClient extends HttpClient { /** * Sends a request and expects a response of the specified type. - * + * * @param request Request to send * @param responseContentClass Type of expected response * @param contentValidator Checks if the parsed response is valid 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 a7a134ed0..a7f3e6bab 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 @@ -30,7 +30,7 @@ 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.Device; -import org.openhab.binding.boschshc.internal.devices.bridge.dto.DeviceStatusUpdate; +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.exceptions.BoschSHCException; @@ -51,6 +51,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.gson.Gson; +import com.google.gson.JsonElement; import com.google.gson.reflect.TypeToken; /** @@ -299,52 +300,96 @@ public class BridgeHandler extends BaseBridgeHandler { /** * Bridge callback handler for the results of long polls. * - * It will check the result and - * forward the received to the bosch thing handlers. + * It will check the results and + * forward the received states 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) { - logger.debug("Got update for service {} of type {}: {}", update.id, update.type, update.state); + for (DeviceServiceData deviceServiceData : result.result) { + handleDeviceServiceData(deviceServiceData); + } + } - var updateDeviceId = update.deviceId; - if (updateDeviceId == null) { - continue; - } + /** + * Processes a single long poll result. + * + * @param deviceServiceData object representing a single long poll result + */ + private void handleDeviceServiceData(@Nullable DeviceServiceData deviceServiceData) { + if (deviceServiceData != null) { + JsonElement state = obtainState(deviceServiceData); - logger.debug("Got update for device {}", updateDeviceId); + logger.debug("Got update for service {} of type {}: {}", deviceServiceData.id, deviceServiceData.type, + state); - boolean handled = false; - - 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; - logger.debug("Registered device: {} - looking for {}", deviceId, updateDeviceId); - - if (deviceId != null && updateDeviceId.equals(deviceId)) { - logger.debug("Found child: {} - calling processUpdate (id: {}) with {}", handler, update.id, - update.state); - handler.processUpdate(update.id, update.state); - } - } else { - logger.warn("longPoll: child handler for {} does not implement Bosch SHC handler", baseHandler); - } - } - - if (!handled) { - logger.debug("Could not find a thing for device ID: {}", updateDeviceId); - } + var updateDeviceId = deviceServiceData.deviceId; + if (updateDeviceId == null || state == null) { + return; } + + logger.debug("Got update for device {}", updateDeviceId); + + forwardStateToHandlers(deviceServiceData, state, updateDeviceId); + } + } + + /** + * Extracts the actual state object from the given {@link DeviceServiceData} instance. + *
+ * In some special cases like the BatteryLevel service the {@link DeviceServiceData} object itself
+ * contains the state.
+ * In all other cases, the state is contained in a sub-object named state.
+ *
+ * @param deviceServiceData the {@link DeviceServiceData} object from which the state should be obtained
+ * @return the state sub-object or the {@link DeviceServiceData} object itself
+ */
+ @Nullable
+ private JsonElement obtainState(DeviceServiceData deviceServiceData) {
+ // the battery level service receives no individual state object but rather requires the DeviceServiceData
+ // structure
+ if ("BatteryLevel".equals(deviceServiceData.id)) {
+ return gson.toJsonTree(deviceServiceData);
+ }
+
+ return deviceServiceData.state;
+ }
+
+ /**
+ * Tries to find handlers for the device with the given ID and forwards the received state to the handlers.
+ *
+ * @param deviceServiceData object representing updates received in long poll results
+ * @param state the received state object as JSON element
+ * @param updateDeviceId the ID of the device for which the state update was received
+ */
+ private void forwardStateToHandlers(DeviceServiceData deviceServiceData, JsonElement state, String updateDeviceId) {
+ boolean handled = false;
+
+ 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;
+ logger.debug("Registered device: {} - looking for {}", deviceId, updateDeviceId);
+
+ if (deviceId != null && updateDeviceId.equals(deviceId)) {
+ logger.debug("Found child: {} - calling processUpdate (id: {}) with {}", handler,
+ deviceServiceData.id, state);
+ handler.processUpdate(deviceServiceData.id, state);
+ }
+ } else {
+ logger.warn("longPoll: child handler for {} does not implement Bosch SHC handler", baseHandler);
+ }
+ }
+
+ if (!handled) {
+ logger.debug("Could not find a thing for device ID: {}", updateDeviceId);
}
}
@@ -447,7 +492,7 @@ public class BridgeHandler extends BaseBridgeHandler {
* Query the Bosch Smart Home Controller for the state of the given device.
*
* The URL used for retrieving the state has the following structure: - * + * *
* https://{IP}:8444/smarthome/devices/{deviceId}/services/{serviceName}/state
*
@@ -470,14 +515,14 @@ public class BridgeHandler extends BaseBridgeHandler {
return null;
}
- String url = httpClient.getServiceUrl(stateName, deviceId);
+ String url = httpClient.getServiceStateUrl(stateName, deviceId);
logger.debug("getState(): Requesting \"{}\" from Bosch: {} via {}", stateName, deviceId, url);
return getState(httpClient, url, stateClass);
}
/**
* Queries the Bosch Smart Home Controller for the state using an explicit endpoint.
- *
+ *
* @param null
@@ -611,4 +656,18 @@ public class BridgeHandler extends BaseBridgeHandler {
Request request = httpClient.createRequest(url, POST, requestBody);
return request.send();
}
+
+ public @Nullable DeviceServiceData getServiceData(String deviceId, String serviceName)
+ throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
+ @Nullable
+ BoschHttpClient httpClient = this.httpClient;
+ if (httpClient == null) {
+ logger.warn("HttpClient not initialized");
+ return null;
+ }
+
+ String url = httpClient.getServiceUrl(serviceName, deviceId);
+ logger.debug("getState(): Requesting \"{}\" from Bosch: {} via {}", serviceName, deviceId, url);
+ return getState(httpClient, url, DeviceServiceData.class);
+ }
}
diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/dto/DeviceStatusUpdate.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/dto/DeviceServiceData.java
similarity index 68%
rename from bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/dto/DeviceStatusUpdate.java
rename to bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/dto/DeviceServiceData.java
index 0da4e2434..82f5d495e 100644
--- a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/dto/DeviceStatusUpdate.java
+++ b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/dto/DeviceServiceData.java
@@ -13,9 +13,9 @@
package org.openhab.binding.boschshc.internal.devices.bridge.dto;
import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.boschshc.internal.services.dto.BoschSHCServiceState;
import com.google.gson.JsonElement;
-import com.google.gson.annotations.SerializedName;
/**
* Represents a device status update as represented by the Smart Home
@@ -24,18 +24,13 @@ import com.google.gson.annotations.SerializedName;
* @author Stefan Kästle - Initial contribution
* @author Christian Oeing - refactorings of e.g. server registration
*/
-public class DeviceStatusUpdate {
+public class DeviceServiceData extends BoschSHCServiceState {
+
/**
* Url path of the service the update came from.
*/
public String path;
- /**
- * The type of message.
- */
- @SerializedName("@type")
- public String type;
-
/**
* Name of service the update came from.
*/
@@ -44,15 +39,26 @@ public class DeviceStatusUpdate {
/**
* Current state of device. Serialized as JSON.
*/
- public JsonElement state;
+ public @Nullable JsonElement state;
/**
* Id of device the update is for.
*/
public @Nullable String deviceId;
+ /**
+ * An optional object containing information about device faults.
+ *
+ * Example: low battery warnings are stored as faults with category WARNING
+ */
+ public @Nullable Faults faults;
+
+ public DeviceServiceData() {
+ super("DeviceServiceData");
+ }
+
@Override
public String toString() {
- return this.deviceId + "state: " + this.type;
+ return this.deviceId + " state: " + this.type;
}
}
diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/dto/Fault.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/dto/Fault.java
new file mode 100644
index 000000000..070c8f806
--- /dev/null
+++ b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/dto/Fault.java
@@ -0,0 +1,33 @@
+/**
+ * Copyright (c) 2010-2022 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;
+
+/**
+ * A fault entry containing a category and a type.
+ *
+ * Example JSON: + * + *
+ * {
+ * "type":"LOW_BATTERY",
+ * "category":"WARNING"
+ * }
+ *
+ *
+ * @author David Pace - Initial contribution
+ *
+ */
+public class Fault {
+ public String type;
+ public String category;
+}
diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/dto/Faults.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/dto/Faults.java
new file mode 100644
index 000000000..3e3df0d0c
--- /dev/null
+++ b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/dto/Faults.java
@@ -0,0 +1,39 @@
+/**
+ * Copyright (c) 2010-2022 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;
+
+import java.util.List;
+
+/**
+ * A container object for faults that can be contained in {@link DeviceServiceData}.
+ * + * Example JSON: + * + *
+ * "faults": {
+ * "entries": [
+ * {
+ * "type":"LOW_BATTERY",
+ * "category":"WARNING"
+ * }
+ * ]
+ }
+ *
+ *
+ * @author David Pace - Initial contribution
+ *
+ */
+public class Faults {
+
+ public List* The endpoints of this service have the following URL structure: - * + * *
* https://{IP}:8444/smarthome/devices/{deviceId}/services/{serviceName}/state
*
- *
+ *
* The HTTP client of the bridge will use GET requests to retrieve the state and PUT requests
* to set the state.
*
* The services of the devices and their official APIs can be found
* here.
- *
+ *
* @author Christian Oeing - Initial contribution
* @author David Pace - Service abstraction
*/
@@ -63,7 +63,7 @@ public abstract class BoschSHCService
+ * Note that no fault elements are present when the battery level is OK.
+ *
+ * @param deviceServiceData a device service data model
+ * @return the derived battery level
+ */
+ public static BatteryLevel fromDeviceServiceData(DeviceServiceData deviceServiceData) {
+ Faults faults = deviceServiceData.faults;
+ if (faults == null || faults.entries == null || faults.entries.isEmpty()) {
+ return OK;
+ }
+
+ for (Fault faultEntry : faults.entries) {
+ if ("warning".equalsIgnoreCase(faultEntry.category)) {
+ BatteryLevel batteryLevelState = BatteryLevel.get(faultEntry.type);
+ if (batteryLevelState != null) {
+ return batteryLevelState;
+ }
+ }
+ }
+
+ return OK;
+ }
+
+ /**
+ * Returns the corresponding battery level for the given string or
+ * If the result is null if no state matches.
+ *
+ * @param identifier the battery level identifier
+ *
+ * @return the matching battery level or null
+ */
+ public static @Nullable BatteryLevel get(String identifier) {
+ for (BatteryLevel batteryLevelState : values()) {
+ if (batteryLevelState.toString().equalsIgnoreCase(identifier)) {
+ return batteryLevelState;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Transforms a Bosch-specific battery level to a percentage for the system.battery-level channel.
+ *
+ * @return a percentage between 0 and 100 as integer
+ */
+ public State toState() {
+ switch (this) {
+ case LOW_BATTERY:
+ return new DecimalType(10);
+ case CRITICAL_LOW:
+ case CRITICALLY_LOW_BATTERY:
+ return new DecimalType(1);
+ case NOT_AVAILABLE:
+ return UnDefType.UNDEF;
+ default:
+ return new DecimalType(100);
+ }
+ }
+
+ /**
+ * Transforms a Bosch-specific battery level to an ON/OFF state for the
+ * system.low-battery channel.
+ * ON, the battery is low; if the result is OFF the battery level is OK.
+ *
+ * @return
+ */
+ public OnOffType toLowBatteryState() {
+ switch (this) {
+ case LOW_BATTERY:
+ case CRITICAL_LOW:
+ case CRITICALLY_LOW_BATTERY:
+ return OnOffType.ON;
+ default:
+ return OnOffType.OFF;
+ }
+ }
+}
diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/batterylevel/BatteryLevelService.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/batterylevel/BatteryLevelService.java
new file mode 100644
index 000000000..a4a6b4dd8
--- /dev/null
+++ b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/batterylevel/BatteryLevelService.java
@@ -0,0 +1,53 @@
+/**
+ * Copyright (c) 2010-2022 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.services.batterylevel;
+
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeoutException;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.boschshc.internal.devices.bridge.BridgeHandler;
+import org.openhab.binding.boschshc.internal.devices.bridge.dto.DeviceServiceData;
+import org.openhab.binding.boschshc.internal.exceptions.BoschSHCException;
+import org.openhab.binding.boschshc.internal.services.BoschSHCService;
+
+/**
+ * Service to retrieve battery levels.
+ *
+ * @author David Pace - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class BatteryLevelService extends BoschSHCService