]> git.basschouten.com Git - openhab-addons.git/commitdiff
[boschshc] Support for Bosch Intrusion Detection System (#12700) (#12758)
authorDavid Pace <dev@davidpace.de>
Sun, 22 May 2022 19:45:55 +0000 (21:45 +0200)
committerGitHub <noreply@github.com>
Sun, 22 May 2022 19:45:55 +0000 (21:45 +0200)
* [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>
41 files changed:
bundles/org.openhab.binding.boschshc/README.md
bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/BoschSHCBindingConstants.java
bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/BoschSHCDeviceHandler.java [new file with mode: 0644]
bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/BoschSHCHandler.java
bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/BoschSHCHandlerFactory.java
bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/BridgeHandler.java
bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/camera/CameraHandler.java
bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/climatecontrol/ClimateControlHandler.java
bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/intrusion/IntrusionDetectionHandler.java [new file with mode: 0644]
bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/lightcontrol/LightControlHandler.java
bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/motiondetector/MotionDetectorHandler.java
bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/shuttercontrol/ShutterControlHandler.java
bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/thermostat/ThermostatHandler.java
bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/twinguard/TwinguardHandler.java
bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/wallthermostat/WallThermostatHandler.java
bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/windowcontact/WindowContactHandler.java
bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/AbstractBoschSHCService.java [new file with mode: 0644]
bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/AbstractStatelessBoschSHCService.java [new file with mode: 0644]
bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/AbstractStatelessBoschSHCServiceWithRequestBody.java [new file with mode: 0644]
bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/BoschSHCService.java
bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/BoschSHCSystemService.java [new file with mode: 0644]
bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/intrusion/IntrusionDetectionControlStateService.java [new file with mode: 0644]
bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/intrusion/IntrusionDetectionSystemStateService.java [new file with mode: 0644]
bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/intrusion/SurveillanceAlarmService.java [new file with mode: 0644]
bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/intrusion/actions/arm/ArmActionService.java [new file with mode: 0644]
bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/intrusion/actions/arm/dto/ArmActionRequest.java [new file with mode: 0644]
bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/intrusion/actions/disarm/DisarmActionService.java [new file with mode: 0644]
bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/intrusion/actions/mute/MuteActionService.java [new file with mode: 0644]
bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/intrusion/dto/ActiveConfigurationProfileData.java [new file with mode: 0644]
bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/intrusion/dto/AlarmState.java [new file with mode: 0644]
bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/intrusion/dto/AlarmStateData.java [new file with mode: 0644]
bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/intrusion/dto/ArmingState.java [new file with mode: 0644]
bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/intrusion/dto/ArmingStateData.java [new file with mode: 0644]
bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/intrusion/dto/IntrusionDetectionControlState.java [new file with mode: 0644]
bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/intrusion/dto/IntrusionDetectionSystemState.java [new file with mode: 0644]
bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/intrusion/dto/SurveillanceAlarmState.java [new file with mode: 0644]
bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/intrusion/dto/SystemAvailabilityStateData.java [new file with mode: 0644]
bundles/org.openhab.binding.boschshc/src/main/resources/OH-INF/thing/thing-types.xml
bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/devices/bridge/BridgeHandlerTest.java [new file with mode: 0644]
bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/services/intrusion/IntrusionDetectionControlStateServiceTest.java [new file with mode: 0644]
bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/services/intrusion/IntrusionDetectionSystemStateServiceTest.java [new file with mode: 0644]

index d8dbbe8408928d38d8019d1e0bc12684b200db94..9331afa9fd0c6d8a5208b82e598b4a1ce6b39d9f 100644 (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
index a942c520539d837e3e2a67fbc7f4f2454f015b40..6b3cf4e3eb5c65569de726d2e5702923843d40a9 100644 (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";
 }
diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/BoschSHCDeviceHandler.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/BoschSHCDeviceHandler.java
new file mode 100644 (file)
index 0000000..a7e1fc8
--- /dev/null
@@ -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;
+    }
+}
index c88199deb0a4dc6f144cda647eb047bb23b73ef4..5fbbe50a65341c9bc395afa7981680705bcb0773 100644 (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()));
+        }
+    }
 }
