]> git.basschouten.com Git - openhab-addons.git/commitdiff
[hue] Support new home security products (#15601)
authorAndrew Fiddian-Green <software@whitebear.ch>
Thu, 9 Nov 2023 07:09:20 +0000 (07:09 +0000)
committerGitHub <noreply@github.com>
Thu, 9 Nov 2023 07:09:20 +0000 (08:09 +0100)
Signed-off-by: Andrew Fiddian-Green <software@whitebear.ch>
16 files changed:
bundles/org.openhab.binding.hue/doc/readme_v2.md
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/HueBindingConstants.java
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/ContactReport.java [new file with mode: 0644]
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/Resource.java
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/TamperReport.java [new file with mode: 0644]
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/enums/ContactStateType.java [new file with mode: 0644]
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/enums/ResourceType.java
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/enums/TamperStateType.java [new file with mode: 0644]
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/Clip2ThingHandler.java
bundles/org.openhab.binding.hue/src/main/resources/OH-INF/i18n/hue.properties
bundles/org.openhab.binding.hue/src/main/resources/OH-INF/thing/Clip2Thing.xml
bundles/org.openhab.binding.hue/src/main/resources/OH-INF/thing/channels.xml
bundles/org.openhab.binding.hue/src/test/java/org/openhab/binding/hue/internal/clip2/Clip2DtoTest.java
bundles/org.openhab.binding.hue/src/test/resources/camera_motion.json [new file with mode: 0644]
bundles/org.openhab.binding.hue/src/test/resources/contact.json [new file with mode: 0644]
bundles/org.openhab.binding.hue/src/test/resources/tamper.json [new file with mode: 0644]

index 206bc476a82d2ea17639a51475a00f298514b53f..d9b84392f0a3a180de3c00666f58f3d207c58b87 100644 (file)
@@ -59,35 +59,40 @@ The configuration of all things (as described above) is the same regardless of w
 
 Device things support some of the following channels:
 
-| Channel ID                | Item Type          | Description                                                                                                         |
-|---------------------------|--------------------|---------------------------------------------------------------------------------------------------------------------|
-| color                     | Color              | Supports full color control with hue, saturation and brightness values, or brightness only, or switching on or off. |
-| brightness                | Dimmer             | Supports control of the brightness value, or switching on or off.                                                   |
-| color-temperature         | Dimmer             | Supports control of the color temperature in percent from cold (0%) to warm (100%).                                 |
-| color-temperature-abs     | Number:Temperature | Supports control of the color temperature via a QuantityType having a temperature unit e.g. Kelvin. (Advanced)      |
-| switch                    | Switch             | Supports switching the device on and off.                                                                           |
-| dynamics                  | Number:Time        | Sets the duration of dynamic transitions between light states. (Advanced)                                           |
-| alert                     | String             | Allows setting an alert on a light e.g. flashing them. (Advanced)                                                   |
-| effect                    | String             | Allows setting an effect on a light e.g. 'candle' effect. (Advanced)                                                |
-| button-last-event         | (String)           | Informs which button was last pressed in the device. (Trigger Channel)                                              |
-| button-last-updated       | DateTime           | The date and time when a button was last pressed. (Read Only) (Advanced)                                            |
-| rotary-steps              | (String)           | Informs about the number of rotary steps of the last rotary dial movement. (Trigger Channel)                        |
-| rotary-steps-last-updated | DateTime           | The date and time when the rotary steps were last updated. (Read Only) (Advanced)                                   |
-| motion                    | Switch             | Shows if motion has been detected by the sensor. (Read Only)                                                        |
-| motion-enabled            | Switch             | Supports enabling / disabling the motion sensor. (Advanced)                                                         |
-| motion-last-updated       | DateTime           | The date and time when the motion value was last updated. (Read Only) (Advanced)                                    |
-| light-level               | Number:Illuminance | Shows the current light level measured by the sensor. (Read Only)                                                   |
-| light-level-last-updated  | DateTime           | The date and time when the light level was last updated. (Read Only) (Advanced)                                     |
-| light-level-enabled       | Switch             | Supports enabling / disabling the light level sensor. (Advanced)                                                    |
-| temperature               | Number:Temperature | Shows the current temperature measured by the sensor. (Read Only)                                                   |
-| temperature-last-updated  | DateTime           | The date and time when the temperature was last updated. (Read Only) (Advanced)                                     |
-| temperature-enabled       | Switch             | Supports enabling / disabling the temperature sensor. (Advanced)                                                    |
-| battery-level             | Number             | Shows the battery level. (Read Only)                                                                                |
-| battery-low               | Switch             | Indicates whether the battery is low or not. (Read Only)                                                            |
-| last-updated              | DateTime           | The date and time when the thing state was last updated. (Read Only) (Advanced)                                     |
-| color-xy-only             | Color              | Allows access to the `color-xy` parameter of the light(s) only. Has no impact on `dimming` or `on-off` parameters.  |
-| dimming-only              | Dimmer             | Allows access to the `dimming` parameter of the light(s) only. Has no impact on `color-xy` or `on-off` parameters.  |
-| on-off-only               | Switch             | Allows access to the `on-off` parameter of the light(s) only. Has no impact on `color-xy` or `dimming` parameters.  |
+| Channel ID                    | Item Type          | Description                                                                                                         |
+|-------------------------------|--------------------|---------------------------------------------------------------------------------------------------------------------|
+| color                         | Color              | Supports full color control with hue, saturation and brightness values, or brightness only, or switching on or off. |
+| brightness                    | Dimmer             | Supports control of the brightness value, or switching on or off.                                                   |
+| color-temperature             | Dimmer             | Supports control of the color temperature in percent from cold (0%) to warm (100%).                                 |
+| color-temperature-abs         | Number:Temperature | Supports control of the color temperature via a QuantityType having a temperature unit e.g. Kelvin. (Advanced)      |
+| switch                        | Switch             | Supports switching the device on and off.                                                                           |
+| dynamics                      | Number:Time        | Sets the duration of dynamic transitions between light states. (Advanced)                                           |
+| alert                         | String             | Allows setting an alert on a light e.g. flashing them. (Advanced)                                                   |
+| effect                        | String             | Allows setting an effect on a light e.g. 'candle' effect. (Advanced)                                                |
+| button-last-event             | (String)           | Informs which button was last pressed in the device. (Trigger Channel)                                              |
+| button-last-updated           | DateTime           | The date and time when a button was last pressed. (Read Only) (Advanced)                                            |
+| rotary-steps                  | (String)           | Informs about the number of rotary steps of the last rotary dial movement. (Trigger Channel)                        |
+| rotary-steps-last-updated     | DateTime           | The date and time when the rotary steps were last updated. (Read Only) (Advanced)                                   |
+| motion                        | Switch             | Shows if motion has been detected by the sensor. (Read Only)                                                        |
+| motion-enabled                | Switch             | Supports enabling / disabling the motion sensor. (Advanced)                                                         |
+| motion-last-updated           | DateTime           | The date and time when the motion value was last updated. (Read Only) (Advanced)                                    |
+| light-level                   | Number:Illuminance | Shows the current light level measured by the sensor. (Read Only)                                                   |
+| light-level-last-updated      | DateTime           | The date and time when the light level was last updated. (Read Only) (Advanced)                                     |
+| light-level-enabled           | Switch             | Supports enabling / disabling the light level sensor. (Advanced)                                                    |
+| temperature                   | Number:Temperature | Shows the current temperature measured by the sensor. (Read Only)                                                   |
+| temperature-last-updated      | DateTime           | The date and time when the temperature was last updated. (Read Only) (Advanced)                                     |
+| temperature-enabled           | Switch             | Supports enabling / disabling the temperature sensor. (Advanced)                                                    |
+| battery-level                 | Number             | Shows the battery level. (Read Only)                                                                                |
+| battery-low                   | Switch             | Indicates whether the battery is low or not. (Read Only)                                                            |
+| last-updated                  | DateTime           | The date and time when the thing state was last updated. (Read Only) (Advanced)                                     |
+| color-xy-only                 | Color              | Allows access to the `color-xy` parameter of the light(s) only. Has no impact on `dimming` or `on-off` parameters.  |
+| dimming-only                  | Dimmer             | Allows access to the `dimming` parameter of the light(s) only. Has no impact on `color-xy` or `on-off` parameters.  |
+| on-off-only                   | Switch             | Allows access to the `on-off` parameter of the light(s) only. Has no impact on `color-xy` or `dimming` parameters.  |
+| security-contact              | Contact            | Indicates whether a security contact has been triggered. (Read Only)                                                |
+| security-contact-enabled      | Switch             | Supports enabling / disabling the security contact. (Advanced)                                                      |
+| security-contact-last-updated | DateTime           | The date and time when the security contact state was last updated. (Read Only) (Advanced)                          |
+| security-tamper               | Contact            | Indicates whether a security tamper contact has been triggered. `Open` means tampering detected. (Read Only)        |
+| security-tamper-last-updated  | DateTime           | The date and time when the security tamper contact state was last updated. (Read Only) (Advanced)                   |
 
 The exact list of channels in a given device is determined at run time when the system is started.
 Each device reports its own live list of capabilities, and the respective list of channels is created accordingly.
index 829b3464b9d59f5045fb4f4a083e8f117834feaf..67af11d4c995cb76dfa9eef73631da8bec124827 100644 (file)
@@ -169,6 +169,11 @@ public class HueBindingConstants {
     public static final String CHANNEL_2_COLOR_XY_ONLY = "color-xy-only";
     public static final String CHANNEL_2_DIMMING_ONLY = "dimming-only";
     public static final String CHANNEL_2_ON_OFF_ONLY = "on-off-only";
+    public static final String CHANNEL_2_SECURITY_CONTACT = "security-contact";
+    public static final String CHANNEL_2_SECURITY_CONTACT_ENABLED = "security-contact-enabled";
+    public static final String CHANNEL_2_SECURITY_CONTACT_LAST_UPDATED = "security-contact-last-updated";
+    public static final String CHANNEL_2_SECURITY_TAMPER = "security-tamper";
+    public static final String CHANNEL_2_SECURITY_TAMPER_LAST_UPDATED = "security-tamper-last-updated";
 
     // channel IDs that (optionally) support dynamics
     public static final Set<String> DYNAMIC_CHANNELS = Set.of(CHANNEL_2_BRIGHTNESS, CHANNEL_2_COLOR,
diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/ContactReport.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/ContactReport.java
new file mode 100644 (file)
index 0000000..99c8c2a
--- /dev/null
@@ -0,0 +1,48 @@
+/**
+ * Copyright (c) 2010-2023 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.hue.internal.dto.clip2;
+
+import java.time.Instant;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.hue.internal.dto.clip2.enums.ContactStateType;
+
+/**
+ * DTO for CLIP 2 home security alarm contact.
+ *
+ * @author Andrew Fiddian-Green - Initial contribution
+ */
+@NonNullByDefault
+public class ContactReport {
+
+    private @NonNullByDefault({}) Instant changed;
+    private @NonNullByDefault({}) String state;
+
+    public ContactStateType getContactState() throws IllegalArgumentException {
+        return ContactStateType.valueOf(state.toUpperCase());
+    }
+
+    public Instant getLastChanged() {
+        return changed;
+    }
+
+    public ContactReport setLastChanged(Instant changed) {
+        this.changed = changed;
+        return this;
+    }
+
+    public ContactReport setContactState(String state) {
+        this.state = state;
+        return this;
+    }
+}
index 41fcae3a6cb6d78539d1f3eb1cda41c605a3e66e..2791bbeab535c28cb1cc4017760b5e78ffc3987f 100644 (file)
@@ -28,17 +28,20 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.eclipse.jdt.annotation.Nullable;
 import org.openhab.binding.hue.internal.dto.clip2.enums.ActionType;
 import org.openhab.binding.hue.internal.dto.clip2.enums.ButtonEventType;
+import org.openhab.binding.hue.internal.dto.clip2.enums.ContactStateType;
 import org.openhab.binding.hue.internal.dto.clip2.enums.EffectType;
 import org.openhab.binding.hue.internal.dto.clip2.enums.ResourceType;
 import org.openhab.binding.hue.internal.dto.clip2.enums.SceneRecallAction;
 import org.openhab.binding.hue.internal.dto.clip2.enums.SmartSceneRecallAction;
 import org.openhab.binding.hue.internal.dto.clip2.enums.SmartSceneState;
+import org.openhab.binding.hue.internal.dto.clip2.enums.TamperStateType;
 import org.openhab.binding.hue.internal.dto.clip2.enums.ZigbeeStatus;
 import org.openhab.binding.hue.internal.exceptions.DTOPresentButEmptyException;
 import org.openhab.core.library.types.DateTimeType;
 import org.openhab.core.library.types.DecimalType;
 import org.openhab.core.library.types.HSBType;
 import org.openhab.core.library.types.OnOffType;
+import org.openhab.core.library.types.OpenClosedType;
 import org.openhab.core.library.types.PercentType;
 import org.openhab.core.library.types.QuantityType;
 import org.openhab.core.library.types.StringType;
@@ -103,6 +106,8 @@ public class Resource {
     private @Nullable List<ResourceReference> children;
     private @Nullable JsonElement status;
     private @Nullable @SuppressWarnings("unused") Dynamics dynamics;
+    private @Nullable @SerializedName("contact_report") ContactReport contactReport;
+    private @Nullable @SerializedName("tamper_reports") List<TamperReport> tamperReports;
     private @Nullable String state;
 
     /**
@@ -325,6 +330,20 @@ public class Resource {
         return UnDefType.NULL;
     }
 
+    public State getContactLastUpdatedState(ZoneId zoneId) {
+        ContactReport contactReport = this.contactReport;
+        return Objects.nonNull(contactReport)
+                ? new DateTimeType(ZonedDateTime.ofInstant(contactReport.getLastChanged(), zoneId))
+                : UnDefType.NULL;
+    }
+
+    public State getContactState() {
+        ContactReport contactReport = this.contactReport;
+        return Objects.isNull(contactReport) ? UnDefType.NULL
+                : ContactStateType.CONTACT == contactReport.getContactState() ? OpenClosedType.CLOSED
+                        : OpenClosedType.OPEN;
+    }
+
     public int getControlId() {
         MetaData metadata = this.metadata;
         return Objects.nonNull(metadata) ? metadata.getControlId() : 0;
@@ -649,6 +668,33 @@ public class Resource {
         return new JsonObject();
     }
 
+    public State getTamperLastUpdatedState(ZoneId zoneId) {
+        TamperReport report = getTamperReportsLatest();
+        return Objects.nonNull(report) ? new DateTimeType(ZonedDateTime.ofInstant(report.getLastChanged(), zoneId))
+                : UnDefType.NULL;
+    }
+
+    /**
+     * The the Hue bridge could return its raw list of tamper reports in any order, so sort the list (latest entry
+     * first) according to the respective 'changed' instant and return the first entry i.e. the latest changed entry.
+     *
+     * @return the latest changed tamper report
+     */
+    private @Nullable TamperReport getTamperReportsLatest() {
+        List<TamperReport> reports = this.tamperReports;
+        return Objects.nonNull(reports)
+                ? reports.stream().sorted((e1, e2) -> e2.getLastChanged().compareTo(e1.getLastChanged())).findFirst()
+                        .orElse(null)
+                : null;
+    }
+
+    public State getTamperState() {
+        TamperReport report = getTamperReportsLatest();
+        return Objects.nonNull(report)
+                ? TamperStateType.TAMPERED == report.getTamperState() ? OpenClosedType.OPEN : OpenClosedType.CLOSED
+                : UnDefType.NULL;
+    }
+
     public @Nullable Temperature getTemperature() {
         return temperature;
     }
@@ -736,6 +782,11 @@ public class Resource {
         return this;
     }
 
+    public Resource setContactReport(ContactReport contactReport) {
+        this.contactReport = contactReport;
+        return this;
+    }
+
     public Resource setDimming(Dimming dimming) {
         this.dimming = dimming;
         return this;
@@ -815,6 +866,11 @@ public class Resource {
         return this;
     }
 
+    public Resource setTamperReports(List<TamperReport> tamperReports) {
+        this.tamperReports = tamperReports;
+        return this;
+    }
+
     public Resource setTimedEffects(TimedEffects timedEffects) {
         this.timedEffects = timedEffects;
         return this;
diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/TamperReport.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/TamperReport.java
new file mode 100644 (file)
index 0000000..50d2508
--- /dev/null
@@ -0,0 +1,48 @@
+/**
+ * Copyright (c) 2010-2023 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.hue.internal.dto.clip2;
+
+import java.time.Instant;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.hue.internal.dto.clip2.enums.TamperStateType;
+
+/**
+ * DTO for CLIP 2 home security tamper switch.
+ *
+ * @author Andrew Fiddian-Green - Initial contribution
+ */
+@NonNullByDefault
+public class TamperReport {
+
+    private @NonNullByDefault({}) Instant changed;
+    private @NonNullByDefault({}) String state;
+
+    public Instant getLastChanged() {
+        return changed;
+    }
+
+    public TamperStateType getTamperState() throws IllegalArgumentException {
+        return TamperStateType.valueOf(state.toUpperCase());
+    }
+
+    public TamperReport setLastChanged(Instant changed) {
+        this.changed = changed;
+        return this;
+    }
+
+    public TamperReport setTamperState(String state) {
+        this.state = state;
+        return this;
+    }
+}
diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/enums/ContactStateType.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/enums/ContactStateType.java
new file mode 100644 (file)
index 0000000..2892e77
--- /dev/null
@@ -0,0 +1,26 @@
+/**
+ * Copyright (c) 2010-2023 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.hue.internal.dto.clip2.enums;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * Enum for security contact states.
+ *
+ * @author Andrew Fiddian-Green - Initial contribution
+ */
+@NonNullByDefault
+public enum ContactStateType {
+    NO_CONTACT,
+    CONTACT
+}
index f08566c77f9f21959c2d12c07b84396ecb6457c9..56c2f0061be3e91536600d134ea91bfd7727b5f8 100644 (file)
@@ -31,6 +31,8 @@ public enum ResourceType {
     BRIDGE,
     BRIDGE_HOME,
     BUTTON,
+    CAMERA_MOTION,
+    CONTACT,
     DEVICE,
     DEVICE_POWER,
     ENTERTAINMENT,
@@ -47,6 +49,7 @@ public enum ResourceType {
     ROOM,
     RELATIVE_ROTARY,
     SCENE,
+    TAMPER,
     SMART_SCENE,
     TEMPERATURE,
     ZGP_CONNECTIVITY,
diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/enums/TamperStateType.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/enums/TamperStateType.java
new file mode 100644 (file)
index 0000000..23199b1
--- /dev/null
@@ -0,0 +1,26 @@
+/**
+ * Copyright (c) 2010-2023 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.hue.internal.dto.clip2.enums;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * Enum for tamper switch states.
+ *
+ * @author Andrew Fiddian-Green - Initial contribution
+ */
+@NonNullByDefault
+public enum TamperStateType {
+    NOT_TAMPERED,
+    TAMPERED
+}
index 1f2c3a7292e107663dadada4ba76b0b638adbbb2..a436f7f889a4f9eef7ebae4f989616546e9d1586 100644 (file)
@@ -433,6 +433,10 @@ public class Clip2ThingHandler extends BaseThingHandler {
                 putResource = new Resource(ResourceType.LIGHT_LEVEL).setEnabled(command);
                 break;
 
+            case CHANNEL_2_SECURITY_CONTACT_ENABLED:
+                putResource = new Resource(ResourceType.CONTACT).setEnabled(command);
+                break;
+
             case CHANNEL_2_SCENE:
                 if (command instanceof StringType) {
                     Resource scene = sceneResourceEntries.get(((StringType) command).toString());
@@ -888,6 +892,7 @@ public class Clip2ThingHandler extends BaseThingHandler {
                 break;
 
             case MOTION:
+            case CAMERA_MOTION:
                 updateState(CHANNEL_2_MOTION, resource.getMotionState(), fullUpdate);
                 updateState(CHANNEL_2_MOTION_LAST_UPDATED,
                         resource.getMotionLastUpdatedState(timeZoneProvider.getTimeZone()), fullUpdate);
@@ -920,6 +925,19 @@ public class Clip2ThingHandler extends BaseThingHandler {
                 updateState(CHANNEL_2_SCENE, resource.getSceneState(), fullUpdate);
                 break;
 
+            case CONTACT:
+                updateState(CHANNEL_2_SECURITY_CONTACT, resource.getContactState(), fullUpdate);
+                updateState(CHANNEL_2_SECURITY_CONTACT_LAST_UPDATED,
+                        resource.getContactLastUpdatedState(timeZoneProvider.getTimeZone()), fullUpdate);
+                updateState(CHANNEL_2_SECURITY_CONTACT_ENABLED, resource.getEnabledState(), fullUpdate);
+                break;
+
+            case TAMPER:
+                updateState(CHANNEL_2_SECURITY_TAMPER, resource.getTamperState(), fullUpdate);
+                updateState(CHANNEL_2_SECURITY_TAMPER_LAST_UPDATED,
+                        resource.getTamperLastUpdatedState(timeZoneProvider.getTimeZone()), fullUpdate);
+                break;
+
             case SMART_SCENE:
                 updateState(CHANNEL_2_SCENE, resource.getSmartSceneState(), fullUpdate);
                 break;
index d8b6dd1b38b343ba7d3656ef5463a489d68d7247..3fa66d65d9a0339a9f8b51182202468eda4efc6a 100644 (file)
@@ -55,6 +55,15 @@ thing-type.hue.device.channel.motion-last-updated.description = The date and tim
 thing-type.hue.device.channel.on-off-only.description = Set the on/off parameter of the light without changing other state parameters.
 thing-type.hue.device.channel.rotary-steps-last-updated.label = Rotary Steps Last Updated
 thing-type.hue.device.channel.rotary-steps-last-updated.description = The date and time when the rotary steps were last updated.
+thing-type.hue.device.channel.security-contact.label = Security Contact
+thing-type.hue.device.channel.security-contact.description = Open or closed state of the contact.
+thing-type.hue.device.channel.security-contact-enabled.description = Security contact enabled.
+thing-type.hue.device.channel.security-contact-last-updated.label = Security Contact Last Updated
+thing-type.hue.device.channel.security-contact-last-updated.description = The date and time when the contact state was last updated.
+thing-type.hue.device.channel.security-tamper.label = Security Tamper Contact
+thing-type.hue.device.channel.security-tamper.description = Tamper or no tamper state of the sensor.
+thing-type.hue.device.channel.security-tamper-last-updated.label = Tamper Contact Last Updated
+thing-type.hue.device.channel.security-tamper-last-updated.description = The date and time when the tamper contact state was last updated.
 thing-type.hue.device.channel.temperature.label = Temperature
 thing-type.hue.device.channel.temperature.description = Temperature at the sensor location.
 thing-type.hue.device.channel.temperature-enabled.description = Temperature sensor enabled.
@@ -183,6 +192,8 @@ channel-type.hue.rotary-steps.description = The last 'steps' value (e.g. +/-30)
 channel-type.hue.scene-v2.label = Scene
 channel-type.hue.scene.label = Scene
 channel-type.hue.scene.description = The scene channel allows recalling a scene to all lights that belong to the scene.
+channel-type.hue.security-contact.label = Open/Closed
+channel-type.hue.security-tamper.label = Normal/Tamper
 channel-type.hue.sensor-enabled.label = Sensor Enabled
 channel-type.hue.status.label = Status
 channel-type.hue.status.description = Status of CLIP sensor.
index 03419d9a88ab1057dd3e72b4d9378cc9278fcb67..33f5d935c5608a4f534cfcac69fb96f401d17791 100644 (file)
                        <channel id="temperature-enabled" typeId="sensor-enabled">
                                <description>Temperature sensor enabled.</description>
                        </channel>
+                       <channel id="security-contact" typeId="security-contact">
+                               <label>Security Contact</label>
+                               <description>Open or closed state of the contact.</description>
+                       </channel>
+                       <channel id="security-contact-enabled" typeId="sensor-enabled">
+                               <description>Security contact enabled.</description>
+                       </channel>
+                       <channel id="security-contact-last-updated" typeId="last-updated-v2">
+                               <label>Security Contact Last Updated</label>
+                               <description>The date and time when the contact state was last updated.</description>
+                       </channel>
+                       <channel id="security-tamper" typeId="security-tamper">
+                               <label>Security Tamper Contact</label>
+                               <description>Tamper or no tamper state of the sensor.</description>
+                       </channel>
+                       <channel id="security-tamper-last-updated" typeId="last-updated-v2">
+                               <label>Tamper Contact Last Updated</label>
+                               <description>The date and time when the tamper contact state was last updated.</description>
+                       </channel>
                        <channel id="battery-level" typeId="system.battery-level"/>
                        <channel id="battery-low" typeId="system.low-battery"/>
                        <channel id="last-updated" typeId="last-updated-v2"/>
index 6e51b1761a522db1e60ffe0b22c65684294af60d..c1f82b5516ac7d2c67dbb72e9b5854d1ab6f15eb 100644 (file)
                <category>Switch</category>
        </channel-type>
 
+       <channel-type id="security-contact">
+               <item-type>Contact</item-type>
+               <label>Open/Closed</label>
+               <category>Lock</category>
+       </channel-type>
+
+       <channel-type id="security-tamper">
+               <item-type>Contact</item-type>
+               <label>Normal/Tamper</label>
+               <category>Siren</category>
+       </channel-type>
+
 </thing:thing-descriptions>
index 059a06ba0459a76384c84df47a20fe4d310de3e1..79b32776d512db13f77000ba01586839614618a6 100644 (file)
@@ -21,6 +21,7 @@ import java.lang.reflect.Field;
 import java.time.Duration;
 import java.time.Instant;
 import java.time.ZoneId;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
@@ -31,6 +32,7 @@ import org.junit.jupiter.api.Test;
 import org.openhab.binding.hue.internal.dto.clip2.ActionEntry;
 import org.openhab.binding.hue.internal.dto.clip2.Alerts;
 import org.openhab.binding.hue.internal.dto.clip2.Button;
+import org.openhab.binding.hue.internal.dto.clip2.ContactReport;
 import org.openhab.binding.hue.internal.dto.clip2.Dimming;
 import org.openhab.binding.hue.internal.dto.clip2.Effects;
 import org.openhab.binding.hue.internal.dto.clip2.Event;
@@ -46,6 +48,7 @@ import org.openhab.binding.hue.internal.dto.clip2.ResourceReference;
 import org.openhab.binding.hue.internal.dto.clip2.Resources;
 import org.openhab.binding.hue.internal.dto.clip2.Rotation;
 import org.openhab.binding.hue.internal.dto.clip2.RotationEvent;
+import org.openhab.binding.hue.internal.dto.clip2.TamperReport;
 import org.openhab.binding.hue.internal.dto.clip2.Temperature;
 import org.openhab.binding.hue.internal.dto.clip2.TimedEffects;
 import org.openhab.binding.hue.internal.dto.clip2.enums.ActionType;
@@ -63,6 +66,7 @@ import org.openhab.core.library.types.DateTimeType;
 import org.openhab.core.library.types.DecimalType;
 import org.openhab.core.library.types.HSBType;
 import org.openhab.core.library.types.OnOffType;
+import org.openhab.core.library.types.OpenClosedType;
 import org.openhab.core.library.types.PercentType;
 import org.openhab.core.library.types.QuantityType;
 import org.openhab.core.library.types.StringType;
@@ -707,6 +711,91 @@ class Clip2DtoTest {
     }
 
     @Test
+    void testSecurityContact() {
+        String json = load(ResourceType.CONTACT.name().toLowerCase());
+        Resources resources = GSON.fromJson(json, Resources.class);
+        assertNotNull(resources);
+        List<Resource> list = resources.getResources();
+        assertNotNull(list);
+        assertEquals(1, list.size());
+        Resource resource = list.get(0);
+        assertEquals(ResourceType.CONTACT, resource.getType());
+
+        assertEquals(OpenClosedType.CLOSED, resource.getContactState());
+        assertEquals(new DateTimeType("2023-10-10T19:10:55.919Z"),
+                resource.getContactLastUpdatedState(ZoneId.of("UTC")));
+
+        resource.setContactReport(new ContactReport().setLastChanged(Instant.now()).setContactState("no_contact"));
+        assertEquals(OpenClosedType.OPEN, resource.getContactState());
+        assertTrue(resource.getContactLastUpdatedState(ZoneId.of("UTC")) instanceof DateTimeType);
+    }
+
+    @Test
+    void testSecurityTamper() {
+        String json = load(ResourceType.TAMPER.name().toLowerCase());
+        Resources resources = GSON.fromJson(json, Resources.class);
+        assertNotNull(resources);
+        List<Resource> list = resources.getResources();
+        assertNotNull(list);
+        assertEquals(1, list.size());
+        Resource resource = list.get(0);
+        assertEquals(ResourceType.TAMPER, resource.getType());
+
+        assertEquals(OpenClosedType.CLOSED, resource.getTamperState());
+        assertEquals(new DateTimeType("2023-01-01T00:00:00.001Z"),
+                resource.getTamperLastUpdatedState(ZoneId.of("UTC")));
+
+        Instant start = Instant.now();
+        List<TamperReport> tamperReports;
+        State state;
+
+        tamperReports = new ArrayList<>();
+        tamperReports.add(new TamperReport().setTamperState("not_tampered").setLastChanged(start));
+        resource.setTamperReports(tamperReports);
+        assertEquals(OpenClosedType.CLOSED, resource.getTamperState());
+        state = resource.getTamperLastUpdatedState(ZoneId.of("UTC"));
+        assertTrue(state instanceof DateTimeType);
+        assertEquals(start, ((DateTimeType) state).getInstant());
+
+        tamperReports = new ArrayList<>();
+        tamperReports.add(new TamperReport().setTamperState("not_tampered").setLastChanged(start));
+        tamperReports.add(new TamperReport().setTamperState("tampered").setLastChanged(start.plusSeconds(1)));
+        resource.setTamperReports(tamperReports);
+        assertEquals(OpenClosedType.OPEN, resource.getTamperState());
+        state = resource.getTamperLastUpdatedState(ZoneId.of("UTC"));
+        assertTrue(state instanceof DateTimeType);
+        assertEquals(start.plusSeconds(1), ((DateTimeType) state).getInstant());
+
+        tamperReports = new ArrayList<>();
+        tamperReports.add(new TamperReport().setTamperState("not_tampered").setLastChanged(start));
+        tamperReports.add(new TamperReport().setTamperState("tampered").setLastChanged(start.plusSeconds(1)));
+        tamperReports.add(new TamperReport().setTamperState("not_tampered").setLastChanged(start.plusSeconds(2)));
+        resource.setTamperReports(tamperReports);
+        assertEquals(OpenClosedType.CLOSED, resource.getTamperState());
+        state = resource.getTamperLastUpdatedState(ZoneId.of("UTC"));
+        assertTrue(state instanceof DateTimeType);
+        assertEquals(start.plusSeconds(2), ((DateTimeType) state).getInstant());
+    }
+
+    @Test
+    void testCameraMotion() {
+        String json = load(ResourceType.CAMERA_MOTION.name().toLowerCase());
+        Resources resources = GSON.fromJson(json, Resources.class);
+        assertNotNull(resources);
+        List<Resource> list = resources.getResources();
+        assertNotNull(list);
+        assertEquals(1, list.size());
+        Resource resource = list.get(0);
+        assertEquals(ResourceType.CAMERA_MOTION, resource.getType());
+
+        Boolean enabled = resource.getEnabled();
+        assertNotNull(enabled);
+        assertTrue(enabled);
+        assertEquals(OnOffType.ON, resource.getMotionState());
+        assertEquals(new DateTimeType("2020-04-01T20:04:30.395Z"),
+                resource.getMotionLastUpdatedState(ZoneId.of("UTC")));
+    }
+
     void testFixedEffectSetter() {
         Resource source;
         Resource target;
diff --git a/bundles/org.openhab.binding.hue/src/test/resources/camera_motion.json b/bundles/org.openhab.binding.hue/src/test/resources/camera_motion.json
new file mode 100644 (file)
index 0000000..c678d62
--- /dev/null
@@ -0,0 +1,28 @@
+{
+    "errors": [],
+    "data": [
+        {
+            "id": "00000000-0000-0000-0000-000000000005",
+            "id_v1": "/sensors/5",
+            "owner": {
+                "rid": "00000000-0000-0000-0000-000000000000",
+                "rtype": "device"
+            },
+            "enabled": true,
+            "motion": {
+                "motion": false,
+                "motion_valid": true,
+                "motion_report": {
+                    "changed": "2020-04-01T20:04:30.395Z",
+                    "motion": true
+                }
+            },
+            "sensitivity": {
+                "status": "set",
+                "sensitivity": 2,
+                "sensitivity_max": 4
+            },
+            "type": "camera_motion"
+        }
+    ]
+}
diff --git a/bundles/org.openhab.binding.hue/src/test/resources/contact.json b/bundles/org.openhab.binding.hue/src/test/resources/contact.json
new file mode 100644 (file)
index 0000000..6b7af5b
--- /dev/null
@@ -0,0 +1,19 @@
+{
+    "errors": [
+    ],
+    "data": [
+        {
+            "id": "bcaee909-1b37-454b-814d-9928776ad350",
+            "owner": {
+                "rid": "faac7940-a303-4e8e-9f06-075fffb7229c",
+                "rtype": "device"
+            },
+            "enabled": true,
+            "contact_report": {
+                "changed": "2023-10-10T19:10:55.919Z",
+                "state": "contact"
+            },
+            "type": "contact"
+        }
+    ]
+}
diff --git a/bundles/org.openhab.binding.hue/src/test/resources/tamper.json b/bundles/org.openhab.binding.hue/src/test/resources/tamper.json
new file mode 100644 (file)
index 0000000..10af6dc
--- /dev/null
@@ -0,0 +1,31 @@
+{
+    "errors": [
+    ],
+    "data": [
+        {
+            "id": "6c2ef541-fe03-4cae-ac60-3edcaa93b33e",
+            "owner": {
+                "rid": "faac7940-a303-4e8e-9f06-075fffb7229c",
+                "rtype": "device"
+            },
+            "tamper_reports": [
+                {
+                    "changed": "1970-01-01T00:00:00.000Z",
+                    "source": "battery_door",
+                    "state": "not_tampered"
+                },
+                {
+                    "changed": "2023-01-01T00:00:00.001Z",
+                    "source": "battery_door",
+                    "state": "not_tampered"
+                },
+                {
+                    "changed": "2023-01-01T00:00:00.000Z",
+                    "source": "battery_door",
+                    "state": "tampered"
+                }
+            ],
+            "type": "tamper"
+        }
+    ]
+}