[boschshc] Support for Bosch Intrusion Detection System (#12700) (#12758)

* [boschshc] Support for Bosch Intrusion Detection System (#12700)

* Add thing and channel definitions for intrusion detection system
* Extract handler abstraction for devices with non-configurable device
IDs
* Extract service abstractions for write-only services (i.e. services
that can not receive states from the bridge)
* Add handler, services and DTO implementations for the intrusion
detection system
* Add detailed Javadocs
* Generalize mechanism to actively fetch initial states for certain
services
* Add unit tests
* Add documentation

closes #12700

* [boschshc] Documentation and formatting enhancements

Signed-off-by: David Pace <dev@davidpace.de>
This commit is contained in:
David Pace 2022-05-22 21:45:55 +02:00 committed by GitHub
parent 50831fff14
commit c16e9df7a0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
41 changed files with 1828 additions and 164 deletions

View File

@ -14,6 +14,7 @@ Binding for the Bosch Smart Home.
- [Wall Thermostat](#wall-thermostat)
- [Security Camera 360](#security-camera-360)
- [Security Camera Eyes](#security-camera-eyes)
- [Intrusion Detection System](#intrusion-detection-system)
- [Limitations](#limitations)
- [Discovery](#discovery)
- [Bridge Configuration](#bridge-configuration)
@ -138,6 +139,22 @@ Outdoor security camera with motion detection and light.
| privacy-mode | Switch | &#9745; | If privacy mode is enabled, the camera is disabled and vice versa. |
| camera-notification | Switch | &#9745; | Enables or disables notifications for the camera. |
### Intrusion Detection System
Allows to retrieve notifications in case of intrusions. The system can be armed and disarmed and alarms can be muted.
**Thing Type ID**: `intrusion-detection-system`
| Channel Type ID | Item Type | Writable | Description |
| ---------------------------- | -------------------- | :------: | -------------------------------------------------------------- |
| system-availability | Switch | &#9744; | Indicates whether the intrusion detection system is available. |
| arming-state | String | &#9744; | Read-only channel to retrieve the current arming state. Possible values are `SYSTEM_ARMING`, `SYSTEM_ARMED` and `SYSTEM_DISARMED`. |
| alarm-state | String | &#9744; | Read-only channel to retrieve the current alarm state. Possible values are `ALARM_OFF`, `PRE_ALARM`, `ALARM_ON`, `ALARM_MUTED` and `UNKNOWN`. |
| active-configuration-profile | String | &#9744; | The name of the active configuration profile used for the intrusion detection system. |
| arm-action | String | &#9745; | Arms the intrusion detection system using the given profile ID (default is "0"). |
| disarm-action | Switch | &#9745; | Disarms the intrusion detection system when an ON command is received. |
| mute-action | Switch | &#9745; | Mutes the alarm when an ON command is received. |
## Limitations
- Discovery of Things

View File

@ -22,6 +22,7 @@ import org.openhab.core.thing.ThingTypeUID;
* @author Stefan Kästle - Initial contribution
* @author Christian Oeing - added Shutter Control, ThermostatHandler
* @author Christian Oeing - Added WallThermostatHandler
* @author David Pace - Added cameras and intrusion detection system
*/
@NonNullByDefault
public class BoschSHCBindingConstants {
@ -41,6 +42,8 @@ public class BoschSHCBindingConstants {
public static final ThingTypeUID THING_TYPE_WALL_THERMOSTAT = new ThingTypeUID(BINDING_ID, "wall-thermostat");
public static final ThingTypeUID THING_TYPE_CAMERA_360 = new ThingTypeUID(BINDING_ID, "security-camera-360");
public static final ThingTypeUID THING_TYPE_CAMERA_EYES = new ThingTypeUID(BINDING_ID, "security-camera-eyes");
public static final ThingTypeUID THING_TYPE_INTRUSION_DETECTION_SYSTEM = new ThingTypeUID(BINDING_ID,
"intrusion-detection-system");
// List of all Channel IDs
// Auto-generated from thing-types.xml via script, don't modify
@ -63,4 +66,14 @@ public class BoschSHCBindingConstants {
public static final String CHANNEL_CHILD_LOCK = "child-lock";
public static final String CHANNEL_PRIVACY_MODE = "privacy-mode";
public static final String CHANNEL_CAMERA_NOTIFICATION = "camera-notification";
public static final String CHANNEL_SYSTEM_AVAILABILITY = "system-availability";
public static final String CHANNEL_ARMING_STATE = "arming-state";
public static final String CHANNEL_ALARM_STATE = "alarm-state";
public static final String CHANNEL_ACTIVE_CONFIGURATION_PROFILE = "active-configuration-profile";
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";
// static device/service names
public static final String SERVICE_INTRUSION_DETECTION = "intrusionDetectionSystem";
}

View File

@ -0,0 +1,95 @@
/**
* 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 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.exceptions.BoschSHCException;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
/**
* Handler for physical Bosch devices with configurable IDs (as opposed to system services, which have static IDs).
* <p>
* The device ID of physical devices has to be configured in the thing configuration.
* <p>
* Examples for physical device IDs are:
*
* <pre>
* hdm:Cameras:d20354de-44b5-3acc-924c-24c98d59da42
* hdm:ZigBee:000d6f0016d1cdae
* </pre>
*
* @author Stefan Kästle - Initial contribution
* @author Christian Oeing - refactorings of e.g. server registration
* @author David Pace - Handler abstraction
*
*/
@NonNullByDefault
public class BoschSHCDeviceHandler extends BoschSHCHandler {
/**
* Bosch SHC configuration loaded from openHAB configuration.
*/
private @Nullable BoschSHCConfiguration config;
public BoschSHCDeviceHandler(Thing thing) {
super(thing);
}
@Override
public void initialize() {
var config = this.config = getConfigAs(BoschSHCConfiguration.class);
String deviceId = config.id;
if (deviceId == null || deviceId.isBlank()) {
this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"@text/offline.conf-error.empty-device-id");
return;
}
// Try to get device info to make sure the device exists
try {
var bridgeHandler = this.getBridgeHandler();
var info = bridgeHandler.getDeviceInfo(deviceId);
logger.trace("Device initialized:\n{}", info);
} catch (TimeoutException | ExecutionException | BoschSHCException e) {
this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage());
return;
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage());
return;
}
super.initialize();
}
/**
* Returns the unique id of the Bosch device.
*
* @return Unique id of the Bosch device.
*/
public @Nullable String getBoschID() {
if (config != null) {
return config.id;
}
return null;
}
}

View File

@ -24,6 +24,9 @@ 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.exceptions.BoschSHCException;
import org.openhab.binding.boschshc.internal.services.AbstractBoschSHCService;
import org.openhab.binding.boschshc.internal.services.AbstractStatelessBoschSHCService;
import org.openhab.binding.boschshc.internal.services.AbstractStatelessBoschSHCServiceWithRequestBody;
import org.openhab.binding.boschshc.internal.services.BoschSHCService;
import org.openhab.binding.boschshc.internal.services.dto.BoschSHCServiceState;
import org.openhab.core.thing.Bridge;
@ -42,10 +45,11 @@ import com.google.gson.JsonElement;
/**
* The {@link BoschSHCHandler} represents Bosch Things. Each type of device
* inherits from this abstract thing handler.
* or system service inherits from this abstract thing handler.
*
* @author Stefan Kästle - Initial contribution
* @author Christian Oeing - refactorings of e.g. server registration
* @author David Pace - Handler abstraction
*/
@NonNullByDefault
public abstract class BoschSHCHandler extends BaseThingHandler {
@ -83,33 +87,36 @@ public abstract class BoschSHCHandler extends BaseThingHandler {
protected final Logger logger = LoggerFactory.getLogger(getClass());
/**
* Bosch SHC configuration loaded from openHAB configuration.
*/
private @Nullable BoschSHCConfiguration config;
/**
* Services of the device.
*/
private List<DeviceService<? extends BoschSHCServiceState>> services = new ArrayList<>();
public BoschSHCHandler(Thing thing) {
protected BoschSHCHandler(Thing thing) {
super(thing);
}
/**
* Returns the unique id of the Bosch device.
* Returns the unique id of the Bosch device or service.
* <p>
* For physical devices, the ID looks like
*
* <pre>
* hdm:Cameras:d20354de-44b5-3acc-924c-24c98d59da42
* hdm:ZigBee:000d6f0016d1c087
* </pre>
*
* For virtual devices / services, static IDs like the following are used:
*
* <pre>
* ventilationService
* smokeDetectionSystem
* intrusionDetectionSystem
* </pre>
*
* @return Unique id of the Bosch device.
* @return Unique ID of the Bosch device or service.
*/
public @Nullable String getBoschID() {
BoschSHCConfiguration config = this.config;
if (config != null) {
return config.id;
} else {
return null;
}
}
public abstract @Nullable String getBoschID();
/**
* Initializes this handler. Use this method to register all services of the device with
@ -117,24 +124,6 @@ public abstract class BoschSHCHandler extends BaseThingHandler {
*/
@Override
public void initialize() {
var config = this.config = getConfigAs(BoschSHCConfiguration.class);
String deviceId = config.id;
if (deviceId == null || deviceId.isEmpty()) {
this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"@text/offline.conf-error.empty-device-id");
return;
}
// Try to get device info to make sure the device exists
try {
var bridgeHandler = this.getBridgeHandler();
var info = bridgeHandler.getDeviceInfo(deviceId);
logger.trace("Device initialized:\n{}", info.toString());
} catch (InterruptedException | TimeoutException | ExecutionException | BoschSHCException e) {
this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage());
return;
}
// Initialize device services
try {
@ -273,16 +262,95 @@ public abstract class BoschSHCHandler extends BaseThingHandler {
protected <TService extends BoschSHCService<TState>, TState extends BoschSHCServiceState> void registerService(
TService service, Consumer<TState> stateUpdateListener, Collection<String> affectedChannels)
throws BoschSHCException {
BridgeHandler bridgeHandler = this.getBridgeHandler();
registerService(service, stateUpdateListener, affectedChannels, false);
}
/**
* Registers a service for this device.
*
* @param <TService> Type of service.
* @param <TState> Type of service state.
* @param service Service to register.
* @param stateUpdateListener Function to call when a state update was received
* from the device.
* @param affectedChannels Channels which are affected by the state of this
* service.
* @param shouldFetchInitialState indicates whether the initial state should be actively requested from the device
* or service. Useful if state updates are not included in long poll results.
* @throws BoschSHCException If bridge for handler is not set or an invalid bridge is set.
* @throws BoschSHCException If no device id is set.
*/
protected <TService extends BoschSHCService<TState>, TState extends BoschSHCServiceState> void registerService(
TService service, Consumer<TState> stateUpdateListener, Collection<String> affectedChannels,
boolean shouldFetchInitialState) throws BoschSHCException {
String deviceId = verifyBoschID();
service.initialize(getBridgeHandler(), deviceId, stateUpdateListener);
this.registerService(service, affectedChannels);
if (shouldFetchInitialState) {
fetchInitialState(service, stateUpdateListener);
}
}
/**
* Actively requests the initial state for the given service. This is required if long poll results do not contain
* status updates for the given service.
*
* @param <TService> Type of the service for which the state should be obtained
* @param <TState> Type of the objects to serialize and deserialize the service state
* @param service Service for which the state should be requested
* @param stateUpdateListener Function to process the obtained state
*/
private <TService extends BoschSHCService<TState>, TState extends BoschSHCServiceState> void fetchInitialState(
TService service, Consumer<TState> stateUpdateListener) {
try {
@Nullable
TState serviceState = service.getState();
if (serviceState != null) {
stateUpdateListener.accept(serviceState);
}
} catch (TimeoutException | ExecutionException | BoschSHCException e) {
logger.debug("Could not retrieve the initial state for service {} of device {}", service.getServiceName(),
getBoschID());
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
logger.debug("Could not retrieve the initial state for service {} of device {}", service.getServiceName(),
getBoschID());
}
}
/**
* Registers a write-only service that does not receive states from the bridge.
* <p>
* Examples for such services are the actions of the intrusion detection service.
*
* @param <TService> Type of service.
* @param service Service to register.
* @throws BoschSHCException If no device ID is set.
*/
protected <TService extends AbstractBoschSHCService> void registerStatelessService(TService service)
throws BoschSHCException {
String deviceId = verifyBoschID();
service.initialize(getBridgeHandler(), deviceId);
// do not register in service list because the service can not receive state updates
}
/**
* Verifies that a Bosch device or service ID is set and throws an exception if this is not the case.
*
* @return the Bosch ID, if present
* @throws BoschSHCException if no Bosch ID is set
*/
private String verifyBoschID() throws BoschSHCException {
String deviceId = this.getBoschID();
if (deviceId == null) {
throw new BoschSHCException(
String.format("Could not register service for %s, no device id set", this.getThing()));
}
service.initialize(bridgeHandler, deviceId, stateUpdateListener);
this.registerService(service, affectedChannels);
return deviceId;
}
/**
@ -367,4 +435,45 @@ public abstract class BoschSHCHandler extends BaseThingHandler {
Collection<String> affectedChannels) {
this.services.add(new DeviceService<TState>(service, affectedChannels));
}
/**
* Sends a HTTP POST request with empty body.
*
* @param <TService> Type of service.
* @param service Service implementing the action
*/
protected <TService extends AbstractStatelessBoschSHCService> void postAction(TService service) {
try {
service.postAction();
} catch (ExecutionException | TimeoutException e) {
this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
String.format("Error while triggering action %s", service.getEndpoint()));
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
String.format("Error while triggering action %s", service.getEndpoint()));
}
}
/**
* Sends a HTTP POST request with the given request body.
*
* @param <TService> Type of service.
* @param <TState> Type of the request to be sent.
* @param service Service implementing the action
* @param request Request object to be serialized to JSON
*/
protected <TService extends AbstractStatelessBoschSHCServiceWithRequestBody<TState>, TState extends BoschSHCServiceState> void postAction(
TService service, TState request) {
try {
service.postAction(request);
} catch (ExecutionException | TimeoutException e) {
this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
String.format("Error while triggering action %s", service.getEndpoint()));
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
String.format("Error while triggering action %s", service.getEndpoint()));
}
}
}

View File

@ -23,6 +23,7 @@ import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.boschshc.internal.devices.bridge.BridgeHandler;
import org.openhab.binding.boschshc.internal.devices.camera.CameraHandler;
import org.openhab.binding.boschshc.internal.devices.climatecontrol.ClimateControlHandler;
import org.openhab.binding.boschshc.internal.devices.intrusion.IntrusionDetectionHandler;
import org.openhab.binding.boschshc.internal.devices.lightcontrol.LightControlHandler;
import org.openhab.binding.boschshc.internal.devices.motiondetector.MotionDetectorHandler;
import org.openhab.binding.boschshc.internal.devices.shuttercontrol.ShutterControlHandler;
@ -46,6 +47,7 @@ import org.osgi.service.component.annotations.Component;
* @author Stefan Kästle - Initial contribution
* @author Christian Oeing - Added Shutter Control and ThermostatHandler; refactored handler mapping
* @author Christian Oeing - Added WallThermostatHandler
* @author David Pace - Added cameras and intrusion detection system
*/
@NonNullByDefault
@Component(configurationPid = "binding.boschshc", service = ThingHandlerFactory.class)
@ -72,7 +74,8 @@ public class BoschSHCHandlerFactory extends BaseThingHandlerFactory {
new ThingTypeHandlerMapping(THING_TYPE_CLIMATE_CONTROL, ClimateControlHandler::new),
new ThingTypeHandlerMapping(THING_TYPE_WALL_THERMOSTAT, WallThermostatHandler::new),
new ThingTypeHandlerMapping(THING_TYPE_CAMERA_360, CameraHandler::new),
new ThingTypeHandlerMapping(THING_TYPE_CAMERA_EYES, CameraHandler::new));
new ThingTypeHandlerMapping(THING_TYPE_CAMERA_EYES, CameraHandler::new),
new ThingTypeHandlerMapping(THING_TYPE_INTRUSION_DETECTION_SYSTEM, IntrusionDetectionHandler::new));
@Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) {

View File

@ -59,6 +59,7 @@ import com.google.gson.reflect.TypeToken;
* @author Stefan Kästle - Initial contribution
* @author Gerd Zanker - added HttpClient with pairing support
* @author Christian Oeing - refactorings of e.g. server registration
* @author David Pace - Added support for custom endpoints and HTTP POST requests
*/
@NonNullByDefault
public class BridgeHandler extends BaseBridgeHandler {
@ -75,7 +76,13 @@ public class BridgeHandler extends BaseBridgeHandler {
*/
private final LongPolling longPolling;
private @Nullable BoschHttpClient httpClient;
/**
* HTTP client for all communications to and from the bridge.
* <p>
* This member is package-protected to enable mocking in unit tests.
*/
/* package */ @Nullable
BoschHttpClient httpClient;
private @Nullable ScheduledFuture<?> scheduledPairing;
@ -300,14 +307,14 @@ public class BridgeHandler extends BaseBridgeHandler {
private void handleLongPollResult(LongPollResult result) {
for (DeviceStatusUpdate update : result.result) {
if (update != null && update.state != null) {
logger.debug("Got update of type {}: {}", update.type, update.state);
logger.debug("Got update for service {} of type {}: {}", update.id, update.type, update.state);
var updateDeviceId = update.deviceId;
if (updateDeviceId == null) {
continue;
}
logger.debug("Got update for {}", updateDeviceId);
logger.debug("Got update for device {}", updateDeviceId);
boolean handled = false;
@ -437,11 +444,18 @@ public class BridgeHandler extends BaseBridgeHandler {
}
/**
* Query the Bosch Smart Home Controller for the state of the given thing.
* 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>
*
* @param deviceId Id of device to get state for
* @param stateName Name of the state to query
* @param stateClass Class to convert the resulting JSON to
* @return the deserialized state object, may be <code>null</code>
* @throws ExecutionException
* @throws TimeoutException
* @throws InterruptedException
@ -457,26 +471,68 @@ public class BridgeHandler extends BaseBridgeHandler {
}
String url = httpClient.getServiceUrl(stateName, deviceId);
Request request = httpClient.createRequest(url, GET).header("Accept", "application/json");
logger.debug("getState(): Requesting \"{}\" from Bosch: {} via {}", stateName, deviceId, url);
return getState(httpClient, url, stateClass);
}
logger.debug("refreshState: Requesting \"{}\" from Bosch: {} via {}", stateName, deviceId, url);
/**
* 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
* @return the deserialized state object, may be <code>null</code>
* @throws InterruptedException
* @throws TimeoutException
* @throws ExecutionException
* @throws BoschSHCException
*/
public <T extends BoschSHCServiceState> @Nullable T getState(String endpoint, Class<T> stateClass)
throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
@Nullable
BoschHttpClient httpClient = this.httpClient;
if (httpClient == null) {
logger.warn("HttpClient not initialized");
return null;
}
String url = httpClient.getBoschSmartHomeUrl(endpoint);
logger.debug("getState(): Requesting from Bosch: {}", url);
return getState(httpClient, url, stateClass);
}
/**
* 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
* @param stateClass Class to convert the resulting JSON to
* @return the deserialized state object, may be <code>null</code>
* @throws InterruptedException
* @throws TimeoutException
* @throws ExecutionException
* @throws BoschSHCException
*/
protected <T extends BoschSHCServiceState> @Nullable T getState(BoschHttpClient httpClient, String url,
Class<T> stateClass) throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
Request request = httpClient.createRequest(url, GET).header("Accept", "application/json");
ContentResponse contentResponse = request.send();
String content = contentResponse.getContentAsString();
logger.debug("refreshState: Request complete: [{}] - return code: {}", content, contentResponse.getStatus());
logger.debug("getState(): Request complete: [{}] - return code: {}", content, contentResponse.getStatus());
int statusCode = contentResponse.getStatus();
if (statusCode != 200) {
JsonRestExceptionResponse errorResponse = gson.fromJson(content, JsonRestExceptionResponse.class);
if (errorResponse != null) {
throw new BoschSHCException(String.format(
"State request for service %s of device %s failed with status code %d and error code %s",
stateName, deviceId, errorResponse.statusCode, errorResponse.errorCode));
throw new BoschSHCException(
String.format("State request with URL %s failed with status code %d and error code %s", url,
errorResponse.statusCode, errorResponse.errorCode));
} else {
throw new BoschSHCException(
String.format("State request for service %s of device %s failed with status code %d", stateName,
deviceId, statusCode));
String.format("State request with URL %s failed with status code %d", url, statusCode));
}
}
@ -516,4 +572,43 @@ public class BridgeHandler extends BaseBridgeHandler {
// Send request
return request.send();
}
/**
* 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
* @throws TimeoutException
* @throws ExecutionException
*/
public @Nullable Response postAction(String endpoint)
throws InterruptedException, TimeoutException, ExecutionException {
return postAction(endpoint, null);
}
/**
* 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>
* @return the HTTP response
* @throws InterruptedException
* @throws TimeoutException
* @throws ExecutionException
*/
public <T extends BoschSHCServiceState> @Nullable Response postAction(String endpoint, @Nullable T requestBody)
throws InterruptedException, TimeoutException, ExecutionException {
@Nullable
BoschHttpClient httpClient = this.httpClient;
if (httpClient == null) {
logger.warn("HttpClient not initialized");
return null;
}
String url = httpClient.getBoschSmartHomeUrl(endpoint);
Request request = httpClient.createRequest(url, POST, requestBody);
return request.send();
}
}

View File

@ -16,12 +16,9 @@ import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConst
import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.CHANNEL_PRIVACY_MODE;
import java.util.List;
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.BoschSHCHandler;
import org.openhab.binding.boschshc.internal.devices.BoschSHCDeviceHandler;
import org.openhab.binding.boschshc.internal.exceptions.BoschSHCException;
import org.openhab.binding.boschshc.internal.services.cameranotification.CameraNotificationService;
import org.openhab.binding.boschshc.internal.services.cameranotification.CameraNotificationState;
@ -54,7 +51,7 @@ import org.openhab.core.types.Command;
*
*/
@NonNullByDefault
public class CameraHandler extends BoschSHCHandler {
public class CameraHandler extends BoschSHCDeviceHandler {
private PrivacyModeService privacyModeService;
private CameraNotificationService cameraNotificationService;
@ -69,60 +66,9 @@ public class CameraHandler extends BoschSHCHandler {
protected void initializeServices() throws BoschSHCException {
super.initializeServices();
this.registerService(this.privacyModeService, this::updateChannels, List.of(CHANNEL_PRIVACY_MODE));
this.registerService(this.cameraNotificationService, this::updateChannels,
List.of(CHANNEL_CAMERA_NOTIFICATION));
}
@Override
public void initialize() {
super.initialize();
requestInitialStates();
}
/**
* Requests the initial states for relevant services.
* <p>
* If this is not done, items associated with the corresponding channels with stay in an uninitialized state
* (<code>null</code>).
* This in turn leads to events not being fired properly when switches are used in the UI.
* <p>
* Unfortunately the long poll results do not contain camera-related updates, so this is the current approach
* to get the initial states.
*/
private void requestInitialStates() {
requestInitialPrivacyState();
requestInitialNotificationState();
}
private void requestInitialPrivacyState() {
try {
@Nullable
PrivacyModeServiceState serviceState = privacyModeService.getState();
if (serviceState != null) {
super.updateState(CHANNEL_PRIVACY_MODE, serviceState.value.toOnOffType());
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
logger.debug("Could not retrieve the initial privacy state of camera {}", getBoschID());
} catch (TimeoutException | ExecutionException | BoschSHCException e) {
logger.debug("Could not retrieve the initial privacy state of camera {}", getBoschID());
}
}
private void requestInitialNotificationState() {
try {
@Nullable
CameraNotificationServiceState serviceState = cameraNotificationService.getState();
if (serviceState != null) {
super.updateState(CHANNEL_CAMERA_NOTIFICATION, serviceState.value.toOnOffType());
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
logger.debug("Could not retrieve the initial notification state of camera {}", getBoschID());
} catch (TimeoutException | ExecutionException | BoschSHCException e) {
logger.debug("Could not retrieve the initial notification state of camera {}", getBoschID());
}
this.registerService(this.privacyModeService, this::updateChannels, List.of(CHANNEL_PRIVACY_MODE), true);
this.registerService(this.cameraNotificationService, this::updateChannels, List.of(CHANNEL_CAMERA_NOTIFICATION),
true);
}
@Override

View File

@ -18,7 +18,7 @@ import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConst
import java.util.List;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.boschshc.internal.devices.BoschSHCHandler;
import org.openhab.binding.boschshc.internal.devices.BoschSHCDeviceHandler;
import org.openhab.binding.boschshc.internal.exceptions.BoschSHCException;
import org.openhab.binding.boschshc.internal.services.roomclimatecontrol.RoomClimateControlService;
import org.openhab.binding.boschshc.internal.services.roomclimatecontrol.dto.RoomClimateControlServiceState;
@ -36,7 +36,7 @@ import org.openhab.core.types.Command;
* @author Christian Oeing - Initial contribution
*/
@NonNullByDefault
public final class ClimateControlHandler extends BoschSHCHandler {
public final class ClimateControlHandler extends BoschSHCDeviceHandler {
private RoomClimateControlService roomClimateControlService;

View File

@ -0,0 +1,159 @@
/**
* 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.intrusion;
import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.CHANNEL_ACTIVE_CONFIGURATION_PROFILE;
import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.CHANNEL_ALARM_STATE;
import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.CHANNEL_ARMING_STATE;
import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.CHANNEL_ARM_ACTION;
import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.CHANNEL_DISARM_ACTION;
import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.CHANNEL_MUTE_ACTION;
import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.CHANNEL_SYSTEM_AVAILABILITY;
import java.util.List;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants;
import org.openhab.binding.boschshc.internal.devices.BoschSHCHandler;
import org.openhab.binding.boschshc.internal.exceptions.BoschSHCException;
import org.openhab.binding.boschshc.internal.services.intrusion.IntrusionDetectionControlStateService;
import org.openhab.binding.boschshc.internal.services.intrusion.IntrusionDetectionSystemStateService;
import org.openhab.binding.boschshc.internal.services.intrusion.SurveillanceAlarmService;
import org.openhab.binding.boschshc.internal.services.intrusion.actions.arm.ArmActionService;
import org.openhab.binding.boschshc.internal.services.intrusion.actions.arm.dto.ArmActionRequest;
import org.openhab.binding.boschshc.internal.services.intrusion.actions.disarm.DisarmActionService;
import org.openhab.binding.boschshc.internal.services.intrusion.actions.mute.MuteActionService;
import org.openhab.binding.boschshc.internal.services.intrusion.dto.IntrusionDetectionControlState;
import org.openhab.binding.boschshc.internal.services.intrusion.dto.IntrusionDetectionSystemState;
import org.openhab.binding.boschshc.internal.services.intrusion.dto.SurveillanceAlarmState;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.types.Command;
/**
* Handler for the intrusion detection alarm system.
* <p>
* It supports
* <ul>
* <li>Obtaining the current intrusion detection system state</li>
* <li>Receiving updates related to the detection control state</li>
* <li>Receiving updates related to surveillance alarm events</li>
* <li>Arming the system</li>
* <li>Disarming the system</li>
* <li>Muting the alarm</li>
* </ul>
*
* @author David Pace - Initial contribution
*
*/
@NonNullByDefault
public class IntrusionDetectionHandler extends BoschSHCHandler {
private IntrusionDetectionSystemStateService intrusionDetectionSystemStateService;
private IntrusionDetectionControlStateService intrusionDetectionControlStateService;
private SurveillanceAlarmService surveillanceAlarmService;
private ArmActionService armActionService;
private DisarmActionService disarmActionService;
private MuteActionService muteActionService;
public IntrusionDetectionHandler(Thing thing) {
super(thing);
this.intrusionDetectionSystemStateService = new IntrusionDetectionSystemStateService();
this.intrusionDetectionControlStateService = new IntrusionDetectionControlStateService();
this.surveillanceAlarmService = new SurveillanceAlarmService();
this.armActionService = new ArmActionService();
this.disarmActionService = new DisarmActionService();
this.muteActionService = new MuteActionService();
}
@Override
public @Nullable String getBoschID() {
return BoschSHCBindingConstants.SERVICE_INTRUSION_DETECTION;
}
@Override
protected void initializeServices() throws BoschSHCException {
super.initializeServices();
this.registerService(intrusionDetectionSystemStateService, this::updateChannels,
List.of(CHANNEL_SYSTEM_AVAILABILITY, CHANNEL_ARMING_STATE, CHANNEL_ALARM_STATE,
CHANNEL_ACTIVE_CONFIGURATION_PROFILE),
true);
this.registerService(intrusionDetectionControlStateService, this::updateChannels,
List.of(CHANNEL_ARMING_STATE));
this.registerService(surveillanceAlarmService, this::updateChannels, List.of(CHANNEL_ALARM_STATE));
this.registerStatelessService(armActionService);
this.registerStatelessService(disarmActionService);
this.registerStatelessService(muteActionService);
}
private void updateChannels(IntrusionDetectionSystemState systemState) {
super.updateState(CHANNEL_SYSTEM_AVAILABILITY, OnOffType.from(systemState.systemAvailability.available));
super.updateState(CHANNEL_ARMING_STATE, new StringType(systemState.armingState.state.toString()));
super.updateState(CHANNEL_ALARM_STATE, new StringType(systemState.alarmState.value.toString()));
super.updateState(CHANNEL_ACTIVE_CONFIGURATION_PROFILE,
new StringType(systemState.activeConfigurationProfile.profileId));
}
private void updateChannels(IntrusionDetectionControlState controlState) {
super.updateState(CHANNEL_ARMING_STATE, new StringType(controlState.value.toString()));
}
private void updateChannels(SurveillanceAlarmState surveillanceAlarmState) {
super.updateState(CHANNEL_ALARM_STATE, new StringType(surveillanceAlarmState.value.toString()));
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
super.handleCommand(channelUID, command);
switch (channelUID.getId()) {
case CHANNEL_ARM_ACTION:
if (command instanceof StringType) {
armIntrusionDetectionSystem((StringType) command);
}
break;
case CHANNEL_DISARM_ACTION:
if (command instanceof OnOffType) {
disarmIntrusionDetectionSystem((OnOffType) command);
}
break;
case CHANNEL_MUTE_ACTION:
if (command instanceof OnOffType) {
muteIntrusionDetectionSystem((OnOffType) command);
}
break;
}
}
private void armIntrusionDetectionSystem(StringType profileIdCommand) {
ArmActionRequest armActionRequest = new ArmActionRequest();
armActionRequest.profileId = profileIdCommand.toFullString();
postAction(armActionService, armActionRequest);
}
private void disarmIntrusionDetectionSystem(OnOffType command) {
if (command == OnOffType.ON) {
postAction(disarmActionService);
}
}
private void muteIntrusionDetectionSystem(OnOffType command) {
if (command == OnOffType.ON) {
postAction(muteActionService);
}
}
}

View File

@ -20,7 +20,7 @@ import javax.measure.quantity.Energy;
import javax.measure.quantity.Power;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.boschshc.internal.devices.BoschSHCHandler;
import org.openhab.binding.boschshc.internal.devices.BoschSHCDeviceHandler;
import org.openhab.binding.boschshc.internal.exceptions.BoschSHCException;
import org.openhab.binding.boschshc.internal.services.powermeter.PowerMeterService;
import org.openhab.binding.boschshc.internal.services.powermeter.dto.PowerMeterServiceState;
@ -41,7 +41,7 @@ import org.openhab.core.types.State;
* @author Stefan Kästle - Initial contribution
*/
@NonNullByDefault
public class LightControlHandler extends BoschSHCHandler {
public class LightControlHandler extends BoschSHCDeviceHandler {
private final PowerSwitchService powerSwitchService;

View File

@ -17,7 +17,7 @@ import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConst
import java.util.List;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.boschshc.internal.devices.BoschSHCHandler;
import org.openhab.binding.boschshc.internal.devices.BoschSHCDeviceHandler;
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;
@ -32,7 +32,7 @@ import org.openhab.core.thing.Thing;
* @author Christian Oeing - Use service instead of custom logic
*/
@NonNullByDefault
public class MotionDetectorHandler extends BoschSHCHandler {
public class MotionDetectorHandler extends BoschSHCDeviceHandler {
public MotionDetectorHandler(Thing thing) {
super(thing);

View File

@ -17,7 +17,7 @@ import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConst
import java.util.List;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.boschshc.internal.devices.BoschSHCHandler;
import org.openhab.binding.boschshc.internal.devices.BoschSHCDeviceHandler;
import org.openhab.binding.boschshc.internal.exceptions.BoschSHCException;
import org.openhab.binding.boschshc.internal.services.shuttercontrol.OperationState;
import org.openhab.binding.boschshc.internal.services.shuttercontrol.ShutterControlService;
@ -35,7 +35,7 @@ import org.openhab.core.types.Command;
* @author Christian Oeing - Initial contribution
*/
@NonNullByDefault
public class ShutterControlHandler extends BoschSHCHandler {
public class ShutterControlHandler extends BoschSHCDeviceHandler {
/**
* Utility functions to convert data between Bosch things and openHAB items
*/

View File

@ -19,7 +19,7 @@ import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConst
import java.util.List;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.boschshc.internal.devices.BoschSHCHandler;
import org.openhab.binding.boschshc.internal.devices.BoschSHCDeviceHandler;
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;
@ -37,7 +37,7 @@ import org.openhab.core.types.Command;
* @author Christian Oeing - Initial contribution
*/
@NonNullByDefault
public final class ThermostatHandler extends BoschSHCHandler {
public final class ThermostatHandler extends BoschSHCDeviceHandler {
private ChildLockService childLockService;

View File

@ -27,7 +27,7 @@ import javax.measure.quantity.Dimensionless;
import javax.measure.quantity.Temperature;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.boschshc.internal.devices.BoschSHCHandler;
import org.openhab.binding.boschshc.internal.devices.BoschSHCDeviceHandler;
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;
@ -44,7 +44,7 @@ import org.openhab.core.thing.Thing;
* @author Christian Oeing - Use service instead of custom logic
*/
@NonNullByDefault
public class TwinguardHandler extends BoschSHCHandler {
public class TwinguardHandler extends BoschSHCDeviceHandler {
public TwinguardHandler(Thing thing) {
super(thing);

View File

@ -18,7 +18,7 @@ import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConst
import java.util.List;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.boschshc.internal.devices.BoschSHCHandler;
import org.openhab.binding.boschshc.internal.devices.BoschSHCDeviceHandler;
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;
@ -32,7 +32,7 @@ import org.openhab.core.thing.Thing;
* @author Christian Oeing - Initial contribution
*/
@NonNullByDefault
public final class WallThermostatHandler extends BoschSHCHandler {
public final class WallThermostatHandler extends BoschSHCDeviceHandler {
public WallThermostatHandler(Thing thing) {
super(thing);

View File

@ -17,7 +17,7 @@ import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConst
import java.util.List;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.boschshc.internal.devices.BoschSHCHandler;
import org.openhab.binding.boschshc.internal.devices.BoschSHCDeviceHandler;
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;
@ -32,7 +32,7 @@ import org.openhab.core.types.State;
* @author Stefan Kästle - Initial contribution
*/
@NonNullByDefault
public class WindowContactHandler extends BoschSHCHandler {
public class WindowContactHandler extends BoschSHCDeviceHandler {
public WindowContactHandler(Thing thing) {
super(thing);

View File

@ -0,0 +1,72 @@
/**
* 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;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.boschshc.internal.devices.bridge.BridgeHandler;
/**
* Base class for Bosch Smart Home services containing what all services have in common.
* <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
*/
@NonNullByDefault
public abstract class AbstractBoschSHCService {
/**
* Unique service name
*/
private final String serviceName;
/**
* Bridge to use for communication from/to the device
*/
private @Nullable BridgeHandler bridgeHandler;
/**
* Id of device the service belongs to
*/
private @Nullable String deviceId;
protected AbstractBoschSHCService(String serviceName) {
this.serviceName = serviceName;
}
/**
* Initializes the service
*
* @param bridgeHandler Bridge to use for communication from/to the device
* @param deviceId Id of device this service is for
*/
public void initialize(BridgeHandler bridgeHandler, String deviceId) {
this.bridgeHandler = bridgeHandler;
this.deviceId = deviceId;
}
public String getServiceName() {
return serviceName;
}
protected @Nullable BridgeHandler getBridgeHandler() {
return bridgeHandler;
}
protected @Nullable String getDeviceId() {
return deviceId;
}
}

View File

@ -0,0 +1,69 @@
/**
* 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;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeoutException;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.boschshc.internal.devices.bridge.BridgeHandler;
/**
* Abstract implementation for services that only allow setting states via HTTP POST requests.
* State-less services can not receive any states from the bridge.
* <p>
* This implementation does not support request bodies when submitting the POST request.
* Request bodies are supported by the subclass {@link AbstractStatelessBoschSHCServiceWithRequestBody}.
* <p>
* Examples for this kind of service are the following actions of the intrusion detection system:
*
* <pre>
* /intrusion/actions/arm
* /intrusion/actions/disarm
* /intrusion/actions/mute
* </pre>
* <p>
* The services of the devices and their official APIs can be found
* <a href="https://apidocs.bosch-smarthome.com/local/">here</a>.
*
* @author David Pace - Initial contribution
*/
@NonNullByDefault
public abstract class AbstractStatelessBoschSHCService extends AbstractBoschSHCService {
private String endpoint;
protected AbstractStatelessBoschSHCService(String serviceName, String endpoint) {
super(serviceName);
this.endpoint = endpoint;
}
/**
* Sends a HTTP POST request without request body to the endpoint specified by {@link #endpoint}.
*
* @throws InterruptedException
* @throws TimeoutException
* @throws ExecutionException
*/
public void postAction() throws InterruptedException, TimeoutException, ExecutionException {
BridgeHandler bridgeHandler = getBridgeHandler();
if (bridgeHandler == null)
return;
bridgeHandler.postAction(endpoint);
}
public String getEndpoint() {
return endpoint;
}
}

View File

@ -0,0 +1,60 @@
/**
* 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;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeoutException;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.boschshc.internal.devices.bridge.BridgeHandler;
import org.openhab.binding.boschshc.internal.services.dto.BoschSHCServiceState;
/**
* Abstract implementation for services that allow setting states via HTTP POST requests containing a JSON request body.
* State-less services can not receive any states from the bridge.
* <p>
* An example of such a service is the <code>arm</code> action of the intrusion detection system.
* <p>
* The services of the devices and their official APIs can be found
* <a href="https://apidocs.bosch-smarthome.com/local/">here</a>.
*
* @param <TRequest> Type to represent JSON requests sent by this service
*
* @author David Pace - Initial contribution
*/
@NonNullByDefault
public abstract class AbstractStatelessBoschSHCServiceWithRequestBody<TRequest extends BoschSHCServiceState>
extends AbstractStatelessBoschSHCService {
protected AbstractStatelessBoschSHCServiceWithRequestBody(String serviceName, String endpoint) {
super(serviceName, endpoint);
}
/**
* Sends a HTTP POST request containing the serialized request body to the endpoint specified by
* {@link #getEndpoint()}.
*
* @param request a JSON object representing the request body
* @throws InterruptedException
* @throws TimeoutException
* @throws ExecutionException
*/
public void postAction(TRequest request) throws InterruptedException, TimeoutException, ExecutionException {
BridgeHandler bridgeHandler = getBridgeHandler();
if (bridgeHandler == null) {
return;
}
bridgeHandler.postAction(getEndpoint(), request);
}
}

View File

@ -28,37 +28,34 @@ import org.slf4j.LoggerFactory;
import com.google.gson.JsonElement;
/**
* Base class of a service of a Bosch Smart Home device. The services of the
* devices and their official APIs can be found here:
* https://apidocs.bosch-smarthome.com/local/
* Abstract implementation of a service that supports reading and writing its state using the same JSON message and the
* 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
*/
@NonNullByDefault
public abstract class BoschSHCService<TState extends BoschSHCServiceState> {
public abstract class BoschSHCService<TState extends BoschSHCServiceState> extends AbstractBoschSHCService {
protected final Logger logger = LoggerFactory.getLogger(BoschSHCService.class);
/**
* Unique service name
*/
private final String serviceName;
/**
* Class of service state
*/
private final Class<TState> stateClass;
/**
* Bridge to use for communication from/to the device
*/
private @Nullable BridgeHandler bridgeHandler;
/**
* Id of device the service belongs to
*/
private @Nullable String deviceId;
/**
* Function to call after receiving state updates from the device
*/
@ -72,7 +69,7 @@ public abstract class BoschSHCService<TState extends BoschSHCServiceState> {
* from/to the device.
*/
protected BoschSHCService(String serviceName, Class<TState> stateClass) {
this.serviceName = serviceName;
super(serviceName);
this.stateClass = stateClass;
}
@ -86,20 +83,10 @@ public abstract class BoschSHCService<TState extends BoschSHCServiceState> {
*/
public void initialize(BridgeHandler bridgeHandler, String deviceId,
@Nullable Consumer<TState> stateUpdateListener) {
this.bridgeHandler = bridgeHandler;
this.deviceId = deviceId;
super.initialize(bridgeHandler, deviceId);
this.stateUpdateListener = stateUpdateListener;
}
/**
* Returns the unique name of this service.
*
* @return Unique name of the service.
*/
public String getServiceName() {
return this.serviceName;
}
/**
* Returns the class of the state this service provides.
*
@ -136,15 +123,15 @@ public abstract class BoschSHCService<TState extends BoschSHCServiceState> {
*/
public @Nullable TState getState()
throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
String deviceId = this.deviceId;
String deviceId = getDeviceId();
if (deviceId == null) {
return null;
}
BridgeHandler bridgeHandler = this.bridgeHandler;
BridgeHandler bridgeHandler = getBridgeHandler();
if (bridgeHandler == null) {
return null;
}
return bridgeHandler.getState(deviceId, this.serviceName, this.stateClass);
return bridgeHandler.getState(deviceId, getServiceName(), getStateClass());
}
/**
@ -156,15 +143,15 @@ public abstract class BoschSHCService<TState extends BoschSHCServiceState> {
* @throws TimeoutException
*/
public void setState(TState state) throws InterruptedException, TimeoutException, ExecutionException {
String deviceId = this.deviceId;
String deviceId = getDeviceId();
if (deviceId == null) {
return;
}
BridgeHandler bridgeHandler = this.bridgeHandler;
BridgeHandler bridgeHandler = getBridgeHandler();
if (bridgeHandler == null) {
return;
}
bridgeHandler.putState(deviceId, this.serviceName, state);
bridgeHandler.putState(deviceId, getServiceName(), state);
}
/**

View File

@ -0,0 +1,80 @@
/**
* 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;
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.exceptions.BoschSHCException;
import org.openhab.binding.boschshc.internal.services.dto.BoschSHCServiceState;
/**
* Abstract implementation of a system service that does not represent a physical device.
* Examples for system services are the intrusion detection system and the water detection system.
* <p>
* The endpoints to retrieve system states are different from the ones for physical devices, i.e. they do not follow the
* pattern
*
* <pre>
* https://{IP}:8444/smarthome/devices/{deviceId}/services/{serviceName}/state
* </pre>
*
* Instead, system services have endpoints like
*
* <pre>
* /intrusion/states/system
* </pre>
*
* <p>
* The services of the devices and their official APIs can be found
* <a href="https://apidocs.bosch-smarthome.com/local/">here</a>.
*
* @param <TState> type used for representing the service state
*
* @author David Pace - Initial contribution
*/
@NonNullByDefault
public abstract class BoschSHCSystemService<TState extends BoschSHCServiceState> extends BoschSHCService<TState> {
private String endpoint;
/**
* Constructs a system service instance.
*
* @param serviceName name of the service, such as <code>intrusionDetectionService</code>
* @param stateClass the class representing states of the system
* @param endpoint the part of the URL after <code>https://{IP}:8444/smarthome/</code>, e.g.
* <code>intrusion/states/system</code>
*/
protected BoschSHCSystemService(String serviceName, Class<TState> stateClass, String endpoint) {
super(serviceName, stateClass);
this.endpoint = endpoint;
}
/**
* Uses the endpoint directly instead of constructing a device-specific URL.
*/
@Override
public @Nullable TState getState()
throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
BridgeHandler bridgeHandler = getBridgeHandler();
if (bridgeHandler == null) {
return null;
}
return bridgeHandler.getState(this.endpoint, getStateClass());
}
}

View File

@ -0,0 +1,31 @@
/**
* 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.intrusion;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.boschshc.internal.services.BoschSHCService;
import org.openhab.binding.boschshc.internal.services.intrusion.dto.IntrusionDetectionControlState;
/**
* Allows to retrieve the control state of the intrusion detection system.
*
* @author David Pace - Initial contribution
*
*/
@NonNullByDefault
public class IntrusionDetectionControlStateService extends BoschSHCService<IntrusionDetectionControlState> {
public IntrusionDetectionControlStateService() {
super("IntrusionDetectionControl", IntrusionDetectionControlState.class);
}
}

View File

@ -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.services.intrusion;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants;
import org.openhab.binding.boschshc.internal.services.BoschSHCSystemService;
import org.openhab.binding.boschshc.internal.services.intrusion.dto.IntrusionDetectionSystemState;
/**
* Allows to retrieve the system state of the intrusion detection system.
*
* @author David Pace - Initial contribution
*
*/
@NonNullByDefault
public class IntrusionDetectionSystemStateService extends BoschSHCSystemService<IntrusionDetectionSystemState> {
public IntrusionDetectionSystemStateService() {
super(BoschSHCBindingConstants.SERVICE_INTRUSION_DETECTION, IntrusionDetectionSystemState.class,
"intrusion/states/system");
}
}

View File

@ -0,0 +1,31 @@
/**
* 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.intrusion;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.boschshc.internal.services.BoschSHCService;
import org.openhab.binding.boschshc.internal.services.intrusion.dto.SurveillanceAlarmState;
/**
* Service to handle intrusion detection events.
*
* @author David Pace - Initial contribution
*
*/
@NonNullByDefault
public class SurveillanceAlarmService extends BoschSHCService<SurveillanceAlarmState> {
public SurveillanceAlarmService() {
super("SurveillanceAlarm", SurveillanceAlarmState.class);
}
}

View File

@ -0,0 +1,32 @@
/**
* 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.intrusion.actions.arm;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants;
import org.openhab.binding.boschshc.internal.services.AbstractStatelessBoschSHCServiceWithRequestBody;
import org.openhab.binding.boschshc.internal.services.intrusion.actions.arm.dto.ArmActionRequest;
/**
* Service to arm the intrusion detection system using a specified profile.
*
* @author David Pace - Initial contribution
*
*/
@NonNullByDefault
public class ArmActionService extends AbstractStatelessBoschSHCServiceWithRequestBody<ArmActionRequest> {
public ArmActionService() {
super(BoschSHCBindingConstants.SERVICE_INTRUSION_DETECTION, "intrusion/actions/arm");
}
}

View File

@ -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.services.intrusion.actions.arm.dto;
import org.openhab.binding.boschshc.internal.services.dto.BoschSHCServiceState;
/**
* DTO for arming the intrusion detection system using a specified profile.
*
* @author David Pace - Initial contribution
*
*/
public class ArmActionRequest extends BoschSHCServiceState {
public ArmActionRequest() {
super("armRequest");
}
/**
* The ID of the profile to be used for arming the system.
*/
public String profileId;
}

View File

@ -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.services.intrusion.actions.disarm;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants;
import org.openhab.binding.boschshc.internal.services.AbstractStatelessBoschSHCService;
/**
* Service for disarming the intrusion detection system.
* <p>
* This service does not require a DTO because it uses a simple HTTP POST request without a request body.
*
* @author David Pace - Initial contribution
*
*/
@NonNullByDefault
public class DisarmActionService extends AbstractStatelessBoschSHCService {
public DisarmActionService() {
super(BoschSHCBindingConstants.SERVICE_INTRUSION_DETECTION, "intrusion/actions/disarm");
}
}

View File

@ -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.services.intrusion.actions.mute;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants;
import org.openhab.binding.boschshc.internal.services.AbstractStatelessBoschSHCService;
/**
* Service to mute the intrusion detection system.
* <p>
* This service does not require a DTO because it uses a simple HTTP POST request without a request body.
*
* @author David Pace - Initial contribution
*
*/
@NonNullByDefault
public class MuteActionService extends AbstractStatelessBoschSHCService {
public MuteActionService() {
super(BoschSHCBindingConstants.SERVICE_INTRUSION_DETECTION, "intrusion/actions/mute");
}
}

View File

@ -0,0 +1,27 @@
/**
* 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.intrusion.dto;
/**
* Represents the active configuration profile of the intrusion detection system.
*
* @author David Pace - Initial contribution
*
*/
public class ActiveConfigurationProfileData {
/**
* The ID of the active configuration profile (default: <code>"0"</code>)
*/
public String profileId;
}

View File

@ -0,0 +1,30 @@
/**
* 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.intrusion.dto;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* Possible alarm states of the intrusion detection system.
*
* @author David Pace - Initial contribution
*
*/
@NonNullByDefault
public enum AlarmState {
ALARM_OFF,
PRE_ALARM,
ALARM_ON,
ALARM_MUTED,
UNKNOWN;
}

View File

@ -0,0 +1,47 @@
/**
* 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.intrusion.dto;
/**
* DTO for the alarm state of the intrusion detection system.
* <p>
* Example data:
*
* <pre>
* "alarmState": {
* "value": "ALARM_OFF",
* "incidents": [
* {
* "id": "string",
* "triggerName": "string",
* "type": "SYSTEM_ARMED",
* "time": 0,
* "location": "string",
* "locationId": "string"
* }
* ],
* "deleted": true,
* "id": "string"
* }
* </pre>
*
* <p>
* <b>Note:</b> Incidents are not supported yet as they do not seem to be included in the responses from the bridge.
*
* @author David Pace - Initial contribution
*
*/
public class AlarmStateData {
public AlarmState value;
}

View File

@ -0,0 +1,28 @@
/**
* 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.intrusion.dto;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* Possible arming state values of the intrusion detection system.
*
* @author David Pace - Initial contribution
*
*/
@NonNullByDefault
public enum ArmingState {
SYSTEM_ARMED,
SYSTEM_ARMING,
SYSTEM_DISARMED;
}

View File

@ -0,0 +1,37 @@
/**
* 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.intrusion.dto;
/**
* DTO for the arming state of the intrusion detection system.
* <p>
* Example data:
*
* <pre>
* "armingState": {
* "remainingTimeUntilArmed": 0,
* "state": "SYSTEM_ARMING",
* "deleted": true,
* "id": "string"
* }
* </pre>
*
* @author David Pace - Initial contribution
*
*/
public class ArmingStateData {
public long remainingTimeUntilArmed;
public ArmingState state;
}

View File

@ -0,0 +1,70 @@
/**
* 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.intrusion.dto;
import org.openhab.binding.boschshc.internal.services.dto.BoschSHCServiceState;
/**
* A state which is periodically sent by the intrusion detection control state while arming.
* <p>
* Example data:
*
* <pre>
* {
* "@type": "intrusionDetectionControlState",
* "activeProfile": "0",
* "alarmActivationDelayTime": 30,
* "actuators": [
* {
* "readonly": false,
* "active": true,
* "id": "intrusion:video"
* },
* {
* "readonly": false,
* "active": false,
* "id": "intrusion:siren"
* }
* ],
* "remainingTimeUntilArmed": 29559,
* "armActivationDelayTime": 30,
* "triggers": [
* {
* "readonly": false,
* "active": true,
* "id": "hdm:ZigBee:000d6f0012f02378"
* }
* ],
* "value": "SYSTEM_ARMING"
* }
* </pre>
*
* @author David Pace - Initial contribution
*
*/
public class IntrusionDetectionControlState extends BoschSHCServiceState {
public IntrusionDetectionControlState() {
super("intrusionDetectionControlState");
}
public String activeProfile;
public int alarmActivationDelayTime;
public long remainingTimeUntilArmed;
public int armActivationDelayTime;
public ArmingState value;
}

View File

@ -0,0 +1,70 @@
/**
* 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.intrusion.dto;
import org.openhab.binding.boschshc.internal.services.dto.BoschSHCServiceState;
/**
* Represents the state of the intrusion detection system as reported by the Bosch Smart Home Controller.
* <p>
* Example data:
*
* <pre>
* {
* "@type": "systemState",
* "systemAvailability": {
* "@type": "systemAvailabilityState",
* "available": true,
* "deleted": false
* },
* "armingState": {
* "@type": "armingState",
* "state": "SYSTEM_DISARMED",
* "deleted": false
* },
* "alarmState": {
* "@type": "alarmState",
* "value": "ALARM_OFF",
* "incidents": [],
* "deleted": false
* },
* "activeConfigurationProfile": {
* "@type": "activeConfigurationProfile",
* "deleted": false
* },
* "securityGapState": {
* "@type": "securityGapState",
* "securityGaps": [],
* "deleted": false
* },
* "deleted": false
* }
* </pre>
*
* @author David Pace - Initial contribution
*
*/
public class IntrusionDetectionSystemState extends BoschSHCServiceState {
public IntrusionDetectionSystemState() {
super("systemState");
}
public SystemAvailabilityStateData systemAvailability;
public ArmingStateData armingState;
public AlarmStateData alarmState;
public ActiveConfigurationProfileData activeConfigurationProfile;
}

View File

@ -0,0 +1,56 @@
/**
* 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.intrusion.dto;
import org.openhab.binding.boschshc.internal.services.dto.BoschSHCServiceState;
/**
* A state containing information about intrusion detection events.
* <p>
* Example data:
*
* <pre>
* {
* "@type": "surveillanceAlarmState",
* "incidents": [
* {
* "triggerName": "Motion Detector",
* "locationId": "hz_5",
* "location": "Living Room",
* "id": "hdm:ZigBee:000d6f0012f02342",
* "time": 1652615755336,
* "type": "INTRUSION"
* }
* ],
* "value": "ALARM_ON"
* }
* </pre>
*
* <p>
* <b>Note:</b> This state is not documented in the official Bosch API docs.
* The type of the incidents seems to be very similar to <code>IncidentType</code> documented for
* the system state. However, the type enum seems to be slightly different (<code>INTRUSION</code> instead of
* <code>INTRUSION_DETECTED</code>).
* For this reason incidents are not modeled in this state object for now.
*
* @author David Pace - Initial contribution
*
*/
public class SurveillanceAlarmState extends BoschSHCServiceState {
public SurveillanceAlarmState() {
super("surveillanceAlarmState");
}
public AlarmState value;
}

View File

@ -0,0 +1,34 @@
/**
* 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.intrusion.dto;
/**
* DTO for the availability state of the intrusion detection system.
* <p>
* Example data:
*
* <pre>
* {
* "@type": "systemAvailabilityState",
* "available": true,
* "deleted": false
* }
* </pre>
*
* @author David Pace - Initial contribution
*
*/
public class SystemAvailabilityStateData {
public boolean available;
}

View File

@ -189,6 +189,89 @@
</thing-type>
<thing-type id="intrusion-detection-system">
<supported-bridge-type-refs>
<bridge-type-ref id="shc"/>
</supported-bridge-type-refs>
<label>Intrusion Detection System</label>
<description>Allows to retrieve and control the state of the intrusion detection alarm system.</description>
<channels>
<channel id="system-availability" typeId="system-availability"/>
<channel id="arming-state" typeId="arming-state"/>
<channel id="alarm-state" typeId="alarm-state"/>
<channel id="active-configuration-profile" typeId="active-configuration-profile"/>
<channel id="arm-action" typeId="arm-action"/>
<channel id="disarm-action" typeId="disarm-action"/>
<channel id="mute-action" typeId="mute-action"/>
</channels>
</thing-type>
<channel-type id="system-availability">
<item-type>Switch</item-type>
<label>System Availability</label>
<description>Indicates whether the intrusion detection system is available.</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="arming-state">
<item-type>String</item-type>
<label>Arming State</label>
<description>The arming state of the intrusion detection system. Possible values are SYSTEM_ARMING, SYSTEM_ARMED and
SYSTEM_DISARMED. This channel is read-only. Use the arm-action and disarm-action channels to arm and disarm the
system.</description>
<state readOnly="true">
<options>
<option value="SYSTEM_ARMING">System is currently arming</option>
<option value="SYSTEM_ARMED">System is armed</option>
<option value="SYSTEM_DISARMED">System is disarmed</option>
</options>
</state>
</channel-type>
<channel-type id="alarm-state">
<item-type>String</item-type>
<label>Alarm State</label>
<description>The alarm state of the intrusion detection system. Possible values are ALARM_OFF, PRE_ALARM, ALARM_ON,
ALARM_MUTED and UNKNOWN.</description>
<state readOnly="true">
<options>
<option value="ALARM_OFF">No alarm</option>
<option value="PRE_ALARM">Alarm is about to go off</option>
<option value="ALARM_ON">Alarm was triggered</option>
<option value="ALARM_MUTED">Alarm is muted</option>
<option value="UNKNOWN">Alarm status is unknown</option>
</options>
</state>
</channel-type>
<channel-type id="active-configuration-profile">
<item-type>String</item-type>
<label>Active Configuration Profile</label>
<description>The name of the active configuration profile used for the intrusion detection system.</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="arm-action">
<item-type>String</item-type>
<label>Arm Action</label>
<description>Arms the intrusion detection system using the given profile ID.</description>
</channel-type>
<channel-type id="disarm-action">
<item-type>Switch</item-type>
<label>Disarm Action</label>
<description>Disarms the intrusion detection system when an ON command is received.</description>
</channel-type>
<channel-type id="mute-action">
<item-type>Switch</item-type>
<label>Mute Action</label>
<description>Mutes the alarm when an ON command is received.</description>
</channel-type>
<channel-type id="privacy-mode">
<item-type>Switch</item-type>
<label>Privacy Mode</label>

View File

@ -0,0 +1,72 @@
/**
* 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;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.same;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeoutException;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.http.HttpMethod;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.openhab.binding.boschshc.internal.services.intrusion.actions.arm.dto.ArmActionRequest;
import org.openhab.core.thing.Bridge;
/**
* Unit tests for the {@link BridgeHandler}.
*
* @author David Pace - Initial contribution
*
*/
@NonNullByDefault
class BridgeHandlerTest {
@Nullable
private BridgeHandler fixture;
@Nullable
private BoschHttpClient httpClient;
@BeforeEach
void beforeEach() {
Bridge bridge = mock(Bridge.class);
fixture = new BridgeHandler(bridge);
httpClient = mock(BoschHttpClient.class);
fixture.httpClient = httpClient;
}
@Test
void postAction() throws InterruptedException, TimeoutException, ExecutionException {
String endpoint = "/intrusion/actions/arm";
String url = "https://127.0.0.1:8444/smarthome/intrusion/actions/arm";
when(httpClient.getBoschSmartHomeUrl(endpoint)).thenReturn(url);
Request mockRequest = mock(Request.class);
when(httpClient.createRequest(anyString(), any(), any())).thenReturn(mockRequest);
ArmActionRequest request = new ArmActionRequest();
request.profileId = "0";
fixture.postAction(endpoint, request);
verify(httpClient).createRequest(eq(url), same(HttpMethod.POST), same(request));
verify(mockRequest).send();
}
}

View File

@ -0,0 +1,111 @@
/**
* 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.intrusion;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeoutException;
import java.util.function.Consumer;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants;
import org.openhab.binding.boschshc.internal.devices.bridge.BridgeHandler;
import org.openhab.binding.boschshc.internal.exceptions.BoschSHCException;
import org.openhab.binding.boschshc.internal.services.intrusion.dto.IntrusionDetectionControlState;
import com.google.gson.JsonElement;
import com.google.gson.JsonParser;
/**
* Unit tests for {@link IntrusionDetectionControlStateService}.
*
* @author David Pace - Initial contribution
*
*/
@ExtendWith(MockitoExtension.class)
class IntrusionDetectionControlStateServiceTest {
private IntrusionDetectionControlStateService fixture;
@Mock
private BridgeHandler bridgeHandler;
@Mock
private Consumer<IntrusionDetectionControlState> consumer;
@Mock
private IntrusionDetectionControlState testState;
@BeforeEach
void beforeEach() throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
fixture = new IntrusionDetectionControlStateService();
fixture.initialize(bridgeHandler, BoschSHCBindingConstants.SERVICE_INTRUSION_DETECTION, consumer);
}
@Test
void getServiceName() {
assertEquals("IntrusionDetectionControl", fixture.getServiceName());
}
@Test
void getStateClass() {
assertSame(IntrusionDetectionControlState.class, fixture.getStateClass());
}
@Test
void getState() throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
when(bridgeHandler.getState(anyString(), anyString(), any())).thenReturn(testState);
IntrusionDetectionControlState state = fixture.getState();
verify(bridgeHandler).getState(BoschSHCBindingConstants.SERVICE_INTRUSION_DETECTION,
"IntrusionDetectionControl", IntrusionDetectionControlState.class);
assertSame(testState, state);
}
@Test
void setState() throws InterruptedException, TimeoutException, ExecutionException {
fixture.setState(testState);
verify(bridgeHandler).putState(BoschSHCBindingConstants.SERVICE_INTRUSION_DETECTION,
"IntrusionDetectionControl", testState);
}
@Test
void onStateUpdate() {
final String json = "{\n" + "\"@type\": \"intrusionDetectionControlState\",\n" + "\"activeProfile\": \"0\",\n"
+ "\"alarmActivationDelayTime\": 30,\n" + "\"actuators\": [\n" + "{\n" + "\"readonly\": false,\n"
+ "\"active\": true,\n" + "\"id\": \"intrusion:video\"\n" + "},\n" + "{\n" + "\"readonly\": false,\n"
+ "\"active\": false,\n" + "\"id\": \"intrusion:siren\"\n" + "}\n" + "],\n"
+ "\"remainingTimeUntilArmed\": 28959,\n" + "\"armActivationDelayTime\": 30,\n" + "\"triggers\": [\n"
+ "{\n" + "\"readonly\": false,\n" + "\"active\": true,\n" + "\"id\": \"hdm:ZigBee:000d6f0422f42378\"\n"
+ "}\n" + "],\n" + "\"value\": \"SYSTEM_ARMING\"\n" + "}";
JsonElement jsonElement = JsonParser.parseString(json);
fixture.onStateUpdate(jsonElement);
verify(consumer).accept(any());
}
@Test
void refreshState() throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
when(bridgeHandler.getState(anyString(), anyString(), any())).thenReturn(testState);
fixture.refreshState();
verify(consumer).accept(testState);
}
}

View File

@ -0,0 +1,68 @@
/**
* 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.intrusion;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeoutException;
import java.util.function.Consumer;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants;
import org.openhab.binding.boschshc.internal.devices.bridge.BridgeHandler;
import org.openhab.binding.boschshc.internal.exceptions.BoschSHCException;
import org.openhab.binding.boschshc.internal.services.intrusion.dto.IntrusionDetectionSystemState;
/**
* Unit tests for {@link IntrusionDetectionSystemStateService}.
*
* @author David Pace - Initial contribution
*
*/
@ExtendWith(MockitoExtension.class)
class IntrusionDetectionSystemStateServiceTest {
private IntrusionDetectionSystemStateService fixture;
@Mock
private BridgeHandler bridgeHandler;
@Mock
private Consumer<IntrusionDetectionSystemState> consumer;
@Mock
private IntrusionDetectionSystemState testState;
@BeforeEach
void beforeEach() {
fixture = new IntrusionDetectionSystemStateService();
fixture.initialize(bridgeHandler, BoschSHCBindingConstants.SERVICE_INTRUSION_DETECTION, consumer);
}
@Test
void getState() throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
when(bridgeHandler.getState(anyString(), any())).thenReturn(testState);
IntrusionDetectionSystemState state = fixture.getState();
verify(bridgeHandler).getState("intrusion/states/system", IntrusionDetectionSystemState.class);
assertSame(testState, state);
}
}