From 4f6d33bb791b2686bde1619c7b28e6c44b0e6458 Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Thu, 9 Nov 2023 07:09:20 +0000 Subject: [PATCH] [hue] Support new home security products (#15601) Signed-off-by: Andrew Fiddian-Green --- .../org.openhab.binding.hue/doc/readme_v2.md | 63 +++++++------ .../hue/internal/HueBindingConstants.java | 5 ++ .../hue/internal/dto/clip2/ContactReport.java | 48 ++++++++++ .../hue/internal/dto/clip2/Resource.java | 56 ++++++++++++ .../hue/internal/dto/clip2/TamperReport.java | 48 ++++++++++ .../dto/clip2/enums/ContactStateType.java | 26 ++++++ .../dto/clip2/enums/ResourceType.java | 3 + .../dto/clip2/enums/TamperStateType.java | 26 ++++++ .../internal/handler/Clip2ThingHandler.java | 18 ++++ .../main/resources/OH-INF/i18n/hue.properties | 11 +++ .../resources/OH-INF/thing/Clip2Thing.xml | 19 ++++ .../main/resources/OH-INF/thing/channels.xml | 12 +++ .../hue/internal/clip2/Clip2DtoTest.java | 89 +++++++++++++++++++ .../src/test/resources/camera_motion.json | 28 ++++++ .../src/test/resources/contact.json | 19 ++++ .../src/test/resources/tamper.json | 31 +++++++ 16 files changed, 473 insertions(+), 29 deletions(-) create mode 100644 bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/ContactReport.java create mode 100644 bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/TamperReport.java create mode 100644 bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/enums/ContactStateType.java create mode 100644 bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/enums/TamperStateType.java create mode 100644 bundles/org.openhab.binding.hue/src/test/resources/camera_motion.json create mode 100644 bundles/org.openhab.binding.hue/src/test/resources/contact.json create mode 100644 bundles/org.openhab.binding.hue/src/test/resources/tamper.json diff --git a/bundles/org.openhab.binding.hue/doc/readme_v2.md b/bundles/org.openhab.binding.hue/doc/readme_v2.md index 206bc476a82..d9b84392f0a 100644 --- a/bundles/org.openhab.binding.hue/doc/readme_v2.md +++ b/bundles/org.openhab.binding.hue/doc/readme_v2.md @@ -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. diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/HueBindingConstants.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/HueBindingConstants.java index 829b3464b9d..67af11d4c99 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/HueBindingConstants.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/HueBindingConstants.java @@ -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 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 index 00000000000..99c8c2a835c --- /dev/null +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/ContactReport.java @@ -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; + } +} diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/Resource.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/Resource.java index 41fcae3a6cb..2791bbeab53 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/Resource.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/Resource.java @@ -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 children; private @Nullable JsonElement status; private @Nullable @SuppressWarnings("unused") Dynamics dynamics; + private @Nullable @SerializedName("contact_report") ContactReport contactReport; + private @Nullable @SerializedName("tamper_reports") List 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 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 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 index 00000000000..50d250898ad --- /dev/null +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/TamperReport.java @@ -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 index 00000000000..2892e77ca18 --- /dev/null +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/enums/ContactStateType.java @@ -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 +} diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/enums/ResourceType.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/enums/ResourceType.java index f08566c77f9..56c2f0061be 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/enums/ResourceType.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/enums/ResourceType.java @@ -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 index 00000000000..23199b1af50 --- /dev/null +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/dto/clip2/enums/TamperStateType.java @@ -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 +} diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/Clip2ThingHandler.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/Clip2ThingHandler.java index 1f2c3a7292e..a436f7f889a 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/Clip2ThingHandler.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/Clip2ThingHandler.java @@ -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; diff --git a/bundles/org.openhab.binding.hue/src/main/resources/OH-INF/i18n/hue.properties b/bundles/org.openhab.binding.hue/src/main/resources/OH-INF/i18n/hue.properties index d8b6dd1b38b..3fa66d65d9a 100644 --- a/bundles/org.openhab.binding.hue/src/main/resources/OH-INF/i18n/hue.properties +++ b/bundles/org.openhab.binding.hue/src/main/resources/OH-INF/i18n/hue.properties @@ -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. diff --git a/bundles/org.openhab.binding.hue/src/main/resources/OH-INF/thing/Clip2Thing.xml b/bundles/org.openhab.binding.hue/src/main/resources/OH-INF/thing/Clip2Thing.xml index 03419d9a88a..33f5d935c56 100644 --- a/bundles/org.openhab.binding.hue/src/main/resources/OH-INF/thing/Clip2Thing.xml +++ b/bundles/org.openhab.binding.hue/src/main/resources/OH-INF/thing/Clip2Thing.xml @@ -62,6 +62,25 @@ Temperature sensor enabled. + + + Open or closed state of the contact. + + + Security contact enabled. + + + + The date and time when the contact state was last updated. + + + + Tamper or no tamper state of the sensor. + + + + The date and time when the tamper contact state was last updated. + diff --git a/bundles/org.openhab.binding.hue/src/main/resources/OH-INF/thing/channels.xml b/bundles/org.openhab.binding.hue/src/main/resources/OH-INF/thing/channels.xml index 6e51b1761a5..c1f82b5516a 100644 --- a/bundles/org.openhab.binding.hue/src/main/resources/OH-INF/thing/channels.xml +++ b/bundles/org.openhab.binding.hue/src/main/resources/OH-INF/thing/channels.xml @@ -256,4 +256,16 @@ Switch + + Contact + + Lock + + + + Contact + + Siren + + diff --git a/bundles/org.openhab.binding.hue/src/test/java/org/openhab/binding/hue/internal/clip2/Clip2DtoTest.java b/bundles/org.openhab.binding.hue/src/test/java/org/openhab/binding/hue/internal/clip2/Clip2DtoTest.java index 059a06ba045..79b32776d51 100644 --- a/bundles/org.openhab.binding.hue/src/test/java/org/openhab/binding/hue/internal/clip2/Clip2DtoTest.java +++ b/bundles/org.openhab.binding.hue/src/test/java/org/openhab/binding/hue/internal/clip2/Clip2DtoTest.java @@ -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 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 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 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 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 index 00000000000..c678d620d27 --- /dev/null +++ b/bundles/org.openhab.binding.hue/src/test/resources/camera_motion.json @@ -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 index 00000000000..6b7af5b44a0 --- /dev/null +++ b/bundles/org.openhab.binding.hue/src/test/resources/contact.json @@ -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 index 00000000000..10af6dc915f --- /dev/null +++ b/bundles/org.openhab.binding.hue/src/test/resources/tamper.json @@ -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" + } + ] +}