index 0bfb53c6ece9b8d1166c66295279208aae11c057..b686acb5172faae2edd0ebbcb691e41f2f40e275 100644 (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) {
index 508de1d20e764643bac640f77599ede779c9cac6..a7a134ed049eb3a21b1d091540c40c4af6356531 100644 (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();
+    }
 }
index 06eb5f0a1c6316548f38bdfb7058b92034fbe4d8..b50086de85aa528e74266e7560cc7f7accb2859c 100644 (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
index ee56dea3680cfc2fd610d0b26b267add95a447a2..904dbc8db68ffd8d868d5838e3db0a5c9c9814b7 100644 (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;
 
diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/intrusion/IntrusionDetectionHandler.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/intrusion/IntrusionDetectionHandler.java
new file mode 100644 (file)
index 0000000..1855381
--- /dev/null
@@ -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);
+        }
+    }
+}
index 038e24009df5024e18228edefe6b9e8845003cb5..2dde138cee857fba0d24d9b1c6b90d0d99b2ddc6 100644 (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;
 
index ef4c309c25ddb21529e392589589cb3e160122bd..f4a088ceca3644d0002c6e855fc1fadc6d4cf2ab 100644 (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);
index 45ae948a80d8039da87a3591677987f478b75f38..f852200d172f3074413c704f61aa693accef6a79 100644 (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
      */
index 33de80c482173d86c2848cb5f8ee5220ff740f58..ff00373bea3ac45839e38199237287ff21d8f8e3 100644 (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;
 
index fd5894c9e9a375ab32a76d17cec11b432cf888f4..d155c18965fb829d6e5963eb59e02bff051db78b 100644 (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);
index a3a084a673df1aecaa147705b0b7012a6ab7a8f8..411c52ebb7a5286b9810048f3ef8a7cc94928e44 100644 (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);
index 1013de75567e7743e311c211db56e4e1a7960cba..08a34099cae7803a55a670f1e2ab2d16d289fe43 100644 (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);
diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/AbstractBoschSHCService.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/AbstractBoschSHCService.java
new file mode 100644 (file)
index 0000000..c7b3e50
--- /dev/null
@@ -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;
+    }
+}
diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/AbstractStatelessBoschSHCService.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/AbstractStatelessBoschSHCService.java
new file mode 100644 (file)
index 0000000..debdd1f
--- /dev/null
@@ -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;
+    }
+}
diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/AbstractStatelessBoschSHCServiceWithRequestBody.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/AbstractStatelessBoschSHCServiceWithRequestBody.java
new file mode 100644 (file)
index 0000000..e69f7f5
--- /dev/null
@@ -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);
+    }
+}
index 4082816f979ab85cdf7293888e6bd1d4535e9250..d3a0db77dfbf2bb676662dc89bee3e4ffff67941 100644 (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);
     }
 
     /**
diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/BoschSHCSystemService.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/BoschSHCSystemService.java
new file mode 100644 (file)
index 0000000..338e44d
--- /dev/null
@@ -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());
+    }
+}
diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/intrusion/IntrusionDetectionControlStateService.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/intrusion/IntrusionDetectionControlStateService.java
new file mode 100644 (file)
index 0000000..199dae8
--- /dev/null
@@ -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);
+    }
+}
diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/intrusion/IntrusionDetectionSystemStateService.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/intrusion/IntrusionDetectionSystemStateService.java
new file mode 100644 (file)
index 0000000..d220ca8
--- /dev/null
@@ -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");
+    }
+}
diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/intrusion/SurveillanceAlarmService.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/intrusion/SurveillanceAlarmService.java
new file mode 100644 (file)
index 0000000..9946fa0
--- /dev/null
@@ -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);
+    }
+}
diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/intrusion/actions/arm/ArmActionService.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/intrusion/actions/arm/ArmActionService.java
new file mode 100644 (file)
index 0000000..3a37af1
--- /dev/null
@@ -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");
+    }
+}
diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/intrusion/actions/arm/dto/ArmActionRequest.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/intrusion/actions/arm/dto/ArmActionRequest.java
new file mode 100644 (file)
index 0000000..645059b
--- /dev/null
@@ -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;
+}
diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/intrusion/actions/disarm/DisarmActionService.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/intrusion/actions/disarm/DisarmActionService.java
new file mode 100644 (file)
index 0000000..8d76a04
--- /dev/null
@@ -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");
+    }
+}
diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/intrusion/actions/mute/MuteActionService.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/intrusion/actions/mute/MuteActionService.java
new file mode 100644 (file)
index 0000000..7f89307
--- /dev/null
@@ -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");
+    }
+}
diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/intrusion/dto/ActiveConfigurationProfileData.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/intrusion/dto/ActiveConfigurationProfileData.java
new file mode 100644 (file)
index 0000000..48a7cd6
--- /dev/null
@@ -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;
+}
diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/intrusion/dto/AlarmState.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/intrusion/dto/AlarmState.java
new file mode 100644 (file)
index 0000000..0f493d4
--- /dev/null
@@ -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;
+}
diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/intrusion/dto/AlarmStateData.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/intrusion/dto/AlarmStateData.java
new file mode 100644 (file)
index 0000000..b1bcb62
--- /dev/null
@@ -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;
+}
diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/intrusion/dto/ArmingState.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/intrusion/dto/ArmingState.java
new file mode 100644 (file)
index 0000000..86bde20
--- /dev/null
@@ -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;
+}
diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/intrusion/dto/ArmingStateData.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/intrusion/dto/ArmingStateData.java
new file mode 100644 (file)
index 0000000..b1139ec
--- /dev/null
@@ -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;
+}
diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/intrusion/dto/IntrusionDetectionControlState.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/intrusion/dto/IntrusionDetectionControlState.java
new file mode 100644 (file)
index 0000000..a774fbb
--- /dev/null
@@ -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;
+}
diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/intrusion/dto/IntrusionDetectionSystemState.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/intrusion/dto/IntrusionDetectionSystemState.java
new file mode 100644 (file)
index 0000000..b89cff0
--- /dev/null
@@ -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;
+}
diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/intrusion/dto/SurveillanceAlarmState.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/intrusion/dto/SurveillanceAlarmState.java
new file mode 100644 (file)
index 0000000..0ed5238
--- /dev/null
@@ -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;
+}
diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/intrusion/dto/SystemAvailabilityStateData.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/intrusion/dto/SystemAvailabilityStateData.java
new file mode 100644 (file)
index 0000000..2d79bda
--- /dev/null
@@ -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;
+}
index a50b94e457827fed7dc2cf43bcd45cfc84e69b44..34c55fc801c580e99db0ecfea6d7d28140516d2a 100644 (file)
 
        </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>
diff --git a/bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/devices/bridge/BridgeHandlerTest.java b/bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/devices/bridge/BridgeHandlerTest.java
new file mode 100644 (file)
index 0000000..20507b8
--- /dev/null
@@ -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();
+    }
+}
diff --git a/bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/services/intrusion/IntrusionDetectionControlStateServiceTest.java b/bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/services/intrusion/IntrusionDetectionControlStateServiceTest.java
new file mode 100644 (file)
index 0000000..422758c
--- /dev/null
@@ -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);
+    }
+}
diff --git a/bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/services/intrusion/IntrusionDetectionSystemStateServiceTest.java b/bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/services/intrusion/IntrusionDetectionSystemStateServiceTest.java
new file mode 100644 (file)
index 0000000..86229ed
--- /dev/null
@@ -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);
+    }
+}