[hue] Support new home security products (#15601)

Signed-off-by: Andrew Fiddian-Green <software@whitebear.ch>
This commit is contained in:
Andrew Fiddian-Green
2023-11-09 08:09:20 +01:00
committed by GitHub
parent 5555d68f92
commit 4f6d33bb79
16 changed files with 473 additions and 29 deletions
@@ -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.
@@ -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,
@@ -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;
}
}
@@ -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;
@@ -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;
}
}
@@ -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
}
@@ -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,
@@ -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
}
@@ -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;
@@ -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.
@@ -62,6 +62,25 @@
<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"/>
@@ -256,4 +256,16 @@
<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>
@@ -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;
@@ -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"
}
]
}
@@ -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"
}
]
}
@@ -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"
}
]
}