| 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.
| 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
| 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
| 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
| 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
| --------------- | -------------------- | :------: | ------------------------------------- |
| 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
--- /dev/null
+/**
+ * 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());
+ }
+}
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";
* @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;
/**
* 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() {
/**
* 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
*/
/**
* 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
*/
}
/**
- * Returns a device & service URL.
+ * Returns a URL to get or put a service state.
+ * <p>
+ * Example:
+ *
+ * <pre>
+ * https://localhost:8444/smarthome/devices/hdm:ZigBee:000d6f0016d1cdae/services/AirQualityLevel/state
+ * </pre>
+ *
* 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 getServiceUrl(String serviceName, String deviceId) {
+ 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.
+ * <p>
+ * Example:
+ *
+ * <pre>
+ * https://localhost:8444/smarthome/devices/hdm:ZigBee:000d6f0016d1cdae/services/BatteryLevel
+ * </pre>
+ *
+ * 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", deviceId, serviceName));
+ }
+
/**
* Checks if the Bosch SHC is online.
*
/**
* 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
*/
/**
* Creates a HTTP request.
- *
+ *
* @param url for the HTTP request
* @param method for the HTTP request
* @return created HTTP request instance
/**
* Creates a HTTP request.
- *
+ *
* @param url for the HTTP request
* @param method for the HTTP request
* @param content for the HTTP request
/**
* 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
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;
import org.slf4j.LoggerFactory;
import com.google.gson.Gson;
+import com.google.gson.JsonElement;
import com.google.gson.reflect.TypeToken;
/**
/**
* 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;
+ var updateDeviceId = deviceServiceData.deviceId;
+ if (updateDeviceId == null || state == null) {
+ return;
+ }
- 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();
+ logger.debug("Got update for device {}", updateDeviceId);
- handled = true;
- logger.debug("Registered device: {} - looking for {}", deviceId, updateDeviceId);
+ forwardStateToHandlers(deviceServiceData, state, 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);
- }
- }
+ /**
+ * Extracts the actual state object from the given {@link DeviceServiceData} instance.
+ * <p>
+ * In some special cases like the <code>BatteryLevel</code> service the {@link DeviceServiceData} object itself
+ * contains the state.
+ * In all other cases, the state is contained in a sub-object named <code>state</code>.
+ *
+ * @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;
+ }
- if (!handled) {
- logger.debug("Could not find a thing for device ID: {}", updateDeviceId);
+ /**
+ * 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);
+ }
}
/**
* Query the Bosch Smart Home Controller for the state of the given device.
* <p>
* The URL used for retrieving the state has the following structure:
- *
+ *
* <pre>
* https://{IP}:8444/smarthome/devices/{deviceId}/services/{serviceName}/state
* </pre>
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 <T> Type to which the resulting JSON should be deserialized to
* @param endpoint The destination endpoint part of the URL
* @param stateClass Class to convert the resulting JSON to
/**
* Sends a HTTP GET request in order to retrieve a state from the Bosch Smart Home Controller.
- *
+ *
* @param <T> Type to which the resulting JSON should be deserialized to
* @param httpClient HTTP client used for sending the request
* @param url URL at which the state should be retrieved
}
// Create request
- String url = httpClient.getServiceUrl(serviceName, deviceId);
+ String url = httpClient.getServiceStateUrl(serviceName, deviceId);
Request request = httpClient.createRequest(url, PUT, state);
// Send request
/**
* Sends a HTTP POST request without a request body to the given endpoint.
- *
+ *
* @param endpoint The destination endpoint part of the URL
* @return the HTTP response
* @throws InterruptedException
/**
* Sends a HTTP POST request with a request body to the given endpoint.
- *
+ *
* @param <T> Type of the request
* @param endpoint The destination endpoint part of the URL
* @param requestBody object representing the request body to be sent, may be <code>null</code>
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);
+ }
}
--- /dev/null
+/**
+ * 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 org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.boschshc.internal.services.dto.BoschSHCServiceState;
+
+import com.google.gson.JsonElement;
+
+/**
+ * Represents a device status update as represented by the Smart Home
+ * Controller.
+ *
+ * @author Stefan Kästle - Initial contribution
+ * @author Christian Oeing - refactorings of e.g. server registration
+ */
+public class DeviceServiceData extends BoschSHCServiceState {
+
+ /**
+ * Url path of the service the update came from.
+ */
+ public String path;
+
+ /**
+ * Name of service the update came from.
+ */
+ public String id;
+
+ /**
+ * Current state of device. Serialized as JSON.
+ */
+ public @Nullable JsonElement state;
+
+ /**
+ * Id of device the update is for.
+ */
+ public @Nullable String deviceId;
+
+ /**
+ * An optional object containing information about device faults.
+ * <p>
+ * Example: low battery warnings are stored as faults with category <code>WARNING</code>
+ */
+ public @Nullable Faults faults;
+
+ public DeviceServiceData() {
+ super("DeviceServiceData");
+ }
+
+ @Override
+ public String toString() {
+ return this.deviceId + " state: " + this.type;
+ }
+}
+++ /dev/null
-/**
- * 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 org.eclipse.jdt.annotation.Nullable;
-
-import com.google.gson.JsonElement;
-import com.google.gson.annotations.SerializedName;
-
-/**
- * Represents a device status update as represented by the Smart Home
- * Controller.
- *
- * @author Stefan Kästle - Initial contribution
- * @author Christian Oeing - refactorings of e.g. server registration
- */
-public class DeviceStatusUpdate {
- /**
- * 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.
- */
- public String id;
-
- /**
- * Current state of device. Serialized as JSON.
- */
- public JsonElement state;
-
- /**
- * Id of device the update is for.
- */
- public @Nullable String deviceId;
-
- @Override
- public String toString() {
- return this.deviceId + "state: " + this.type;
- }
-}
--- /dev/null
+/**
+ * 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.
+ * <p>
+ * Example JSON:
+ *
+ * <pre>
+ * {
+ * "type":"LOW_BATTERY",
+ * "category":"WARNING"
+ * }
+ * </pre>
+ *
+ * @author David Pace - Initial contribution
+ *
+ */
+public class Fault {
+ public String type;
+ public String category;
+}
--- /dev/null
+/**
+ * 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}.
+ * <p>
+ * Example JSON:
+ *
+ * <pre>
+ * "faults": {
+ * "entries": [
+ * {
+ * "type":"LOW_BATTERY",
+ * "category":"WARNING"
+ * }
+ * ]
+ }
+ * </pre>
+ *
+ * @author David Pace - Initial contribution
+ *
+ */
+public class Faults {
+
+ public List<Fault> entries;
+}
* ],"jsonrpc":"2.0"}
*/
- public ArrayList<DeviceStatusUpdate> result;
+ public ArrayList<DeviceServiceData> result;
public String jsonrpc;
}
import java.util.List;
import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.openhab.binding.boschshc.internal.devices.BoschSHCDeviceHandler;
+import org.openhab.binding.boschshc.internal.devices.AbstractBatteryPoweredDeviceHandler;
import org.openhab.binding.boschshc.internal.exceptions.BoschSHCException;
import org.openhab.binding.boschshc.internal.services.latestmotion.LatestMotionService;
import org.openhab.binding.boschshc.internal.services.latestmotion.dto.LatestMotionServiceState;
* @author Christian Oeing - Use service instead of custom logic
*/
@NonNullByDefault
-public class MotionDetectorHandler extends BoschSHCDeviceHandler {
+public class MotionDetectorHandler extends AbstractBatteryPoweredDeviceHandler {
public MotionDetectorHandler(Thing thing) {
super(thing);
*/
package org.openhab.binding.boschshc.internal.devices.thermostat;
-import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.CHANNEL_CHILD_LOCK;
-import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.CHANNEL_TEMPERATURE;
-import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.CHANNEL_VALVE_TAPPET_POSITION;
+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.BoschSHCDeviceHandler;
+import org.openhab.binding.boschshc.internal.devices.AbstractBatteryPoweredDeviceHandler;
import org.openhab.binding.boschshc.internal.exceptions.BoschSHCException;
import org.openhab.binding.boschshc.internal.services.childlock.ChildLockService;
import org.openhab.binding.boschshc.internal.services.childlock.dto.ChildLockServiceState;
/**
* Handler for a thermostat device.
- *
+ *
* @author Christian Oeing - Initial contribution
*/
@NonNullByDefault
-public final class ThermostatHandler extends BoschSHCDeviceHandler {
+public final class ThermostatHandler extends AbstractBatteryPoweredDeviceHandler {
private ChildLockService childLockService;
@Override
protected void initializeServices() throws BoschSHCException {
+ super.initializeServices();
+
this.createService(TemperatureLevelService::new, this::updateChannels, List.of(CHANNEL_TEMPERATURE));
this.createService(ValveTappetService::new, this::updateChannels, List.of(CHANNEL_VALVE_TAPPET_POSITION));
this.registerService(this.childLockService, this::updateChannels, List.of(CHANNEL_CHILD_LOCK));
/**
* Updates the channels which are linked to the {@link TemperatureLevelService}
* of the device.
- *
+ *
* @param state Current state of {@link TemperatureLevelService}.
*/
private void updateChannels(TemperatureLevelServiceState state) {
/**
* Updates the channels which are linked to the {@link ValveTappetService} of
* the device.
- *
+ *
* @param state Current state of {@link ValveTappetService}.
*/
private void updateChannels(ValveTappetServiceState state) {
/**
* Updates the channels which are linked to the {@link ChildLockService} of the
* device.
- *
+ *
* @param state Current state of {@link ChildLockService}.
*/
private void updateChannels(ChildLockServiceState state) {
*/
package org.openhab.binding.boschshc.internal.devices.twinguard;
-import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.CHANNEL_AIR_DESCRIPTION;
-import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.CHANNEL_COMBINED_RATING;
-import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.CHANNEL_HUMIDITY;
-import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.CHANNEL_HUMIDITY_RATING;
-import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.CHANNEL_PURITY;
-import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.CHANNEL_PURITY_RATING;
-import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.CHANNEL_TEMPERATURE;
-import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.CHANNEL_TEMPERATURE_RATING;
+import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.*;
import java.util.List;
import javax.measure.quantity.Temperature;
import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.openhab.binding.boschshc.internal.devices.BoschSHCDeviceHandler;
+import org.openhab.binding.boschshc.internal.devices.AbstractBatteryPoweredDeviceHandler;
import org.openhab.binding.boschshc.internal.exceptions.BoschSHCException;
import org.openhab.binding.boschshc.internal.services.airqualitylevel.AirQualityLevelService;
import org.openhab.binding.boschshc.internal.services.airqualitylevel.dto.AirQualityLevelServiceState;
* @author Christian Oeing - Use service instead of custom logic
*/
@NonNullByDefault
-public class TwinguardHandler extends BoschSHCDeviceHandler {
+public class TwinguardHandler extends AbstractBatteryPoweredDeviceHandler {
public TwinguardHandler(Thing thing) {
super(thing);
*/
package org.openhab.binding.boschshc.internal.devices.wallthermostat;
-import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.CHANNEL_HUMIDITY;
-import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.CHANNEL_TEMPERATURE;
+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.BoschSHCDeviceHandler;
+import org.openhab.binding.boschshc.internal.devices.AbstractBatteryPoweredDeviceHandler;
import org.openhab.binding.boschshc.internal.exceptions.BoschSHCException;
import org.openhab.binding.boschshc.internal.services.humiditylevel.HumidityLevelService;
import org.openhab.binding.boschshc.internal.services.humiditylevel.dto.HumidityLevelServiceState;
/**
* Handler for a wall thermostat device.
- *
+ *
* @author Christian Oeing - Initial contribution
*/
@NonNullByDefault
-public final class WallThermostatHandler extends BoschSHCDeviceHandler {
+public final class WallThermostatHandler extends AbstractBatteryPoweredDeviceHandler {
public WallThermostatHandler(Thing thing) {
super(thing);
@Override
protected void initializeServices() throws BoschSHCException {
+ super.initializeServices();
+
this.createService(TemperatureLevelService::new, this::updateChannels, List.of(CHANNEL_TEMPERATURE));
this.createService(HumidityLevelService::new, this::updateChannels, List.of(CHANNEL_HUMIDITY));
}
/**
* Updates the channels which are linked to the {@link TemperatureLevelService} of the device.
- *
+ *
* @param state Current state of {@link TemperatureLevelService}.
*/
private void updateChannels(TemperatureLevelServiceState state) {
/**
* Updates the channels which are linked to the {@link HumidityLevelService} of the device.
- *
+ *
* @param state Current state of {@link HumidityLevelService}.
*/
private void updateChannels(HumidityLevelServiceState state) {
import java.util.List;
import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.openhab.binding.boschshc.internal.devices.BoschSHCDeviceHandler;
+import org.openhab.binding.boschshc.internal.devices.AbstractBatteryPoweredDeviceHandler;
import org.openhab.binding.boschshc.internal.exceptions.BoschSHCException;
import org.openhab.binding.boschshc.internal.services.shuttercontact.ShutterContactService;
import org.openhab.binding.boschshc.internal.services.shuttercontact.ShutterContactState;
* @author Stefan Kästle - Initial contribution
*/
@NonNullByDefault
-public class WindowContactHandler extends BoschSHCDeviceHandler {
+public class WindowContactHandler extends AbstractBatteryPoweredDeviceHandler {
public WindowContactHandler(Thing thing) {
super(thing);
@Override
protected void initializeServices() throws BoschSHCException {
+ super.initializeServices();
+
this.createService(ShutterContactService::new, this::updateChannels, List.of(CHANNEL_CONTACT));
}
* same endpoint.
* <p>
* The endpoints of this service have the following URL structure:
- *
+ *
* <pre>
* https://{IP}:8444/smarthome/devices/{deviceId}/services/{serviceName}/state
* </pre>
- *
+ *
* The HTTP client of the bridge will use <code>GET</code> requests to retrieve the state and <code>PUT</code> requests
* to set the state.
* <p>
* The services of the devices and their official APIs can be found
* <a href="https://apidocs.bosch-smarthome.com/local/">here</a>.
- *
+ *
* @author Christian Oeing - Initial contribution
* @author David Pace - Service abstraction
*/
/**
* Constructor
- *
+ *
* @param serviceName Unique name of the service.
* @param stateClass State class that this service uses for data transfers
* from/to the device.
/**
* Initializes the service
- *
+ *
* @param bridgeHandler Bridge to use for communication from/to the device
* @param deviceId Id of device this service is for
* @param stateUpdateListener Function to call when a state update was received
/**
* Returns the class of the state this service provides.
- *
+ *
* @return Class of the state this service provides.
*/
public Class<TState> getStateClass() {
/**
* Requests the current state of the service and updates it.
- *
+ *
* @throws ExecutionException
* @throws TimeoutException
* @throws InterruptedException
/**
* Requests the current state of the device with the specified id.
- *
+ *
* @return Current state of the device.
* @throws ExecutionException
* @throws TimeoutException
/**
* Sets the state of the device with the specified id.
- *
+ *
* @param state State to set.
* @throws InterruptedException
* @throws ExecutionException
/**
* A state update was received from the bridge
- *
+ *
* @param stateData Current state of service. Serialized as JSON.
*/
- public void onStateUpdate(JsonElement stateData) {
+ public void onStateUpdate(@Nullable JsonElement stateData) {
@Nullable
TState state = BoschSHCServiceState.fromJson(stateData, this.stateClass);
if (state == null) {
/**
* A state update was received from the bridge.
- *
+ *
* @param state Current state of service as an instance of the state class.
*/
private void onStateUpdate(TState state) {
/**
* Allows a service to handle a command and create a new state out of it.
* The new state still has to be set via setState.
- *
+ *
* @param command Command to handle
* @throws BoschSHCException If service can not handle command
*/
--- /dev/null
+/**
+ * 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 org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.boschshc.internal.devices.bridge.dto.DeviceServiceData;
+import org.openhab.binding.boschshc.internal.devices.bridge.dto.Fault;
+import org.openhab.binding.boschshc.internal.devices.bridge.dto.Faults;
+import org.openhab.core.library.types.DecimalType;
+import org.openhab.core.library.types.OnOffType;
+import org.openhab.core.types.State;
+import org.openhab.core.types.UnDefType;
+
+/**
+ * Possible battery levels.
+ *
+ * @author David Pace - Initial contribution
+ *
+ */
+@NonNullByDefault
+public enum BatteryLevel {
+ OK,
+ LOW_BATTERY,
+ CRITICAL_LOW,
+ CRITICALLY_LOW_BATTERY,
+ NOT_AVAILABLE;
+
+ /**
+ * Derives a battery level by analyzing the fault elements in the given device service data.
+ * <p>
+ * 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 <code>null</code> if no state matches.
+ *
+ * @param identifier the battery level identifier
+ *
+ * @return the matching battery level or <code>null</code>
+ */
+ 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 <code>system.battery-level</code> 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 <code>ON</code>/<code>OFF</code> state for the
+ * <code>system.low-battery</code> channel.
+ * <p>
+ * If the result is <code>ON</code>, the battery is low; if the result is <code>OFF</code> 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;
+ }
+ }
+}
--- /dev/null
+/**
+ * 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<DeviceServiceData> {
+
+ public BatteryLevelService() {
+ super("BatteryLevel", DeviceServiceData.class);
+ }
+
+ @Override
+ public @Nullable DeviceServiceData getState()
+ throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
+
+ String deviceId = getDeviceId();
+ if (deviceId == null) {
+ return null;
+ }
+
+ BridgeHandler bridgeHandler = getBridgeHandler();
+ if (bridgeHandler == null) {
+ return null;
+ }
+ return bridgeHandler.getServiceData(deviceId, getServiceName());
+ }
+}
/**
* Base Bosch Smart Home Controller service state.
- *
+ *
* @author Christian Oeing - Initial contribution
*/
public class BoschSHCServiceState {
private @Nullable String stateType = null;
@SerializedName("@type")
- private final String type;
+ public final String type;
protected BoschSHCServiceState(String type) {
this.type = type;
<bridge-type-ref id="shc"/>
</supported-bridge-type-refs>
- <label>TwinGuard</label>
+ <label>Twinguard</label>
<description>The Twinguard smoke detector warns you in case of fire and constantly monitors the air.</description>
<channels>
<channel id="air-description" typeId="air-description"/>
<channel id="purity-rating" typeId="purity-rating"/>
<channel id="combined-rating" typeId="combined-rating"/>
+ <channel id="battery-level" typeId="system.battery-level"/>
+ <channel id="low-battery" typeId="system.low-battery"/>
</channels>
<config-description-ref uri="thing-type:boschshc:device"/>
<channels>
<channel id="contact" typeId="contact"/>
+ <channel id="battery-level" typeId="system.battery-level"/>
+ <channel id="low-battery" typeId="system.low-battery"/>
</channels>
<config-description-ref uri="thing-type:boschshc:device"/>
<channels>
<channel id="latest-motion" typeId="latest-motion"/>
+ <channel id="battery-level" typeId="system.battery-level"/>
+ <channel id="low-battery" typeId="system.low-battery"/>
</channels>
<config-description-ref uri="thing-type:boschshc:device"/>
<channel id="temperature" typeId="temperature"/>
<channel id="valve-tappet-position" typeId="valve-tappet-position"/>
<channel id="child-lock" typeId="child-lock"/>
+ <channel id="battery-level" typeId="system.battery-level"/>
+ <channel id="low-battery" typeId="system.low-battery"/>
</channels>
<config-description-ref uri="thing-type:boschshc:device"/>
<channels>
<channel id="temperature" typeId="temperature"/>
<channel id="humidity" typeId="humidity"/>
+ <channel id="battery-level" typeId="system.battery-level"/>
+ <channel id="low-battery" typeId="system.low-battery"/>
</channels>
<config-description-ref uri="thing-type:boschshc:device"/>
--- /dev/null
+/**
+ * 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.mockito.Mockito.verify;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.junit.jupiter.api.Test;
+import org.openhab.core.library.types.DecimalType;
+import org.openhab.core.library.types.OnOffType;
+import org.openhab.core.thing.ChannelUID;
+import org.openhab.core.types.UnDefType;
+
+import com.google.gson.JsonElement;
+import com.google.gson.JsonParser;
+
+/**
+ * Abstract test implementation for battery-powered devices.
+ *
+ * @author David Pace - Initial contribution
+ *
+ * @param <T> type of the battery-powered device to be tested
+ */
+@NonNullByDefault
+public abstract class AbstractBatteryPoweredDeviceHandlerTest<T extends AbstractBatteryPoweredDeviceHandler>
+ extends AbstractBoschSHCDeviceHandlerTest<T> {
+
+ @Test
+ public void testProcessUpdate_BatteryLevel_LowBattery() {
+ JsonElement deviceServiceData = JsonParser.parseString("{ \n" + " \"@type\":\"DeviceServiceData\",\n"
+ + " \"path\":\"/devices/hdm:ZigBee:000d6f0004b93361/services/BatteryLevel\",\n"
+ + " \"id\":\"BatteryLevel\",\n" + " \"deviceId\":\"hdm:ZigBee:000d6f0004b93361\",\n"
+ + " \"faults\":{ \n" + " \"entries\":[\n" + " {\n"
+ + " \"type\":\"LOW_BATTERY\",\n" + " \"category\":\"WARNING\"\n" + " }\n"
+ + " ]\n" + " }\n" + "}");
+ getFixture().processUpdate("BatteryLevel", deviceServiceData);
+ verify(getCallback()).stateUpdated(
+ new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_BATTERY_LEVEL),
+ new DecimalType(10));
+ verify(getCallback()).stateUpdated(
+ new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_LOW_BATTERY), OnOffType.ON);
+ }
+
+ @Test
+ public void testProcessUpdate_BatteryLevel_CriticalLow() {
+ JsonElement deviceServiceData = JsonParser.parseString("{ \n" + " \"@type\":\"DeviceServiceData\",\n"
+ + " \"path\":\"/devices/hdm:ZigBee:000d6f0004b93361/services/BatteryLevel\",\n"
+ + " \"id\":\"BatteryLevel\",\n" + " \"deviceId\":\"hdm:ZigBee:000d6f0004b93361\",\n"
+ + " \"faults\":{ \n" + " \"entries\":[\n" + " {\n"
+ + " \"type\":\"CRITICAL_LOW\",\n" + " \"category\":\"WARNING\"\n"
+ + " }\n" + " ]\n" + " }\n" + "}");
+ getFixture().processUpdate("BatteryLevel", deviceServiceData);
+ verify(getCallback()).stateUpdated(
+ new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_BATTERY_LEVEL),
+ new DecimalType(1));
+ verify(getCallback()).stateUpdated(
+ new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_LOW_BATTERY), OnOffType.ON);
+ }
+
+ @Test
+ public void testProcessUpdate_BatteryLevel_CriticallyLowBattery() {
+ JsonElement deviceServiceData = JsonParser.parseString("{ \n" + " \"@type\":\"DeviceServiceData\",\n"
+ + " \"path\":\"/devices/hdm:ZigBee:000d6f0004b93361/services/BatteryLevel\",\n"
+ + " \"id\":\"BatteryLevel\",\n" + " \"deviceId\":\"hdm:ZigBee:000d6f0004b93361\",\n"
+ + " \"faults\":{ \n" + " \"entries\":[\n" + " {\n"
+ + " \"type\":\"CRITICALLY_LOW_BATTERY\",\n" + " \"category\":\"WARNING\"\n"
+ + " }\n" + " ]\n" + " }\n" + "}");
+ getFixture().processUpdate("BatteryLevel", deviceServiceData);
+ verify(getCallback()).stateUpdated(
+ new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_BATTERY_LEVEL),
+ new DecimalType(1));
+ verify(getCallback()).stateUpdated(
+ new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_LOW_BATTERY), OnOffType.ON);
+ }
+
+ @Test
+ public void testProcessUpdate_BatteryLevel_OK() {
+ JsonElement deviceServiceData = JsonParser.parseString("{ \n" + " \"@type\":\"DeviceServiceData\",\n"
+ + " \"path\":\"/devices/hdm:ZigBee:000d6f0004b93361/services/BatteryLevel\",\n"
+ + " \"id\":\"BatteryLevel\",\n" + " \"deviceId\":\"hdm:ZigBee:000d6f0004b93361\" }");
+ getFixture().processUpdate("BatteryLevel", deviceServiceData);
+ verify(getCallback()).stateUpdated(
+ new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_BATTERY_LEVEL),
+ new DecimalType(100));
+ verify(getCallback()).stateUpdated(
+ new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_LOW_BATTERY), OnOffType.OFF);
+ }
+
+ @Test
+ public void testProcessUpdate_BatteryLevel_NotAvailable() {
+ JsonElement deviceServiceData = JsonParser.parseString("{ \n" + " \"@type\":\"DeviceServiceData\",\n"
+ + " \"path\":\"/devices/hdm:ZigBee:000d6f0004b93361/services/BatteryLevel\",\n"
+ + " \"id\":\"BatteryLevel\",\n" + " \"deviceId\":\"hdm:ZigBee:000d6f0004b93361\",\n"
+ + " \"faults\":{ \n" + " \"entries\":[\n" + " {\n"
+ + " \"type\":\"NOT_AVAILABLE\",\n" + " \"category\":\"WARNING\"\n"
+ + " }\n" + " ]\n" + " }\n" + "}");
+ getFixture().processUpdate("BatteryLevel", deviceServiceData);
+ verify(getCallback()).stateUpdated(
+ new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_BATTERY_LEVEL), UnDefType.UNDEF);
+ verify(getCallback()).stateUpdated(
+ new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_LOW_BATTERY), OnOffType.OFF);
+ }
+}
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.thing.ChannelUID;
-import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.ThingUID;
import com.google.gson.JsonElement;
public void testHandleCommand()
throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
- when(getThing().getUID()).thenReturn(new ThingUID("boschshc", "abcdef"));
-
getFixture().handleCommand(new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_POWER_SWITCH),
OnOffType.ON);
verify(getBridgeHandler()).putState(eq(getDeviceID()), eq("PowerSwitch"), serviceStateCaptor.capture());
assertSame(PowerSwitchState.OFF, state.switchState);
}
- protected abstract ThingTypeUID getThingTypeUID();
-
@Test
public void testUpdateChannel_PowerSwitchState() {
- when(getThing().getUID()).thenReturn(new ThingUID("boschshc", "abcdef"));
-
JsonElement jsonObject = JsonParser
.parseString("{\n" + " \"@type\": \"powerSwitchState\",\n" + " \"switchState\": \"ON\"\n" + "}");
getFixture().processUpdate("PowerSwitch", jsonObject);
@Test
public void testUpdateChannel_PowerMeterServiceState() {
- when(getThing().getUID()).thenReturn(new ThingUID("boschshc", "abcdef"));
-
JsonElement jsonObject = JsonParser.parseString("{\n" + " \"@type\": \"powerMeterState\",\n"
+ " \"powerConsumption\": \"23\",\n" + " \"energyConsumption\": 42\n" + "}");
getFixture().processUpdate("PowerMeter", jsonObject);
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.ThingStatusInfo;
+import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.ThingUID;
import org.openhab.core.thing.binding.ThingHandlerCallback;
@BeforeEach
public void beforeEach() {
fixture = createFixture();
+ lenient().when(thing.getUID()).thenReturn(getThingUID());
when(thing.getBridgeUID()).thenReturn(new ThingUID("boschshc", "shc", "myBridgeUID"));
when(callback.getBridge(any())).thenReturn(bridge);
fixture.setCallback(callback);
return fixture;
}
+ protected ThingUID getThingUID() {
+ return new ThingUID(getThingTypeUID(), "abcdef");
+ }
+
+ protected abstract ThingTypeUID getThingTypeUID();
+
protected Configuration getConfiguration() {
return new Configuration();
}
@Test
void getServiceUrl() {
- assertEquals("https://127.0.0.1:8444/smarthome/devices/testDevice/services/testService/state",
+ assertEquals("https://127.0.0.1:8444/smarthome/devices/testDevice/services/testService",
httpClient.getServiceUrl("testService", "testDevice"));
}
+ @Test
+ void getServiceStateUrl() {
+ assertEquals("https://127.0.0.1:8444/smarthome/devices/testDevice/services/testService/state",
+ httpClient.getServiceStateUrl("testService", "testDevice"));
+ }
+
@Test
void isAccessPossible() throws InterruptedException {
assertFalse(httpClient.isAccessPossible());
--- /dev/null
+/**
+ * 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.motiondetector;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.boschshc.internal.devices.AbstractBatteryPoweredDeviceHandlerTest;
+import org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants;
+import org.openhab.core.thing.ThingTypeUID;
+
+/**
+ * Unit Tests for {@link MotionDetectorHandler}.
+ *
+ * @author David Pace - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class MotionDetectorHandlerTest extends AbstractBatteryPoweredDeviceHandlerTest<MotionDetectorHandler> {
+
+ @Override
+ protected MotionDetectorHandler createFixture() {
+ return new MotionDetectorHandler(getThing());
+ }
+
+ @Override
+ protected String getDeviceID() {
+ return "hdm:ZigBee:000d6f0012fd2571";
+ }
+
+ @Override
+ protected ThingTypeUID getThingTypeUID() {
+ return BoschSHCBindingConstants.THING_TYPE_MOTION_DETECTOR;
+ }
+}
--- /dev/null
+/**
+ * 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.thermostat;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.boschshc.internal.devices.AbstractBatteryPoweredDeviceHandlerTest;
+import org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants;
+import org.openhab.core.thing.ThingTypeUID;
+
+/**
+ * Unit Tests for {@link ThermostatHandler}.
+ *
+ * @author David Pace - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class ThermostatHandlerTest extends AbstractBatteryPoweredDeviceHandlerTest<ThermostatHandler> {
+
+ @Override
+ protected ThermostatHandler createFixture() {
+ return new ThermostatHandler(getThing());
+ }
+
+ @Override
+ protected String getDeviceID() {
+ return "hdm:ZigBee:000d6f0017f1ace2";
+ }
+
+ @Override
+ protected ThingTypeUID getThingTypeUID() {
+ return BoschSHCBindingConstants.THING_TYPE_THERMOSTAT;
+ }
+}
--- /dev/null
+/**
+ * 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.twinguard;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.boschshc.internal.devices.AbstractBatteryPoweredDeviceHandlerTest;
+import org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants;
+import org.openhab.core.thing.ThingTypeUID;
+
+/**
+ * Unit Tests for {@link TwinguardHandler}.
+ *
+ * @author David Pace - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class TwinguardHandlerTest extends AbstractBatteryPoweredDeviceHandlerTest<TwinguardHandler> {
+
+ @Override
+ protected TwinguardHandler createFixture() {
+ return new TwinguardHandler(getThing());
+ }
+
+ @Override
+ protected String getDeviceID() {
+ return "hdm:ZigBee:000d6f0016d1a193";
+ }
+
+ @Override
+ protected ThingTypeUID getThingTypeUID() {
+ return BoschSHCBindingConstants.THING_TYPE_TWINGUARD;
+ }
+}
--- /dev/null
+/**
+ * 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.wallthermostat;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.boschshc.internal.devices.AbstractBatteryPoweredDeviceHandlerTest;
+import org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants;
+import org.openhab.core.thing.ThingTypeUID;
+
+/**
+ * Unit Tests for {@link WallThermostatHandler}.
+ *
+ * @author David Pace - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class WallThermostatHandlerTest extends AbstractBatteryPoweredDeviceHandlerTest<WallThermostatHandler> {
+
+ @Override
+ protected WallThermostatHandler createFixture() {
+ return new WallThermostatHandler(getThing());
+ }
+
+ @Override
+ protected String getDeviceID() {
+ return "hdm:ZigBee:000d6f0016d1a193";
+ }
+
+ @Override
+ protected ThingTypeUID getThingTypeUID() {
+ return BoschSHCBindingConstants.THING_TYPE_WALL_THERMOSTAT;
+ }
+}
--- /dev/null
+/**
+ * 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.windowcontact;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.boschshc.internal.devices.AbstractBatteryPoweredDeviceHandlerTest;
+import org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants;
+import org.openhab.core.thing.ThingTypeUID;
+
+/**
+ * Unit Tests for {@link WindowContactHandler}.
+ *
+ * @author David Pace - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class WindowContactHandlerTest extends AbstractBatteryPoweredDeviceHandlerTest<WindowContactHandler> {
+
+ @Override
+ protected WindowContactHandler createFixture() {
+ return new WindowContactHandler(getThing());
+ }
+
+ @Override
+ protected String getDeviceID() {
+ return "hdm:HomeMaticIP:3014D711A000009D545DEB39D";
+ }
+
+ @Override
+ protected ThingTypeUID getThingTypeUID() {
+ return BoschSHCBindingConstants.THING_TYPE_WINDOW_CONTACT;
+ }
+}
--- /dev/null
+/**
+ * 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 static org.junit.jupiter.api.Assertions.*;
+
+import java.util.ArrayList;
+
+import org.junit.jupiter.api.Test;
+import org.openhab.binding.boschshc.internal.devices.bridge.dto.DeviceServiceData;
+import org.openhab.binding.boschshc.internal.devices.bridge.dto.Fault;
+import org.openhab.binding.boschshc.internal.devices.bridge.dto.Faults;
+import org.openhab.core.library.types.DecimalType;
+import org.openhab.core.library.types.OnOffType;
+import org.openhab.core.types.UnDefType;
+
+/**
+ * Unit tests for {@link BatteryLevel}.
+ *
+ * @author David Pace - Initial contribution
+ *
+ */
+class BatteryLevelTest {
+
+ @Test
+ void testGet() {
+ assertSame(BatteryLevel.OK, BatteryLevel.get("OK"));
+ assertSame(BatteryLevel.OK, BatteryLevel.get("ok"));
+ assertSame(BatteryLevel.LOW_BATTERY, BatteryLevel.get("LOW_BATTERY"));
+ assertSame(BatteryLevel.LOW_BATTERY, BatteryLevel.get("low_battery"));
+ assertSame(BatteryLevel.CRITICAL_LOW, BatteryLevel.get("CRITICAL_LOW"));
+ assertSame(BatteryLevel.CRITICAL_LOW, BatteryLevel.get("critical_low"));
+ assertSame(BatteryLevel.CRITICALLY_LOW_BATTERY, BatteryLevel.get("CRITICALLY_LOW_BATTERY"));
+ assertSame(BatteryLevel.CRITICALLY_LOW_BATTERY, BatteryLevel.get("critically_low_battery"));
+ assertSame(BatteryLevel.NOT_AVAILABLE, BatteryLevel.get("NOT_AVAILABLE"));
+ assertSame(BatteryLevel.NOT_AVAILABLE, BatteryLevel.get("not_available"));
+ assertNull(BatteryLevel.get("foo"));
+ }
+
+ @Test
+ void testFromDeviceServiceData() {
+ DeviceServiceData deviceServiceData = new DeviceServiceData();
+ assertSame(BatteryLevel.OK, BatteryLevel.fromDeviceServiceData(deviceServiceData));
+
+ Faults faults = new Faults();
+ deviceServiceData.faults = faults;
+ assertSame(BatteryLevel.OK, BatteryLevel.fromDeviceServiceData(deviceServiceData));
+
+ ArrayList<Fault> entries = new ArrayList<>();
+ faults.entries = entries;
+ assertSame(BatteryLevel.OK, BatteryLevel.fromDeviceServiceData(deviceServiceData));
+
+ Fault fault = new Fault();
+ entries.add(fault);
+ assertSame(BatteryLevel.OK, BatteryLevel.fromDeviceServiceData(deviceServiceData));
+
+ fault.category = "WARNING";
+ fault.type = "LOW_BATTERY";
+ assertSame(BatteryLevel.LOW_BATTERY, BatteryLevel.fromDeviceServiceData(deviceServiceData));
+
+ fault.type = "CRITICAL_LOW";
+ assertSame(BatteryLevel.CRITICAL_LOW, BatteryLevel.fromDeviceServiceData(deviceServiceData));
+
+ fault.type = "CRITICALLY_LOW_BATTERY";
+ assertSame(BatteryLevel.CRITICALLY_LOW_BATTERY, BatteryLevel.fromDeviceServiceData(deviceServiceData));
+
+ fault.type = "FOO";
+ assertSame(BatteryLevel.OK, BatteryLevel.fromDeviceServiceData(deviceServiceData));
+ }
+
+ @Test
+ void testToState() {
+ assertEquals(new DecimalType(100), BatteryLevel.OK.toState());
+ assertEquals(new DecimalType(10), BatteryLevel.LOW_BATTERY.toState());
+ assertEquals(new DecimalType(1), BatteryLevel.CRITICAL_LOW.toState());
+ assertEquals(new DecimalType(1), BatteryLevel.CRITICALLY_LOW_BATTERY.toState());
+ assertEquals(UnDefType.UNDEF, BatteryLevel.NOT_AVAILABLE.toState());
+ }
+
+ @Test
+ void testToLowBatteryState() {
+ assertEquals(OnOffType.OFF, BatteryLevel.OK.toLowBatteryState());
+ assertEquals(OnOffType.ON, BatteryLevel.LOW_BATTERY.toLowBatteryState());
+ assertEquals(OnOffType.ON, BatteryLevel.CRITICAL_LOW.toLowBatteryState());
+ assertEquals(OnOffType.ON, BatteryLevel.CRITICALLY_LOW_BATTERY.toLowBatteryState());
+ assertEquals(OnOffType.OFF, BatteryLevel.NOT_AVAILABLE.toLowBatteryState());
+ }
+}