]> git.basschouten.com Git - openhab-addons.git/commitdiff
[boschshc] Support for Smart Water Alarm (#16770)
authorDavid Pace <dev@davidpace.de>
Wed, 29 May 2024 14:10:48 +0000 (16:10 +0200)
committerGitHub <noreply@github.com>
Wed, 29 May 2024 14:10:48 +0000 (16:10 +0200)
Adds support for Bosch Smart Water Alarm devices.

* add new thing type and new channel types
* add new services for water detector
* refactor CameraNotificationState and PrivacyModeState to a common enum
EnabledDisabledState that can be re-used in the water detector tilt
service states
* implement handler for new device
* register new device in handler factory and discovery
* add unit tests
* add documentation

Signed-off-by: David Pace <dev@davidpace.de>
32 files changed:
bundles/org.openhab.binding.boschshc/README.md
bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/console/BoschShcCommandExtension.java
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/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/bridge/dto/Message.java [new file with mode: 0644]
bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/dto/MessageCode.java [new file with mode: 0644]
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/waterleakage/WaterLeakageSensorHandler.java [new file with mode: 0644]
bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/discovery/ThingDiscoveryService.java
bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/serialization/BoschServiceDataDeserializer.java
bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/cameranotification/CameraNotificationState.java [deleted file]
bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/cameranotification/dto/CameraNotificationServiceState.java
bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/dto/EnabledDisabledState.java [new file with mode: 0644]
bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/privacymode/PrivacyModeState.java [deleted file]
bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/privacymode/dto/PrivacyModeServiceState.java
bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/waterleakagesensor/WaterLeakageSensorService.java [new file with mode: 0644]
bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/waterleakagesensor/dto/WaterLeakageSensorServiceState.java [new file with mode: 0644]
bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/waterleakagesensor/dto/WaterLeakageState.java [new file with mode: 0644]
bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/waterleakagesensorcheck/WaterLeakageSensorCheckService.java [new file with mode: 0644]
bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/waterleakagesensorcheck/dto/WaterLeakageSensorCheckServiceState.java [new file with mode: 0644]
bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/waterleakagesensortilt/WaterLeakageSensorTiltService.java [new file with mode: 0644]
bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/waterleakagesensortilt/dto/WaterLeakageSensorTiltServiceState.java [new file with mode: 0644]
bundles/org.openhab.binding.boschshc/src/main/resources/OH-INF/i18n/boschshc.properties
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
bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/devices/camera/CameraHandlerTest.java
bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/devices/waterleakage/WaterLeakageSensorHandlerTest.java [new file with mode: 0644]
bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/services/cameranotification/CameraNotificationStateTest.java [deleted file]
bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/services/dto/EnabledDisabledStateTest.java [new file with mode: 0644]
bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/services/privacymode/PrivacyModeStateTest.java [deleted file]

index 1cb21d4b4842960c9cdd97d3a44249266d157804..0d8dd4486a7d7cb184b49389ff8025d3658f1429 100644 (file)
@@ -26,6 +26,7 @@ Binding for Bosch Smart Home devices.
     - [User-defined States](#user-defined-states)
     - [Universal Switch](#universal-switch)
     - [Universal Switch II](#universal-switch-ii)
+    - [Water Detector](#water-detector)
   - [Limitations](#limitations)
   - [Discovery](#discovery)
   - [Bridge Configuration](#bridge-configuration)
@@ -333,6 +334,23 @@ A universally configurable switch with four buttons.
 | key-event-type      | String               | &#9744;  | Indicates how the key was pressed. Possible values are `PRESS_SHORT`, `PRESS_LONG` and `PRESS_LONG_RELEASED`. |
 | key-event-timestamp | DateTime             | &#9744;  | Timestamp indicating when the key was pressed. |
 
+### Water Detector
+
+Smart water leakage detector.
+
+**Thing Type ID**: `water-detector` 
+
+| Channel Type ID            | Item Type | Writable | Description                                       |
+| -------------------------- | --------- | :------: | ------------------------------------------------- |
+| battery-level              | Number    | &#9744;  | Current battery level percentage as integer number. Bosch-specific battery levels are mapped to numbers as follows: `OK`: 100, `LOW_BATTERY`: 10, `CRITICAL_LOW`: 1, `CRITICALLY_LOW_BATTERY`: 1, `NOT_AVAILABLE`: `UNDEF`. |
+| low-battery                | Switch    | &#9744;  | Indicates whether the battery is low (`ON`) or OK (`OFF`).                                                                                                                                                                  |
+| signal-strength            | Number    | &#9744;  | Communication quality between the device and the Smart Home Controller. Possible values range between 0 (unknown) and 4 (best signal strength).                                                                             |
+| water-leakage              | Switch    | &#9744;  | Indicates whether a water leakage was detected.               |
+| push-notifications         | Switch    | &#9745;  | Indicates whether push notifications are enabled.             |
+| acoustic-signals           | Switch    | &#9745;  | Indicates whether acoustic signals are enabled.               |
+| water-leakage-sensor-check | String    | &#9744;  | Provides the result of the last water leakage sensor check.   |
+| sensor-moved               | Trigger   | &#9744;  | Triggered when the sensor is moved.                           |
+
 ## Limitations
 
 No major limitation known.
index 36354778c4f5b92fcba25773854c03db10a62cee..0b91213621482991c19d4d80a9177f2dcfaf640a 100644 (file)
@@ -82,7 +82,8 @@ public class BoschShcCommandExtension extends AbstractConsoleCommandExtension im
                 "childprotection", "communicationquality", "hsbcoloractuator", "humiditylevel", "illuminance",
                 "intrusion", "keypad", "latestmotion", "multilevelswitch", "powermeter", "powerswitch", "privacymode",
                 "roomclimatecontrol", "shuttercontact", "shuttercontrol", "silentmode", "smokedetectorcheck",
-                "temperaturelevel", "userstate", "valvetappet");
+                "temperaturelevel", "userstate", "valvetappet", "waterleakagesensor", "waterleakagesensorcheck",
+                "waterleakagesensortilt");
     }
 
     @Override
index eb7f4dc685b1459fddc9c5f4b9ff6237c1c52e2e..4dba916088faa8b29b74e1a1e973bc9f1ac60e1f 100644 (file)
@@ -28,6 +28,10 @@ import org.openhab.core.thing.ThingTypeUID;
 @NonNullByDefault
 public class BoschSHCBindingConstants {
 
+    private BoschSHCBindingConstants() {
+        // Class containing constants only
+    }
+
     public static final String BINDING_ID = "boschshc";
 
     // List of all Thing Type UIDs
@@ -55,6 +59,7 @@ public class BoschSHCBindingConstants {
     public static final ThingTypeUID THING_TYPE_SMOKE_DETECTOR_2 = new ThingTypeUID(BINDING_ID, "smoke-detector-2");
     public static final ThingTypeUID THING_TYPE_LIGHT_CONTROL_2 = new ThingTypeUID(BINDING_ID, "light-control-2");
     public static final ThingTypeUID THING_TYPE_DIMMER = new ThingTypeUID(BINDING_ID, "dimmer");
+    public static final ThingTypeUID THING_TYPE_WATER_DETECTOR = new ThingTypeUID(BINDING_ID, "water-detector");
 
     public static final ThingTypeUID THING_TYPE_USER_DEFINED_STATE = new ThingTypeUID(BINDING_ID, "user-defined-state");
 
@@ -102,6 +107,11 @@ public class BoschSHCBindingConstants {
     public static final String CHANNEL_KEY_NAME = "key-name";
     public static final String CHANNEL_KEY_EVENT_TYPE = "key-event-type";
     public static final String CHANNEL_KEY_EVENT_TIMESTAMP = "key-event-timestamp";
+    public static final String CHANNEL_WATER_LEAKAGE = "water-leakage";
+    public static final String CHANNEL_PUSH_NOTIFICATIONS_ON_MOVE = "push-notifications-on-move";
+    public static final String CHANNEL_ACOUSTIC_SIGNALS_ON_MOVE = "acoustic-signals-on-move";
+    public static final String CHANNEL_WATER_LEAKAGE_SENSOR_CHECK = "water-leakage-sensor-check";
+    public static final String CHANNEL_SENSOR_MOVED = "sensor-moved";
 
     // numbered channels
     // the rationale for introducing numbered channels was discussed in
index 76cdf6dd0a701c3769fe0da72b2eb6aa09947c8a..a3b07f3303078b45e8febe9c9ca4932a408afd46 100644 (file)
@@ -23,6 +23,7 @@ import java.util.function.Supplier;
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.eclipse.jdt.annotation.Nullable;
 import org.openhab.binding.boschshc.internal.devices.bridge.BridgeHandler;
+import org.openhab.binding.boschshc.internal.devices.bridge.dto.Message;
 import org.openhab.binding.boschshc.internal.exceptions.BoschSHCException;
 import org.openhab.binding.boschshc.internal.services.AbstractBoschSHCService;
 import org.openhab.binding.boschshc.internal.services.AbstractStatelessBoschSHCService;
@@ -175,6 +176,15 @@ public abstract class BoschSHCHandler extends BaseThingHandler {
         // default implementation is empty, subclasses may override
     }
 
+    /**
+     * Processes a device-specific message from the Bosch Smart Home Controller.
+     * 
+     * @param message the message published by the controller
+     */
+    public void processMessage(Message message) {
+        // default implementation is empty, subclasses may override
+    }
+
     /**
      * Use this method to register all services of the device with
      * {@link #registerService(BoschSHCService, Consumer, Collection, boolean)}.
index 21e0b0c87f3316ddabad7254a1a90c11f4f9ab4e..9fabcdff4f5076ec13acf40294e085850312dc78 100644 (file)
@@ -33,6 +33,7 @@ import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConst
 import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.THING_TYPE_UNIVERSAL_SWITCH_2;
 import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.THING_TYPE_USER_DEFINED_STATE;
 import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.THING_TYPE_WALL_THERMOSTAT;
+import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.THING_TYPE_WATER_DETECTOR;
 import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.THING_TYPE_WINDOW_CONTACT;
 import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.THING_TYPE_WINDOW_CONTACT_2;
 
@@ -62,6 +63,7 @@ import org.openhab.binding.boschshc.internal.devices.universalswitch.UniversalSw
 import org.openhab.binding.boschshc.internal.devices.universalswitch.UniversalSwitchHandler;
 import org.openhab.binding.boschshc.internal.devices.userdefinedstate.UserStateHandler;
 import org.openhab.binding.boschshc.internal.devices.wallthermostat.WallThermostatHandler;
+import org.openhab.binding.boschshc.internal.devices.waterleakage.WaterLeakageSensorHandler;
 import org.openhab.binding.boschshc.internal.devices.windowcontact.WindowContact2Handler;
 import org.openhab.binding.boschshc.internal.devices.windowcontact.WindowContactHandler;
 import org.openhab.core.i18n.TimeZoneProvider;
@@ -132,7 +134,8 @@ public class BoschSHCHandlerFactory extends BaseThingHandlerFactory {
                     thing -> new UniversalSwitch2Handler(thing, timeZoneProvider)),
             new ThingTypeHandlerMapping(THING_TYPE_SMOKE_DETECTOR_2, SmokeDetector2Handler::new),
             new ThingTypeHandlerMapping(THING_TYPE_LIGHT_CONTROL_2, LightControl2Handler::new),
-            new ThingTypeHandlerMapping(THING_TYPE_DIMMER, DimmerHandler::new));
+            new ThingTypeHandlerMapping(THING_TYPE_DIMMER, DimmerHandler::new),
+            new ThingTypeHandlerMapping(THING_TYPE_WATER_DETECTOR, WaterLeakageSensorHandler::new));
 
     @Override
     public boolean supportsThingType(ThingTypeUID thingTypeUID) {
index 0e277048cc1261321699799758cf7408f222e77a..a8514f0d24b1d746f38ad8df29b8bfeb04a1f532 100644 (file)
@@ -12,7 +12,9 @@
  */
 package org.openhab.binding.boschshc.internal.devices.bridge;
 
-import static org.eclipse.jetty.http.HttpMethod.*;
+import static org.eclipse.jetty.http.HttpMethod.GET;
+import static org.eclipse.jetty.http.HttpMethod.POST;
+import static org.eclipse.jetty.http.HttpMethod.PUT;
 
 import java.lang.reflect.Type;
 import java.util.ArrayList;
@@ -40,6 +42,7 @@ import org.openhab.binding.boschshc.internal.devices.BoschSHCHandler;
 import org.openhab.binding.boschshc.internal.devices.bridge.dto.Device;
 import org.openhab.binding.boschshc.internal.devices.bridge.dto.DeviceServiceData;
 import org.openhab.binding.boschshc.internal.devices.bridge.dto.LongPollResult;
+import org.openhab.binding.boschshc.internal.devices.bridge.dto.Message;
 import org.openhab.binding.boschshc.internal.devices.bridge.dto.PublicInformation;
 import org.openhab.binding.boschshc.internal.devices.bridge.dto.Room;
 import org.openhab.binding.boschshc.internal.devices.bridge.dto.Scenario;
@@ -488,10 +491,44 @@ public class BridgeHandler extends BaseBridgeHandler {
                 if (channel != null && isLinked(channel.getUID())) {
                     updateState(channel.getUID(), new StringType(scenario.name));
                 }
+            } else if (serviceState instanceof Message message) {
+                handleMessage(message);
             }
         }
     }
 
+    private void handleMessage(Message message) {
+        if (Message.SOURCE_TYPE_DEVICE.equals(message.sourceType) && message.sourceId != null) {
+            forwardMessageToDevice(message, message.sourceId);
+        }
+    }
+
+    private void forwardMessageToDevice(Message message, String deviceId) {
+        BoschSHCHandler deviceHandler = findDeviceHandler(deviceId);
+        if (deviceHandler == null) {
+            return;
+        }
+
+        deviceHandler.processMessage(message);
+    }
+
+    @Nullable
+    private BoschSHCHandler findDeviceHandler(String deviceIdToFind) {
+        for (Thing childThing : getThing().getThings()) {
+            @Nullable
+            ThingHandler baseHandler = childThing.getHandler();
+            if (baseHandler instanceof BoschSHCHandler handler) {
+                @Nullable
+                String deviceId = handler.getBoschID();
+
+                if (deviceIdToFind.equals(deviceId)) {
+                    return handler;
+                }
+            }
+        }
+        return null;
+    }
+
     /**
      * Processes a single long poll result.
      *
diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/dto/Message.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/dto/Message.java
new file mode 100644 (file)
index 0000000..5d2a5a3
--- /dev/null
@@ -0,0 +1,66 @@
+/**
+ * Copyright (c) 2010-2024 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.boschshc.internal.devices.bridge.dto;
+
+import org.openhab.binding.boschshc.internal.services.dto.BoschSHCServiceState;
+
+/**
+ * DTO for messages sent by the Smart Home Controller.
+ * <p>
+ * JSON Example:
+ * 
+ * <pre>
+ * {
+ *   "result": [{
+ *     "sourceId": "hdm:ZigBee:5cc7c1f6fe11fc23",
+ *     "sourceType": "DEVICE",
+ *     "@type": "message",
+ *     "flags": [],
+ *     "messageCode": {
+ *       "name": "TILT_DETECTED",
+ *       "category": "WARNING"
+ *     },
+ *     "location": "Kitchen",
+ *     "arguments": {
+ *       "deviceModel": "WLS"
+ *     },
+ *     "id": "3499a60e-45b5-4c29-ae1a-202c2182970c",
+ *     "sourceName": "Bosch_water_detector_1",
+ *     "timestamp": 1714375556426
+ *   }],
+ *   "jsonrpc": "2.0"
+ * }
+ * </pre>
+ * 
+ * @author David Pace - Initial contribution
+ */
+public class Message extends BoschSHCServiceState {
+
+    /**
+     * Source type indicating that a message is device-specific
+     */
+    public static final String SOURCE_TYPE_DEVICE = "DEVICE";
+
+    public Message() {
+        super("message");
+    }
+
+    public String id;
+    public String sourceId;
+    public String sourceName;
+    public String sourceType;
+    public String location;
+    public long timestamp;
+
+    public MessageCode messageCode;
+}
diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/dto/MessageCode.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/dto/MessageCode.java
new file mode 100644 (file)
index 0000000..227bd27
--- /dev/null
@@ -0,0 +1,32 @@
+/**
+ * Copyright (c) 2010-2024 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.boschshc.internal.devices.bridge.dto;
+
+/**
+ * DTO for message codes sent by the Smart Home Controller.
+ * <p>
+ * JSON Example:
+ * 
+ * <pre>
+ * {
+ *   "name": "TILT_DETECTED",
+ *   "category": "WARNING"
+ * }
+ * </pre>
+ * 
+ * @author David Pace - Initial contribution
+ */
+public class MessageCode {
+    public String name;
+    public String category;
+}
index 108cf1f0425d735ecc8c52c356cdc4f46ff3e8af..982d54ce580c4c31c720661143f1b04d2fec7c27 100644 (file)
@@ -12,7 +12,8 @@
  */
 package org.openhab.binding.boschshc.internal.devices.camera;
 
-import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.*;
+import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.CHANNEL_CAMERA_NOTIFICATION;
+import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.CHANNEL_PRIVACY_MODE;
 
 import java.util.List;
 
@@ -20,10 +21,9 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
 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;
 import org.openhab.binding.boschshc.internal.services.cameranotification.dto.CameraNotificationServiceState;
+import org.openhab.binding.boschshc.internal.services.dto.EnabledDisabledState;
 import org.openhab.binding.boschshc.internal.services.privacymode.PrivacyModeService;
-import org.openhab.binding.boschshc.internal.services.privacymode.PrivacyModeState;
 import org.openhab.binding.boschshc.internal.services.privacymode.dto.PrivacyModeServiceState;
 import org.openhab.core.library.types.OnOffType;
 import org.openhab.core.thing.ChannelUID;
@@ -91,13 +91,13 @@ public class CameraHandler extends BoschSHCDeviceHandler {
 
     private void updatePrivacyModeState(OnOffType command) {
         PrivacyModeServiceState serviceState = new PrivacyModeServiceState();
-        serviceState.value = PrivacyModeState.from(command);
+        serviceState.value = EnabledDisabledState.from(command);
         this.updateServiceState(this.privacyModeService, serviceState);
     }
 
     private void updateCameraNotificationState(OnOffType command) {
         CameraNotificationServiceState serviceState = new CameraNotificationServiceState();
-        serviceState.value = CameraNotificationState.from(command);
+        serviceState.value = EnabledDisabledState.from(command);
         this.updateServiceState(this.cameraNotificationService, serviceState);
     }
 
diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/waterleakage/WaterLeakageSensorHandler.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/waterleakage/WaterLeakageSensorHandler.java
new file mode 100644 (file)
index 0000000..2f28f5c
--- /dev/null
@@ -0,0 +1,160 @@
+/**
+ * Copyright (c) 2010-2024 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.waterleakage;
+
+import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.CHANNEL_ACOUSTIC_SIGNALS_ON_MOVE;
+import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.CHANNEL_PUSH_NOTIFICATIONS_ON_MOVE;
+import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.CHANNEL_SENSOR_MOVED;
+import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.CHANNEL_SIGNAL_STRENGTH;
+import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.CHANNEL_WATER_LEAKAGE;
+import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.CHANNEL_WATER_LEAKAGE_SENSOR_CHECK;
+
+import java.util.List;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.boschshc.internal.devices.AbstractBatteryPoweredDeviceHandler;
+import org.openhab.binding.boschshc.internal.devices.bridge.dto.Message;
+import org.openhab.binding.boschshc.internal.exceptions.BoschSHCException;
+import org.openhab.binding.boschshc.internal.services.communicationquality.CommunicationQualityService;
+import org.openhab.binding.boschshc.internal.services.communicationquality.dto.CommunicationQualityServiceState;
+import org.openhab.binding.boschshc.internal.services.dto.EnabledDisabledState;
+import org.openhab.binding.boschshc.internal.services.waterleakagesensor.WaterLeakageSensorService;
+import org.openhab.binding.boschshc.internal.services.waterleakagesensor.dto.WaterLeakageSensorServiceState;
+import org.openhab.binding.boschshc.internal.services.waterleakagesensorcheck.WaterLeakageSensorCheckService;
+import org.openhab.binding.boschshc.internal.services.waterleakagesensorcheck.dto.WaterLeakageSensorCheckServiceState;
+import org.openhab.binding.boschshc.internal.services.waterleakagesensortilt.WaterLeakageSensorTiltService;
+import org.openhab.binding.boschshc.internal.services.waterleakagesensortilt.dto.WaterLeakageSensorTiltServiceState;
+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;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Handler for water leakage sensors.
+ * 
+ * @author David Pace - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class WaterLeakageSensorHandler extends AbstractBatteryPoweredDeviceHandler {
+
+    private final Logger logger = LoggerFactory.getLogger(WaterLeakageSensorHandler.class);
+
+    /**
+     * Message code indicating that the water leakage sensor was moved
+     */
+    public static final String MESSAGE_CODE_TILT_DETECTED = "TILT_DETECTED";
+
+    private WaterLeakageSensorTiltService waterLeakageSensorTiltService;
+    private WaterLeakageSensorCheckService waterLeakageSensorCheckService;
+
+    @Nullable
+    private WaterLeakageSensorTiltServiceState currentWaterSensorTiltServiceState;
+
+    public WaterLeakageSensorHandler(Thing thing) {
+        super(thing);
+
+        this.waterLeakageSensorTiltService = new WaterLeakageSensorTiltService();
+        this.waterLeakageSensorCheckService = new WaterLeakageSensorCheckService();
+    }
+
+    @Override
+    protected void initializeServices() throws BoschSHCException {
+        super.initializeServices();
+
+        this.createService(WaterLeakageSensorService::new, this::updateChannels, List.of(CHANNEL_WATER_LEAKAGE), true);
+        this.registerService(waterLeakageSensorTiltService, this::updateChannels,
+                List.of(CHANNEL_PUSH_NOTIFICATIONS_ON_MOVE, CHANNEL_ACOUSTIC_SIGNALS_ON_MOVE), true);
+        this.registerService(waterLeakageSensorCheckService, this::updateChannels, List.of());
+
+        this.createService(CommunicationQualityService::new, this::updateChannels, List.of(CHANNEL_SIGNAL_STRENGTH),
+                true);
+    }
+
+    private void updateChannels(WaterLeakageSensorServiceState waterLeakageSensorServiceState) {
+        updateState(CHANNEL_WATER_LEAKAGE, waterLeakageSensorServiceState.state.toOnOffType());
+    }
+
+    private void updateChannels(WaterLeakageSensorTiltServiceState waterLeakageSensorTiltServiceState) {
+        currentWaterSensorTiltServiceState = waterLeakageSensorTiltServiceState;
+        updateState(CHANNEL_PUSH_NOTIFICATIONS_ON_MOVE,
+                waterLeakageSensorTiltServiceState.pushNotificationState.toOnOffType());
+        updateState(CHANNEL_ACOUSTIC_SIGNALS_ON_MOVE,
+                waterLeakageSensorTiltServiceState.acousticSignalState.toOnOffType());
+    }
+
+    private void updateChannels(WaterLeakageSensorCheckServiceState waterLeakageSensorCheckServiceState) {
+        updateState(CHANNEL_WATER_LEAKAGE_SENSOR_CHECK, new StringType(waterLeakageSensorCheckServiceState.result));
+    }
+
+    private void updateChannels(CommunicationQualityServiceState communicationQualityServiceState) {
+        updateState(CHANNEL_SIGNAL_STRENGTH, communicationQualityServiceState.quality.toSystemSignalStrength());
+    }
+
+    @Override
+    public void handleCommand(ChannelUID channelUID, Command command) {
+        super.handleCommand(channelUID, command);
+
+        if (CHANNEL_PUSH_NOTIFICATIONS_ON_MOVE.equals(channelUID.getId())
+                && command instanceof OnOffType onOffCommand) {
+            updatePushNotificationState(onOffCommand);
+        } else if (CHANNEL_ACOUSTIC_SIGNALS_ON_MOVE.equals(channelUID.getId())
+                && command instanceof OnOffType onOffCommand) {
+            updateAcousticSignalState(onOffCommand);
+        }
+    }
+
+    private void updatePushNotificationState(OnOffType onOffCommand) {
+        WaterLeakageSensorTiltServiceState newState = cloneCurrentWaterLeakageSensorTiltServiceState();
+        if (newState != null) {
+            newState.pushNotificationState = EnabledDisabledState.from(onOffCommand);
+            this.currentWaterSensorTiltServiceState = newState;
+            updateServiceState(waterLeakageSensorTiltService, newState);
+        }
+    }
+
+    private void updateAcousticSignalState(OnOffType onOffCommand) {
+        WaterLeakageSensorTiltServiceState newState = cloneCurrentWaterLeakageSensorTiltServiceState();
+        if (newState != null) {
+            newState.acousticSignalState = EnabledDisabledState.from(onOffCommand);
+            this.currentWaterSensorTiltServiceState = newState;
+            updateServiceState(waterLeakageSensorTiltService, newState);
+        }
+    }
+
+    @Nullable
+    private WaterLeakageSensorTiltServiceState cloneCurrentWaterLeakageSensorTiltServiceState() {
+        if (currentWaterSensorTiltServiceState != null) {
+            WaterLeakageSensorTiltServiceState clonedState = new WaterLeakageSensorTiltServiceState();
+            clonedState.acousticSignalState = currentWaterSensorTiltServiceState.acousticSignalState;
+            clonedState.pushNotificationState = currentWaterSensorTiltServiceState.pushNotificationState;
+            return clonedState;
+        } else {
+            logger.warn("Could not obtain current water leakage detector tilt state, command will not be processed.");
+        }
+        return null;
+    }
+
+    @Override
+    public void processMessage(Message message) {
+        super.processMessage(message);
+
+        if (message.messageCode != null && MESSAGE_CODE_TILT_DETECTED.equals(message.messageCode.name)) {
+            triggerChannel(CHANNEL_SENSOR_MOVED);
+        }
+    }
+}
index 423e3acee752bf670a5adfe378d589ada0084618..ab0a30308718b5ad06ade7631ac1627dde1c5a92 100644 (file)
@@ -98,7 +98,8 @@ public class ThingDiscoveryService extends AbstractThingHandlerDiscoveryService<
             new AbstractMap.SimpleEntry<>("MICROMODULE_SHUTTER", BoschSHCBindingConstants.THING_TYPE_SHUTTER_CONTROL_2),
             new AbstractMap.SimpleEntry<>("MICROMODULE_AWNING", BoschSHCBindingConstants.THING_TYPE_SHUTTER_CONTROL_2),
             new AbstractMap.SimpleEntry<>("MICROMODULE_LIGHT_CONTROL", BoschSHCBindingConstants.THING_TYPE_LIGHT_CONTROL_2),
-            new AbstractMap.SimpleEntry<>("MICROMODULE_DIMMER", BoschSHCBindingConstants.THING_TYPE_DIMMER)
+            new AbstractMap.SimpleEntry<>("MICROMODULE_DIMMER", BoschSHCBindingConstants.THING_TYPE_DIMMER),
+            new AbstractMap.SimpleEntry<>("WLS", BoschSHCBindingConstants.THING_TYPE_WATER_DETECTOR)
 // Future Extension: map deviceModel names to BoschSHC Thing Types when they are supported
 //            new AbstractMap.SimpleEntry<>("SMOKE_DETECTION_SYSTEM", BoschSHCBindingConstants.),
 //            new AbstractMap.SimpleEntry<>("PRESENCE_SIMULATION_SERVICE", BoschSHCBindingConstants.),
index 851c2ae44021de4fa6ce0ea2b4881c2cafbd09ac..41b02cfbfb8d299f0964935e8ba744f466f81f23 100644 (file)
@@ -17,6 +17,7 @@ import java.lang.reflect.Type;
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.eclipse.jdt.annotation.Nullable;
 import org.openhab.binding.boschshc.internal.devices.bridge.dto.DeviceServiceData;
+import org.openhab.binding.boschshc.internal.devices.bridge.dto.Message;
 import org.openhab.binding.boschshc.internal.devices.bridge.dto.Scenario;
 import org.openhab.binding.boschshc.internal.devices.bridge.dto.UserDefinedState;
 import org.openhab.binding.boschshc.internal.services.dto.BoschSHCServiceState;
@@ -65,6 +66,9 @@ public class BoschServiceDataDeserializer implements JsonDeserializer<BoschSHCSe
                 state.setState(jsonObject.get("state").getAsBoolean());
                 return state;
             }
+            case "message" -> {
+                return GsonUtils.DEFAULT_GSON_INSTANCE.fromJson(jsonElement, Message.class);
+            }
             default -> {
                 return new BoschSHCServiceState(dataType.getAsString());
             }
diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/cameranotification/CameraNotificationState.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/cameranotification/CameraNotificationState.java
deleted file mode 100644 (file)
index 8340033..0000000
+++ /dev/null
@@ -1,47 +0,0 @@
-/**
- * Copyright (c) 2010-2024 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.cameranotification;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.openhab.core.library.types.OnOffType;
-
-/**
- * Possible states for camera notifications.
- * 
- * @author David Pace - Initial contribution
- *
- */
-@NonNullByDefault
-public enum CameraNotificationState {
-    ENABLED,
-    DISABLED;
-
-    /**
-     * Converts an {@link OnOffType} state into a {@link CameraNotificationState}.
-     * 
-     * @param onOff the on/off state
-     * @return the corresponding notification state
-     */
-    public static CameraNotificationState from(OnOffType onOff) {
-        return onOff == OnOffType.ON ? ENABLED : DISABLED;
-    }
-
-    /**
-     * Converts this {@link CameraNotificationState} into an {@link OnOffType}.
-     * 
-     * @return the on/off state corresponding to the notification state of this enumeration literal
-     */
-    public OnOffType toOnOffType() {
-        return OnOffType.from(this == ENABLED);
-    }
-}
index 1a90421185520c66d234da259e421d6ad9b716d8..028b7f469a6ae096c8e8899568cd729a455a6b6c 100644 (file)
@@ -12,8 +12,8 @@
  */
 package org.openhab.binding.boschshc.internal.services.cameranotification.dto;
 
-import org.openhab.binding.boschshc.internal.services.cameranotification.CameraNotificationState;
 import org.openhab.binding.boschshc.internal.services.dto.BoschSHCServiceState;
+import org.openhab.binding.boschshc.internal.services.dto.EnabledDisabledState;
 
 /**
  * Represents the state of camera notifications as reported by the Smart Home Controller.
@@ -35,5 +35,5 @@ public class CameraNotificationServiceState extends BoschSHCServiceState {
      * {"@type":"cameraNotificationState","value":"ENABLED"}
      * </pre>
      */
-    public CameraNotificationState value;
+    public EnabledDisabledState value;
 }
diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/dto/EnabledDisabledState.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/dto/EnabledDisabledState.java
new file mode 100644 (file)
index 0000000..f57295d
--- /dev/null
@@ -0,0 +1,47 @@
+/**
+ * Copyright (c) 2010-2024 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.dto;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.core.library.types.OnOffType;
+
+/**
+ * State that is serialized as either <code>ENABLED</code> or <code>DISABLED</code>.
+ * 
+ * @author David Pace - Initial contribution
+ *
+ */
+@NonNullByDefault
+public enum EnabledDisabledState {
+    ENABLED,
+    DISABLED;
+
+    /**
+     * Converts an {@link OnOffType} state into a {@link EnabledDisabledState}.
+     * 
+     * @param onOff the on/off state
+     * @return the corresponding enabled/disabled state
+     */
+    public static EnabledDisabledState from(OnOffType onOff) {
+        return onOff == OnOffType.ON ? ENABLED : DISABLED;
+    }
+
+    /**
+     * Converts this {@link EnabledDisabledState} into an {@link OnOffType}.
+     * 
+     * @return the on/off state corresponding to the enabled/disabled state of this enumeration literal
+     */
+    public OnOffType toOnOffType() {
+        return OnOffType.from(this == ENABLED);
+    }
+}
diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/privacymode/PrivacyModeState.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/privacymode/PrivacyModeState.java
deleted file mode 100644 (file)
index 41fc327..0000000
+++ /dev/null
@@ -1,55 +0,0 @@
-/**
- * Copyright (c) 2010-2024 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.privacymode;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.openhab.core.library.types.OnOffType;
-
-/**
- * Possible privacy mode states of security cameras.
- * 
- * @author David Pace - Initial contribution
- *
- */
-@NonNullByDefault
-public enum PrivacyModeState {
-
-    /**
-     * Privacy mode enabled / camera disabled
-     */
-    ENABLED,
-
-    /**
-     * Privacy mode disabled / camera enabled
-     */
-    DISABLED;
-
-    /**
-     * Converts an {@link OnOffType} state into a {@link PrivacyModeState}.
-     * 
-     * @param onOff the on/off state
-     * @return the corresponding privacy mode state
-     */
-    public static PrivacyModeState from(OnOffType onOff) {
-        return onOff == OnOffType.ON ? ENABLED : DISABLED;
-    }
-
-    /**
-     * Converts this {@link PrivacyModeState} into an {@link OnOffType}.
-     * 
-     * @return the on/off state corresponding to the privacy mode state of this enumeration literal
-     */
-    public OnOffType toOnOffType() {
-        return OnOffType.from(this == ENABLED);
-    }
-}
index 352b7e0f2b72180b08633c9ac090be330cb20c60..bbabe505d0cf8df8780a2e52829f890e60c0d92d 100644 (file)
@@ -13,7 +13,7 @@
 package org.openhab.binding.boschshc.internal.services.privacymode.dto;
 
 import org.openhab.binding.boschshc.internal.services.dto.BoschSHCServiceState;
-import org.openhab.binding.boschshc.internal.services.privacymode.PrivacyModeState;
+import org.openhab.binding.boschshc.internal.services.dto.EnabledDisabledState;
 
 /**
  * Represents the privacy mode of cameras as reported by the Smart Home Controller.
@@ -35,5 +35,5 @@ public class PrivacyModeServiceState extends BoschSHCServiceState {
      * {"@type":"privacyModeState","value":"ENABLED"}
      * </pre>
      */
-    public PrivacyModeState value;
+    public EnabledDisabledState value;
 }
diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/waterleakagesensor/WaterLeakageSensorService.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/waterleakagesensor/WaterLeakageSensorService.java
new file mode 100644 (file)
index 0000000..57852a4
--- /dev/null
@@ -0,0 +1,31 @@
+/**
+ * Copyright (c) 2010-2024 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.waterleakagesensor;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.boschshc.internal.services.BoschSHCService;
+import org.openhab.binding.boschshc.internal.services.waterleakagesensor.dto.WaterLeakageSensorServiceState;
+
+/**
+ * Service for the water leakage sensor.
+ * 
+ * @author David Pace - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class WaterLeakageSensorService extends BoschSHCService<WaterLeakageSensorServiceState> {
+
+    public WaterLeakageSensorService() {
+        super("WaterLeakageSensor", WaterLeakageSensorServiceState.class);
+    }
+}
diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/waterleakagesensor/dto/WaterLeakageSensorServiceState.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/waterleakagesensor/dto/WaterLeakageSensorServiceState.java
new file mode 100644 (file)
index 0000000..d01bd0a
--- /dev/null
@@ -0,0 +1,30 @@
+/**
+ * Copyright (c) 2010-2024 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.waterleakagesensor.dto;
+
+import org.openhab.binding.boschshc.internal.services.dto.BoschSHCServiceState;
+
+/**
+ * Water leakage detection state of water detectors.
+ * 
+ * @author David Pace - Initial contribution
+ *
+ */
+public class WaterLeakageSensorServiceState extends BoschSHCServiceState {
+
+    public WaterLeakageSensorServiceState() {
+        super("waterLeakageSensorState");
+    }
+
+    public WaterLeakageState state;
+}
diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/waterleakagesensor/dto/WaterLeakageState.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/waterleakagesensor/dto/WaterLeakageState.java
new file mode 100644 (file)
index 0000000..3d412dc
--- /dev/null
@@ -0,0 +1,32 @@
+/**
+ * Copyright (c) 2010-2024 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.waterleakagesensor.dto;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.core.library.types.OnOffType;
+
+/**
+ * Possible states of the water leakage sensor.
+ * 
+ * @author David Pace - Initial contribution
+ *
+ */
+@NonNullByDefault
+public enum WaterLeakageState {
+    NO_LEAKAGE,
+    LEAKAGE_DETECTED;
+
+    public OnOffType toOnOffType() {
+        return OnOffType.from(this == LEAKAGE_DETECTED);
+    }
+}
diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/waterleakagesensorcheck/WaterLeakageSensorCheckService.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/waterleakagesensorcheck/WaterLeakageSensorCheckService.java
new file mode 100644 (file)
index 0000000..75548a0
--- /dev/null
@@ -0,0 +1,29 @@
+/**
+ * Copyright (c) 2010-2024 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.waterleakagesensorcheck;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.boschshc.internal.services.BoschSHCService;
+import org.openhab.binding.boschshc.internal.services.waterleakagesensorcheck.dto.WaterLeakageSensorCheckServiceState;
+
+/**
+ * 
+ * @author David Pace - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class WaterLeakageSensorCheckService extends BoschSHCService<WaterLeakageSensorCheckServiceState> {
+    public WaterLeakageSensorCheckService() {
+        super("WaterLeakageSensorCheck", WaterLeakageSensorCheckServiceState.class);
+    }
+}
diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/waterleakagesensorcheck/dto/WaterLeakageSensorCheckServiceState.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/waterleakagesensorcheck/dto/WaterLeakageSensorCheckServiceState.java
new file mode 100644 (file)
index 0000000..48c9718
--- /dev/null
@@ -0,0 +1,30 @@
+/**
+ * Copyright (c) 2010-2024 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.waterleakagesensorcheck.dto;
+
+import org.openhab.binding.boschshc.internal.services.dto.BoschSHCServiceState;
+
+/**
+ * DTO for the check state of water leakage sensors.
+ * 
+ * @author David Pace - Initial contribution
+ *
+ */
+public class WaterLeakageSensorCheckServiceState extends BoschSHCServiceState {
+
+    public WaterLeakageSensorCheckServiceState() {
+        super("waterLeakageSensorCheckState");
+    }
+
+    public String result;
+}
diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/waterleakagesensortilt/WaterLeakageSensorTiltService.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/waterleakagesensortilt/WaterLeakageSensorTiltService.java
new file mode 100644 (file)
index 0000000..114abcf
--- /dev/null
@@ -0,0 +1,31 @@
+/**
+ * Copyright (c) 2010-2024 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.waterleakagesensortilt;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.boschshc.internal.services.BoschSHCService;
+import org.openhab.binding.boschshc.internal.services.waterleakagesensortilt.dto.WaterLeakageSensorTiltServiceState;
+
+/**
+ * Service for notifications relating to the water detection sensor.
+ * 
+ * @author David Pace - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class WaterLeakageSensorTiltService extends BoschSHCService<WaterLeakageSensorTiltServiceState> {
+
+    public WaterLeakageSensorTiltService() {
+        super("WaterLeakageSensorTilt", WaterLeakageSensorTiltServiceState.class);
+    }
+}
diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/waterleakagesensortilt/dto/WaterLeakageSensorTiltServiceState.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/waterleakagesensortilt/dto/WaterLeakageSensorTiltServiceState.java
new file mode 100644 (file)
index 0000000..fc313a3
--- /dev/null
@@ -0,0 +1,33 @@
+/**
+ * Copyright (c) 2010-2024 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.waterleakagesensortilt.dto;
+
+import org.openhab.binding.boschshc.internal.services.dto.BoschSHCServiceState;
+import org.openhab.binding.boschshc.internal.services.dto.EnabledDisabledState;
+
+/**
+ * Service for notifications relating to the water detection sensor.
+ * 
+ * @author David Pace - Initial contribution
+ *
+ */
+public class WaterLeakageSensorTiltServiceState extends BoschSHCServiceState {
+
+    public WaterLeakageSensorTiltServiceState() {
+        super("waterLeakageSensorTiltState");
+    }
+
+    public EnabledDisabledState pushNotificationState;
+
+    public EnabledDisabledState acousticSignalState;
+}
index ba8473f1f852549fae154bd80d24ef4815a9ac05..d7963e58b3afd8218d00d45877e3b1fef9cc24d4 100644 (file)
@@ -47,6 +47,8 @@ thing-type.boschshc.user-defined-state.label = User-defined State
 thing-type.boschshc.user-defined-state.description = A User-defined state.
 thing-type.boschshc.wall-thermostat.label = Wall Thermostat
 thing-type.boschshc.wall-thermostat.description = Display of the current room temperature as well as the relative humidity in the room.
+thing-type.boschshc.water-detector.label = Water Detector
+thing-type.boschshc.water-detector.description = Smart sensor to detect water leakage.
 thing-type.boschshc.window-contact-2.label = Door/Window Contact II
 thing-type.boschshc.window-contact-2.description = Detects open windows and doors and features an additional button.
 thing-type.boschshc.window-contact.label = Door/Window Contact
@@ -65,6 +67,10 @@ thing-type.config.boschshc.user-defined-state.id.description = Unique ID of the
 
 # channel types
 
+channel-type.boschshc.acoustic-signals-on-move.label = Acoustic Signals When Moved
+channel-type.boschshc.acoustic-signals-on-move.description = Indicates whether acoustic signals are enabled when the sensor is moved.
+channel-type.boschshc.acoustic-signals-on-move.state.option.ON = Enabled
+channel-type.boschshc.acoustic-signals-on-move.state.option.OFF = Disabled
 channel-type.boschshc.active-configuration-profile.label = Active Configuration Profile
 channel-type.boschshc.active-configuration-profile.description = The name of the active configuration profile used for the intrusion detection system.
 channel-type.boschshc.air-description.label = Description
@@ -148,8 +154,14 @@ channel-type.boschshc.purity-rating.label = Purity Rating
 channel-type.boschshc.purity-rating.description = Rating of the air purity.
 channel-type.boschshc.purity.label = Purity
 channel-type.boschshc.purity.description = Purity of the air. A higher value indicates a higher pollution.
+channel-type.boschshc.push-notifications-on-move.label = Push Notifications When Moved
+channel-type.boschshc.push-notifications-on-move.description = Indicates whether push notifications are enabled when the sensor is moved.
+channel-type.boschshc.push-notifications-on-move.state.option.ON = Enabled
+channel-type.boschshc.push-notifications-on-move.state.option.OFF = Disabled
 channel-type.boschshc.scenario-triggered.label = Scenario Triggered
 channel-type.boschshc.scenario-triggered.description = Name of the triggered scenario
+channel-type.boschshc.sensor-moved.label = Sensor Moved
+channel-type.boschshc.sensor-moved.description = Triggered when the sensor is moved.
 channel-type.boschshc.setpoint-temperature.label = Setpoint Temperature
 channel-type.boschshc.setpoint-temperature.description = Desired temperature.
 channel-type.boschshc.silent-mode.label = Silent Mode
@@ -177,6 +189,12 @@ channel-type.boschshc.user-state.label = State
 channel-type.boschshc.user-state.description = State of user-defined state
 channel-type.boschshc.valve-tappet-position.label = Valve Tappet Position
 channel-type.boschshc.valve-tappet-position.description = Current open ratio (0 to 100).
+channel-type.boschshc.water-leakage-sensor-check.label = Water Leakage Sensor Check
+channel-type.boschshc.water-leakage-sensor-check.description = Provides the result of the last water leakage sensor check.
+channel-type.boschshc.water-leakage.label = Water Leakage
+channel-type.boschshc.water-leakage.description = Indicates whether a water leakage was detected.
+channel-type.boschshc.water-leakage.state.option.ON = Leakage detected
+channel-type.boschshc.water-leakage.state.option.OFF = No leakage detected
 
 # thing status offline descriptions
 
index a5133cfd55795b5ce6afd217e6ec15753b7ed6b2..7ddee65935fd2e83e0e4eca4cd426735e7f93c5d 100644 (file)
                <config-description-ref uri="thing-type:boschshc:device"/>
        </thing-type>
 
+       <thing-type id="water-detector">
+               <supported-bridge-type-refs>
+                       <bridge-type-ref id="shc"/>
+               </supported-bridge-type-refs>
+
+               <label>Water Detector</label>
+               <description>Smart sensor to detect water leakage.</description>
+
+               <channels>
+                       <channel id="water-leakage" typeId="water-leakage"/>
+                       <channel id="push-notifications-on-move" typeId="push-notifications-on-move"/>
+                       <channel id="acoustic-signals-on-move" typeId="acoustic-signals-on-move"/>
+                       <channel id="water-leakage-sensor-check" typeId="water-leakage-sensor-check"/>
+                       <channel id="sensor-moved" typeId="sensor-moved"/>
+                       <channel id="battery-level" typeId="system.battery-level"/>
+                       <channel id="low-battery" typeId="system.low-battery"/>
+                       <channel id="signal-strength" typeId="system.signal-strength"/>
+               </channels>
+
+               <config-description-ref uri="thing-type:boschshc:device"/>
+       </thing-type>
+
        <!-- Channels -->
 
        <channel-type id="system-availability">
                <description>Enables or disables the child protection on the device.</description>
        </channel-type>
 
+       <channel-type id="water-leakage">
+               <item-type>Switch</item-type>
+               <label>Water Leakage</label>
+               <description>Indicates whether a water leakage was detected.</description>
+               <state readOnly="true">
+                       <options>
+                               <option value="ON">Leakage detected</option>
+                               <option value="OFF">No leakage detected</option>
+                       </options>
+               </state>
+       </channel-type>
+
+       <channel-type id="push-notifications-on-move">
+               <item-type>Switch</item-type>
+               <label>Push Notifications When Moved</label>
+               <description>Indicates whether push notifications are enabled when the sensor is moved.</description>
+               <state>
+                       <options>
+                               <option value="ON">Enabled</option>
+                               <option value="OFF">Disabled</option>
+                       </options>
+               </state>
+       </channel-type>
+
+       <channel-type id="acoustic-signals-on-move">
+               <item-type>Switch</item-type>
+               <label>Acoustic Signals When Moved</label>
+               <description>Indicates whether acoustic signals are enabled when the sensor is moved.</description>
+               <state>
+                       <options>
+                               <option value="ON">Enabled</option>
+                               <option value="OFF">Disabled</option>
+                       </options>
+               </state>
+       </channel-type>
+
+       <channel-type id="water-leakage-sensor-check">
+               <item-type>String</item-type>
+               <label>Water Leakage Sensor Check</label>
+               <description>Provides the result of the last water leakage sensor check.</description>
+               <state readOnly="true"/>
+       </channel-type>
+
+       <channel-type id="sensor-moved">
+               <kind>trigger</kind>
+               <label>Sensor Moved</label>
+               <description>Triggered when the sensor is moved.</description>
+               <category>Alarm</category>
+               <tags>
+                       <tag>Alarm</tag>
+                       <tag>Tilt</tag>
+               </tags>
+       </channel-type>
+
 </thing:thing-descriptions>
index 734551a778427a3682d085a22976dc053596240e..83bc12ff84d6f05c10c67d37ac7f3a70725016ab 100644 (file)
@@ -65,6 +65,7 @@ import org.openhab.binding.boschshc.internal.devices.bridge.dto.DeviceServiceDat
 import org.openhab.binding.boschshc.internal.devices.bridge.dto.DeviceTest;
 import org.openhab.binding.boschshc.internal.devices.bridge.dto.Faults;
 import org.openhab.binding.boschshc.internal.devices.bridge.dto.LongPollResult;
+import org.openhab.binding.boschshc.internal.devices.bridge.dto.Message;
 import org.openhab.binding.boschshc.internal.devices.bridge.dto.PublicInformation;
 import org.openhab.binding.boschshc.internal.devices.bridge.dto.Room;
 import org.openhab.binding.boschshc.internal.devices.bridge.dto.Scenario;
@@ -738,6 +739,51 @@ class BridgeHandlerTest {
         verify(thingHandler).processChildUpdate("hdm:ZigBee:70ac08fffefead2d#3", "PowerSwitch", expectedState);
     }
 
+    @Test
+    void handleLongPollResultHandleMessage() {
+        List<Thing> things = new ArrayList<Thing>();
+        when(thing.getThings()).thenReturn(things);
+
+        Thing thing = mock(Thing.class);
+        things.add(thing);
+
+        BoschSHCHandler thingHandler = mock(BoschSHCHandler.class);
+        when(thing.getHandler()).thenReturn(thingHandler);
+
+        when(thingHandler.getBoschID()).thenReturn("hdm:ZigBee:5cc7c1fffe1f7967");
+
+        String json = """
+                {
+                    "result": [{
+                        "sourceId": "hdm:ZigBee:5cc7c1fffe1f7967",
+                        "sourceType": "DEVICE",
+                        "@type": "message",
+                        "flags": [],
+                        "messageCode": {
+                            "name": "TILT_DETECTED",
+                            "category": "WARNING"
+                        },
+                        "location": "Kitchen",
+                        "arguments": {
+                            "deviceModel": "WLS"
+                        },
+                        "id": "3499a60e-45b5-4c29-ae1a-202c2182970c",
+                        "sourceName": "Bosch_water_detector_1",
+                        "timestamp": 1714375556426
+                    }],
+                    "jsonrpc": "2.0"
+                }
+                """;
+        LongPollResult longPollResult = GsonUtils.DEFAULT_GSON_INSTANCE.fromJson(json, LongPollResult.class);
+        assertNotNull(longPollResult);
+
+        fixture.handleLongPollResult(longPollResult);
+
+        Message expectedMessage = (Message) longPollResult.result.get(0);
+
+        verify(thingHandler).processMessage(expectedMessage);
+    }
+
     @Test
     void handleLongPollResultScenarioTriggered() {
         Channel channel = mock(Channel.class);
index 2ef7e7df85dbc7dee8cf7c4268e96fdb44a6d435..f7529fb381c28ddc6517a324b49fce93ec5bfbc2 100644 (file)
@@ -14,7 +14,8 @@ package org.openhab.binding.boschshc.internal.devices.camera;
 
 import static org.junit.jupiter.api.Assertions.assertSame;
 import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.*;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
 
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeoutException;
@@ -26,9 +27,8 @@ import org.mockito.Captor;
 import org.openhab.binding.boschshc.internal.devices.AbstractBoschSHCDeviceHandlerTest;
 import org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants;
 import org.openhab.binding.boschshc.internal.exceptions.BoschSHCException;
-import org.openhab.binding.boschshc.internal.services.cameranotification.CameraNotificationState;
 import org.openhab.binding.boschshc.internal.services.cameranotification.dto.CameraNotificationServiceState;
-import org.openhab.binding.boschshc.internal.services.privacymode.PrivacyModeState;
+import org.openhab.binding.boschshc.internal.services.dto.EnabledDisabledState;
 import org.openhab.binding.boschshc.internal.services.privacymode.dto.PrivacyModeServiceState;
 import org.openhab.core.library.types.OnOffType;
 import org.openhab.core.thing.ChannelUID;
@@ -73,14 +73,14 @@ class CameraHandlerTest extends AbstractBoschSHCDeviceHandlerTest<CameraHandler>
         verify(getBridgeHandler()).putState(eq(getDeviceID()), eq("PrivacyMode"),
                 privacyModeServiceStateCaptor.capture());
         PrivacyModeServiceState state = privacyModeServiceStateCaptor.getValue();
-        assertSame(PrivacyModeState.ENABLED, state.value);
+        assertSame(EnabledDisabledState.ENABLED, state.value);
 
         getFixture().handleCommand(new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_PRIVACY_MODE),
                 OnOffType.OFF);
         verify(getBridgeHandler(), times(2)).putState(eq(getDeviceID()), eq("PrivacyMode"),
                 privacyModeServiceStateCaptor.capture());
         state = privacyModeServiceStateCaptor.getValue();
-        assertSame(PrivacyModeState.DISABLED, state.value);
+        assertSame(EnabledDisabledState.DISABLED, state.value);
     }
 
     @Test
@@ -92,7 +92,7 @@ class CameraHandlerTest extends AbstractBoschSHCDeviceHandlerTest<CameraHandler>
         verify(getBridgeHandler()).putState(eq(getDeviceID()), eq("CameraNotification"),
                 cameraNotificationServiceStateCaptor.capture());
         CameraNotificationServiceState state = cameraNotificationServiceStateCaptor.getValue();
-        assertSame(CameraNotificationState.ENABLED, state.value);
+        assertSame(EnabledDisabledState.ENABLED, state.value);
 
         getFixture().handleCommand(
                 new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_CAMERA_NOTIFICATION),
@@ -100,7 +100,7 @@ class CameraHandlerTest extends AbstractBoschSHCDeviceHandlerTest<CameraHandler>
         verify(getBridgeHandler(), times(2)).putState(eq(getDeviceID()), eq("CameraNotification"),
                 cameraNotificationServiceStateCaptor.capture());
         state = cameraNotificationServiceStateCaptor.getValue();
-        assertSame(CameraNotificationState.DISABLED, state.value);
+        assertSame(EnabledDisabledState.DISABLED, state.value);
     }
 
     @Test
diff --git a/bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/devices/waterleakage/WaterLeakageSensorHandlerTest.java b/bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/devices/waterleakage/WaterLeakageSensorHandlerTest.java
new file mode 100644 (file)
index 0000000..fcf462a
--- /dev/null
@@ -0,0 +1,229 @@
+/**
+ * Copyright (c) 2010-2024 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.waterleakage;
+
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeoutException;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.openhab.binding.boschshc.internal.devices.AbstractBatteryPoweredDeviceHandlerTest;
+import org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants;
+import org.openhab.binding.boschshc.internal.devices.bridge.dto.Message;
+import org.openhab.binding.boschshc.internal.devices.bridge.dto.MessageCode;
+import org.openhab.binding.boschshc.internal.exceptions.BoschSHCException;
+import org.openhab.binding.boschshc.internal.services.dto.EnabledDisabledState;
+import org.openhab.binding.boschshc.internal.services.waterleakagesensortilt.dto.WaterLeakageSensorTiltServiceState;
+import org.openhab.core.library.types.DecimalType;
+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.ThingTypeUID;
+
+import com.google.gson.JsonElement;
+import com.google.gson.JsonParser;
+
+/**
+ * Unit tests for {@link WaterLeakageSensorHandler}.
+ * 
+ * @author David Pace - Initial contribution
+ *
+ */
+@NonNullByDefault
+class WaterLeakageSensorHandlerTest extends AbstractBatteryPoweredDeviceHandlerTest<WaterLeakageSensorHandler> {
+
+    private @Captor @NonNullByDefault({}) ArgumentCaptor<WaterLeakageSensorTiltServiceState> waterLeakageTiltServiceStateCaptor;
+
+    @Override
+    protected WaterLeakageSensorHandler createFixture() {
+        return new WaterLeakageSensorHandler(getThing());
+    }
+
+    @Override
+    protected ThingTypeUID getThingTypeUID() {
+        return BoschSHCBindingConstants.THING_TYPE_WATER_DETECTOR;
+    }
+
+    @Override
+    protected String getDeviceID() {
+        return "hdm:ZigBee:f0d1b80001d639d5";
+    }
+
+    @Test
+    void updateChannelsWaterLeakageSensorServiceState() {
+        JsonElement jsonObject = JsonParser
+                .parseString("{\"@type\":\"waterLeakageSensorState\",\"state\": \"NO_LEAKAGE\"}");
+        getFixture().processUpdate("WaterLeakageSensor", jsonObject);
+        verify(getCallback()).stateUpdated(
+                new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_WATER_LEAKAGE), OnOffType.OFF);
+
+        jsonObject = JsonParser.parseString("{\"@type\":\"waterLeakageSensorState\",\"state\": \"LEAKAGE_DETECTED\"}");
+        getFixture().processUpdate("WaterLeakageSensor", jsonObject);
+        verify(getCallback()).stateUpdated(
+                new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_WATER_LEAKAGE), OnOffType.ON);
+    }
+
+    @Test
+    void updateChannelsWaterLeakageSensorTiltServiceState() {
+        JsonElement jsonObject = JsonParser.parseString(
+                "{\"@type\":\"waterLeakageSensorTiltState\",\"pushNotificationState\": \"DISABLED\",\"acousticSignalState\": \"DISABLED\"}");
+        getFixture().processUpdate("WaterLeakageSensorTilt", jsonObject);
+        verify(getCallback()).stateUpdated(
+                new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_PUSH_NOTIFICATIONS_ON_MOVE),
+                OnOffType.OFF);
+        verify(getCallback()).stateUpdated(
+                new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_ACOUSTIC_SIGNALS_ON_MOVE),
+                OnOffType.OFF);
+
+        jsonObject = JsonParser.parseString(
+                "{\"@type\":\"waterLeakageSensorTiltState\",\"pushNotificationState\": \"ENABLED\",\"acousticSignalState\": \"ENABLED\"}");
+        getFixture().processUpdate("WaterLeakageSensorTilt", jsonObject);
+        verify(getCallback()).stateUpdated(
+                new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_PUSH_NOTIFICATIONS_ON_MOVE),
+                OnOffType.ON);
+        verify(getCallback()).stateUpdated(
+                new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_ACOUSTIC_SIGNALS_ON_MOVE),
+                OnOffType.ON);
+    }
+
+    @Test
+    void testUpdateChannelsCommunicationQualityService() {
+        String json = """
+                {
+                    "@type": "communicationQualityState",
+                    "quality": "UNKNOWN"
+                }
+                """;
+        JsonElement jsonObject = JsonParser.parseString(json);
+
+        getFixture().processUpdate("CommunicationQuality", jsonObject);
+        verify(getCallback()).stateUpdated(
+                new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_SIGNAL_STRENGTH),
+                new DecimalType(0));
+
+        json = """
+                {
+                    "@type": "communicationQualityState",
+                    "quality": "GOOD"
+                }
+                """;
+        jsonObject = JsonParser.parseString(json);
+
+        getFixture().processUpdate("CommunicationQuality", jsonObject);
+        verify(getCallback()).stateUpdated(
+                new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_SIGNAL_STRENGTH),
+                new DecimalType(4));
+    }
+
+    @Test
+    void updateChannelsWaterLeakageSensorCheckServiceState() {
+        JsonElement jsonObject = JsonParser
+                .parseString("{\"@type\":\"waterLeakageSensorCheckState\",\"result\": \"OK\"}");
+        getFixture().processUpdate("WaterLeakageSensorCheck", jsonObject);
+        verify(getCallback()).stateUpdated(
+                new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_WATER_LEAKAGE_SENSOR_CHECK),
+                new StringType("OK"));
+    }
+
+    @Test
+    void testHandleCommandPushNotificationsNoPreviousState()
+            throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
+        getFixture().handleCommand(
+                new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_PUSH_NOTIFICATIONS_ON_MOVE),
+                OnOffType.ON);
+        verify(getBridgeHandler(), times(0)).putState(any(), any(), any());
+    }
+
+    @Test
+    void testHandleCommandPushNotifications()
+            throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
+        JsonElement jsonObject = JsonParser.parseString(
+                "{\"@type\":\"waterLeakageSensorTiltState\",\"pushNotificationState\": \"DISABLED\",\"acousticSignalState\": \"DISABLED\"}");
+        getFixture().processUpdate("WaterLeakageSensorTilt", jsonObject);
+
+        getFixture().handleCommand(
+                new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_PUSH_NOTIFICATIONS_ON_MOVE),
+                OnOffType.ON);
+        verify(getBridgeHandler()).putState(eq(getDeviceID()), eq("WaterLeakageSensorTilt"),
+                waterLeakageTiltServiceStateCaptor.capture());
+        WaterLeakageSensorTiltServiceState state = waterLeakageTiltServiceStateCaptor.getValue();
+        assertSame(EnabledDisabledState.ENABLED, state.pushNotificationState);
+        assertSame(EnabledDisabledState.DISABLED, state.acousticSignalState);
+    }
+
+    @Test
+    void testHandleCommandAcousticSignals()
+            throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
+        JsonElement jsonObject = JsonParser.parseString(
+                "{\"@type\":\"waterLeakageSensorTiltState\",\"pushNotificationState\": \"DISABLED\",\"acousticSignalState\": \"DISABLED\"}");
+        getFixture().processUpdate("WaterLeakageSensorTilt", jsonObject);
+
+        getFixture().handleCommand(
+                new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_ACOUSTIC_SIGNALS_ON_MOVE),
+                OnOffType.ON);
+        verify(getBridgeHandler()).putState(eq(getDeviceID()), eq("WaterLeakageSensorTilt"),
+                waterLeakageTiltServiceStateCaptor.capture());
+        WaterLeakageSensorTiltServiceState state = waterLeakageTiltServiceStateCaptor.getValue();
+        assertSame(EnabledDisabledState.DISABLED, state.pushNotificationState);
+        assertSame(EnabledDisabledState.ENABLED, state.acousticSignalState);
+    }
+
+    @Test
+    void testHandleCommandPushNotificationsInvalidCommandType()
+            throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
+        getFixture().handleCommand(
+                new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_PUSH_NOTIFICATIONS_ON_MOVE),
+                new StringType("test"));
+        verify(getBridgeHandler(), times(0)).putState(any(), any(), any());
+    }
+
+    @Test
+    void testHandleCommandAcousticSignalsInvalidCommandType()
+            throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
+        getFixture().handleCommand(
+                new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_ACOUSTIC_SIGNALS_ON_MOVE),
+                new StringType("test"));
+        verify(getBridgeHandler(), times(0)).putState(any(), any(), any());
+    }
+
+    @Test
+    void testHandleCommandPushNotificationsInvalidChannel()
+            throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
+        getFixture().handleCommand(new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_POWER_SWITCH),
+                OnOffType.ON);
+        verify(getBridgeHandler(), times(0)).putState(any(), any(), any());
+    }
+
+    @Test
+    void processMessageTiltDetected() {
+        Message message = new Message();
+        message.sourceType = Message.SOURCE_TYPE_DEVICE;
+        MessageCode messageCode = new MessageCode();
+        messageCode.name = WaterLeakageSensorHandler.MESSAGE_CODE_TILT_DETECTED;
+        messageCode.category = "WARNING";
+        message.messageCode = messageCode;
+
+        getFixture().processMessage(message);
+
+        verify(getCallback()).channelTriggered(getThing(),
+                new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_SENSOR_MOVED), "");
+    }
+}
diff --git a/bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/services/cameranotification/CameraNotificationStateTest.java b/bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/services/cameranotification/CameraNotificationStateTest.java
deleted file mode 100644 (file)
index db43609..0000000
+++ /dev/null
@@ -1,41 +0,0 @@
-/**
- * Copyright (c) 2010-2024 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.cameranotification;
-
-import static org.junit.jupiter.api.Assertions.assertSame;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.junit.jupiter.api.Test;
-import org.openhab.core.library.types.OnOffType;
-
-/**
- * Unit tests for {@link CameraNotificationState}.
- * 
- * @author David Pace - Initial contribution
- *
- */
-@NonNullByDefault
-class CameraNotificationStateTest {
-
-    @Test
-    void testFromOnOffType() {
-        assertSame(CameraNotificationState.ENABLED, CameraNotificationState.from(OnOffType.ON));
-        assertSame(CameraNotificationState.DISABLED, CameraNotificationState.from(OnOffType.OFF));
-    }
-
-    @Test
-    void testToOnOffType() {
-        assertSame(OnOffType.ON, CameraNotificationState.ENABLED.toOnOffType());
-        assertSame(OnOffType.OFF, CameraNotificationState.DISABLED.toOnOffType());
-    }
-}
diff --git a/bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/services/dto/EnabledDisabledStateTest.java b/bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/services/dto/EnabledDisabledStateTest.java
new file mode 100644 (file)
index 0000000..ebae42c
--- /dev/null
@@ -0,0 +1,41 @@
+/**
+ * Copyright (c) 2010-2024 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.dto;
+
+import static org.junit.jupiter.api.Assertions.assertSame;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.junit.jupiter.api.Test;
+import org.openhab.core.library.types.OnOffType;
+
+/**
+ * Unit tests for {@link EnabledDisabledState}.
+ * 
+ * @author David Pace - Initial contribution
+ *
+ */
+@NonNullByDefault
+class EnabledDisabledStateTest {
+
+    @Test
+    void testFromOnOffType() {
+        assertSame(EnabledDisabledState.ENABLED, EnabledDisabledState.from(OnOffType.ON));
+        assertSame(EnabledDisabledState.DISABLED, EnabledDisabledState.from(OnOffType.OFF));
+    }
+
+    @Test
+    void testToOnOffType() {
+        assertSame(OnOffType.ON, EnabledDisabledState.ENABLED.toOnOffType());
+        assertSame(OnOffType.OFF, EnabledDisabledState.DISABLED.toOnOffType());
+    }
+}
diff --git a/bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/services/privacymode/PrivacyModeStateTest.java b/bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/services/privacymode/PrivacyModeStateTest.java
deleted file mode 100644 (file)
index 9a0df66..0000000
+++ /dev/null
@@ -1,41 +0,0 @@
-/**
- * Copyright (c) 2010-2024 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.privacymode;
-
-import static org.junit.jupiter.api.Assertions.assertSame;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.junit.jupiter.api.Test;
-import org.openhab.core.library.types.OnOffType;
-
-/**
- * Unit tests for {@link PrivacyModeState}.
- * 
- * @author David Pace - Initial contribution
- *
- */
-@NonNullByDefault
-class PrivacyModeStateTest {
-
-    @Test
-    void testFromOnOffType() {
-        assertSame(PrivacyModeState.ENABLED, PrivacyModeState.from(OnOffType.ON));
-        assertSame(PrivacyModeState.DISABLED, PrivacyModeState.from(OnOffType.OFF));
-    }
-
-    @Test
-    void testToOnOffType() {
-        assertSame(OnOffType.ON, PrivacyModeState.ENABLED.toOnOffType());
-        assertSame(OnOffType.OFF, PrivacyModeState.DISABLED.toOnOffType());
-    }
-}