[hue] Add channels for time of last sensor update (API v2) ()

* Add channels for last motion/temperature sensor update (API v2)
* Add channel for last light level sensor update (API v2)
* Add channel for last rotary steps update (API v2)
* Add channel for last button update (API v2)

Resolves 

Signed-off-by: Jacob Laursen <jacob-github@vindvejr.dk>
This commit is contained in:
Jacob Laursen 2023-10-26 23:52:00 +02:00 committed by GitHub
parent bdba338f44
commit 1702631303
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 1466 additions and 685 deletions

@ -59,30 +59,35 @@ 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) |
| rotary-steps | (String) | Informs about the number of rotary steps of the last rotary dial movement. (Trigger Channel) |
| motion | Switch | Shows if motion has been detected by the sensor. (Read Only) |
| motion-enabled | Switch | Supports enabling / disabling the motion sensor. (Advanced) |
| light-level | Number:Illuminance | Shows the current light level measured by the sensor. (Read Only) |
| 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-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. |
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.

@ -151,12 +151,17 @@ public class HueBindingConstants {
public static final String CHANNEL_2_ALERT = CHANNEL_ALERT;
public static final String CHANNEL_2_EFFECT = CHANNEL_EFFECT;
public static final String CHANNEL_2_BUTTON_LAST_EVENT = "button-last-event";
public static final String CHANNEL_2_BUTTON_LAST_UPDATED = "button-last-updated";
public static final String CHANNEL_2_ROTARY_STEPS = "rotary-steps";
public static final String CHANNEL_2_ROTARY_STEPS_LAST_UPDATED = "rotary-steps-last-updated";
public static final String CHANNEL_2_MOTION = "motion";
public static final String CHANNEL_2_MOTION_LAST_UPDATED = "motion-last-updated";
public static final String CHANNEL_2_MOTION_ENABLED = "motion-enabled";
public static final String CHANNEL_2_LIGHT_LEVEL = "light-level";
public static final String CHANNEL_2_LIGHT_LEVEL_LAST_UPDATED = "light-level-last-updated";
public static final String CHANNEL_2_LIGHT_LEVEL_ENABLED = "light-level-enabled";
public static final String CHANNEL_2_TEMPERATURE = CHANNEL_TEMPERATURE;
public static final String CHANNEL_2_TEMPERATURE_LAST_UPDATED = "temperature-last-updated";
public static final String CHANNEL_2_TEMPERATURE_ENABLED = "temperature-enabled";
public static final String CHANNEL_2_BATTERY_LEVEL = "battery-level";
public static final String CHANNEL_2_BATTERY_LOW = "battery-low";

@ -81,12 +81,14 @@ import org.openhab.binding.hue.internal.dto.clip2.enums.ResourceType;
import org.openhab.binding.hue.internal.exceptions.ApiException;
import org.openhab.binding.hue.internal.exceptions.HttpUnauthorizedException;
import org.openhab.binding.hue.internal.handler.Clip2BridgeHandler;
import org.openhab.binding.hue.internal.serialization.InstantDeserializer;
import org.openhab.core.io.net.http.HttpClientFactory;
import org.openhab.core.io.net.http.HttpUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
@ -556,7 +558,8 @@ public class Clip2Bridge implements Closeable {
private final String registrationUrl;
private final String applicationKey;
private final Clip2BridgeHandler bridgeHandler;
private final Gson jsonParser = new Gson();
private final Gson jsonParser = new GsonBuilder().registerTypeAdapter(Instant.class, new InstantDeserializer())
.create();
private final Semaphore streamMutex = new Semaphore(MAX_CONCURRENT_STREAMS, true); // i.e. fair
private final ReadWriteLock sessionUseCreateLock = new ReentrantReadWriteLock(true); // i.e. fair
private final Map<Integer, Future<?>> fatalErrorTasks = new ConcurrentHashMap<>();

@ -12,8 +12,6 @@
*/
package org.openhab.binding.hue.internal.dto.clip2;
import java.util.Objects;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.hue.internal.dto.clip2.enums.ButtonEventType;
@ -28,16 +26,29 @@ import com.google.gson.annotations.SerializedName;
@NonNullByDefault
public class Button {
private @Nullable @SerializedName("last_event") String lastEvent;
private @Nullable @SerializedName("button_report") ButtonReport buttonReport;
private @SerializedName("repeat_interval") int repeatInterval;
/**
* @return the last button event as an enum.
* @throws IllegalArgumentException if lastEvent is null or bad.
* The underlying field is deprecated in the CLIP 2 API.
* Moved to button_report/event
*
* @return the last button event as an enum (null if none or invalid).
*/
public ButtonEventType getLastEvent() throws IllegalArgumentException {
public @Nullable ButtonEventType getLastEvent() {
String lastEvent = this.lastEvent;
if (Objects.nonNull(lastEvent)) {
return ButtonEventType.valueOf(lastEvent.toUpperCase());
if (lastEvent == null) {
return null;
}
throw new IllegalArgumentException("lastEvent field is null");
try {
return ButtonEventType.valueOf(lastEvent.toUpperCase());
} catch (IllegalArgumentException e) {
return null;
}
}
public @Nullable ButtonReport getButtonReport() {
return buttonReport;
}
}

@ -0,0 +1,53 @@
/**
* 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.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.hue.internal.dto.clip2.enums.ButtonEventType;
/**
* DTO for CLIP 2 button report.
*
* @author Jacob Laursen - Initial contribution
*/
@NonNullByDefault
public class ButtonReport {
private @NonNullByDefault({}) Instant updated;
private @Nullable String event;
/**
* @return last time the value of this property is updated.
*/
public Instant getLastChanged() {
return updated;
}
/**
* @return event which can be sent by a button control (null if none or invalid).
*/
public @Nullable ButtonEventType getLastEvent() {
String event = this.event;
if (event == null) {
return null;
}
try {
return ButtonEventType.valueOf(event.toUpperCase());
} catch (IllegalArgumentException e) {
return null;
}
}
}

@ -13,6 +13,7 @@
package org.openhab.binding.hue.internal.dto.clip2;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.unit.Units;
@ -30,11 +31,22 @@ import com.google.gson.annotations.SerializedName;
public class LightLevel {
private @SerializedName("light_level") int lightLevel;
private @SerializedName("light_level_valid") boolean lightLevelValid;
private @Nullable @SerializedName("light_level_report") LightLevelReport lightLevelReport;
/**
* The underlying field is deprecated in the CLIP 2 API.
* Moved to light_level_report/light_level
* Should be used only as fallback for older firmwares.
*/
public int getLightLevel() {
return lightLevel;
}
/**
* The underlying field is deprecated in the CLIP 2 API.
* Indication whether the value presented in light_level is valid
* Should be used only as fallback for older firmwares.
*/
public boolean isLightLevelValid() {
return lightLevelValid;
}
@ -56,4 +68,8 @@ public class LightLevel {
public State isLightLevelValidState() {
return OnOffType.from(lightLevelValid);
}
public @Nullable LightLevelReport getLightLevelReport() {
return lightLevelReport;
}
}

@ -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 com.google.gson.annotations.SerializedName;
/**
* DTO for CLIP 2 light level sensor report.
*
* @author Jacob Laursen - Initial contribution
*/
@NonNullByDefault
public class LightLevelReport {
private @NonNullByDefault({}) Instant changed;
private @SerializedName("light_level") int lightLevel;
/**
* @return last time the value of this property is changed.
*/
public Instant getLastChanged() {
return changed;
}
/**
* Light level in 10000*log10(lux) +1 measured by sensor.
* Logarithmic scale used because the human eye adjusts to light levels and small changes at low lux levels
* are more noticeable than at high lux levels. This allows use of linear scale configuration sliders.
*
* @return light level in 10000*log10(lux) +1 measured by sensor
*/
public int getLightLevel() {
return lightLevel;
}
}

@ -13,6 +13,7 @@
package org.openhab.binding.hue.internal.dto.clip2;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.types.State;
import org.openhab.core.types.UnDefType;
@ -28,11 +29,24 @@ import com.google.gson.annotations.SerializedName;
public class Motion {
private boolean motion;
private @SerializedName("motion_valid") boolean motionValid;
private @Nullable @SerializedName("motion_report") MotionReport motionReport;
/**
* The underlying field is deprecated in the CLIP 2 API.
* Moved to motion_report/motion.
* Should be used only as fallback for older firmwares.
*
* @return true if motion is detected
*/
public boolean isMotion() {
return motion;
}
/**
* The underlying field is deprecated in the CLIP 2 API.
* Motion is valid when motion_report property is present, invalid when absent.
* Should be used only as fallback for older firmwares.
*/
public boolean isMotionValid() {
return motionValid;
}
@ -44,4 +58,8 @@ public class Motion {
public State getMotionValidState() {
return OnOffType.from(motionValid);
}
public @Nullable MotionReport getMotionReport() {
return motionReport;
}
}

@ -0,0 +1,42 @@
/**
* 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;
/**
* DTO for CLIP 2 motion sensor report.
*
* @author Jacob Laursen - Initial contribution
*/
@NonNullByDefault
public class MotionReport {
private @NonNullByDefault({}) Instant changed;
private boolean motion;
/**
* @return last time the value of this property is changed.
*/
public Instant getLastChanged() {
return changed;
}
/**
* @return true if motion is detected
*/
public boolean isMotion() {
return motion;
}
}

@ -29,18 +29,33 @@ import com.google.gson.annotations.SerializedName;
@NonNullByDefault
public class RelativeRotary {
private @Nullable @SerializedName("last_event") RotationEvent lastEvent;
private @Nullable @SerializedName("rotary_report") RotaryReport rotaryReport;
public State getActionState() {
RotationEvent lastEvent = getLastEvent();
return Objects.nonNull(lastEvent) ? lastEvent.getActionState() : UnDefType.NULL;
}
/**
* The underlying field is deprecated in the CLIP 2 API.
* Renamed to RelativeRotaryReport. Indicate which type of rotary event is received.
* Should be used only as fallback for older firmwares.
*/
public @Nullable RotationEvent getLastEvent() {
return lastEvent;
}
/**
* The underlying field is deprecated in the CLIP 2 API.
* Renamed to RelativeRotaryReport.
* Should be used only as fallback for older firmwares.
*/
public State getStepsState() {
RotationEvent lastEvent = getLastEvent();
return Objects.nonNull(lastEvent) ? lastEvent.getStepsState() : UnDefType.NULL;
}
public @Nullable RotaryReport getRotaryReport() {
return rotaryReport;
}
}

@ -16,6 +16,9 @@ import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode;
import java.time.Duration;
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@ -24,6 +27,7 @@ import java.util.Optional;
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.EffectType;
import org.openhab.binding.hue.internal.dto.clip2.enums.ResourceType;
import org.openhab.binding.hue.internal.dto.clip2.enums.SceneRecallAction;
@ -31,12 +35,15 @@ 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.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.PercentType;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.library.unit.SIUnits;
import org.openhab.core.library.unit.Units;
import org.openhab.core.types.Command;
import org.openhab.core.types.State;
import org.openhab.core.types.UnDefType;
@ -189,15 +196,36 @@ public class Resource {
*/
public State getButtonEventState(Map<String, Integer> controlIds) {
Button button = this.button;
if (Objects.nonNull(button)) {
try {
return new DecimalType(
(controlIds.getOrDefault(getId(), 0).intValue() * 1000) + button.getLastEvent().ordinal());
} catch (IllegalArgumentException e) {
// fall through
}
if (button == null) {
return UnDefType.NULL;
}
return UnDefType.NULL;
ButtonEventType event;
ButtonReport buttonReport = button.getButtonReport();
if (buttonReport == null) {
event = button.getLastEvent();
} else {
event = buttonReport.getLastEvent();
}
if (event == null) {
return UnDefType.NULL;
}
return new DecimalType((controlIds.getOrDefault(getId(), 0).intValue() * 1000) + event.ordinal());
}
public State getButtonLastUpdatedState(ZoneId zoneId) {
Button button = this.button;
if (button == null) {
return UnDefType.NULL;
}
ButtonReport buttonReport = button.getButtonReport();
if (buttonReport == null) {
return UnDefType.UNDEF;
}
Instant lastChanged = buttonReport.getLastChanged();
if (Instant.EPOCH.equals(lastChanged)) {
return UnDefType.UNDEF;
}
return new DateTimeType(ZonedDateTime.ofInstant(lastChanged, zoneId));
}
public List<ResourceReference> getChildren() {
@ -386,8 +414,31 @@ public class Resource {
}
public State getLightLevelState() {
LightLevel light = this.light;
return Objects.nonNull(light) ? light.getLightLevelState() : UnDefType.NULL;
LightLevel lightLevel = this.light;
if (lightLevel == null) {
return UnDefType.NULL;
}
LightLevelReport lightLevelReport = lightLevel.getLightLevelReport();
if (lightLevelReport == null) {
return lightLevel.getLightLevelState();
}
return new QuantityType<>(Math.pow(10f, (double) lightLevelReport.getLightLevel() / 10000f) - 1f, Units.LUX);
}
public State getLightLevelLastUpdatedState(ZoneId zoneId) {
LightLevel lightLevel = this.light;
if (lightLevel == null) {
return UnDefType.NULL;
}
LightLevelReport lightLevelReport = lightLevel.getLightLevelReport();
if (lightLevelReport == null) {
return UnDefType.UNDEF;
}
Instant lastChanged = lightLevelReport.getLastChanged();
if (Instant.EPOCH.equals(lastChanged)) {
return UnDefType.UNDEF;
}
return new DateTimeType(ZonedDateTime.ofInstant(lastChanged, zoneId));
}
public @Nullable MetaData getMetaData() {
@ -410,7 +461,30 @@ public class Resource {
public State getMotionState() {
Motion motion = this.motion;
return Objects.nonNull(motion) ? motion.getMotionState() : UnDefType.NULL;
if (motion == null) {
return UnDefType.NULL;
}
MotionReport motionReport = motion.getMotionReport();
if (motionReport == null) {
return motion.getMotionState();
}
return OnOffType.from(motionReport.isMotion());
}
public State getMotionLastUpdatedState(ZoneId zoneId) {
Motion motion = this.motion;
if (motion == null) {
return UnDefType.NULL;
}
MotionReport motionReport = motion.getMotionReport();
if (motionReport == null) {
return UnDefType.UNDEF;
}
Instant lastChanged = motionReport.getLastChanged();
if (Instant.EPOCH.equals(lastChanged)) {
return UnDefType.UNDEF;
}
return new DateTimeType(ZonedDateTime.ofInstant(lastChanged, zoneId));
}
public State getMotionValidState() {
@ -473,14 +547,36 @@ public class Resource {
return relativeRotary;
}
public State getRelativeRotaryActionState() {
RelativeRotary relativeRotary = this.relativeRotary;
return Objects.nonNull(relativeRotary) ? relativeRotary.getActionState() : UnDefType.NULL;
}
public State getRotaryStepsState() {
RelativeRotary relativeRotary = this.relativeRotary;
return Objects.nonNull(relativeRotary) ? relativeRotary.getStepsState() : UnDefType.NULL;
if (relativeRotary == null) {
return UnDefType.NULL;
}
RotaryReport rotaryReport = relativeRotary.getRotaryReport();
if (rotaryReport == null) {
return relativeRotary.getStepsState();
}
Rotation rotation = rotaryReport.getRotation();
if (rotation == null) {
return UnDefType.NULL;
}
return rotation.getStepsState();
}
public State getRotaryStepsLastUpdatedState(ZoneId zoneId) {
RelativeRotary relativeRotary = this.relativeRotary;
if (relativeRotary == null) {
return UnDefType.NULL;
}
RotaryReport rotaryReport = relativeRotary.getRotaryReport();
if (rotaryReport == null) {
return UnDefType.UNDEF;
}
Instant lastChanged = rotaryReport.getLastChanged();
if (Instant.EPOCH.equals(lastChanged)) {
return UnDefType.UNDEF;
}
return new DateTimeType(ZonedDateTime.ofInstant(lastChanged, zoneId));
}
/**
@ -559,7 +655,30 @@ public class Resource {
public State getTemperatureState() {
Temperature temperature = this.temperature;
return Objects.nonNull(temperature) ? temperature.getTemperatureState() : UnDefType.NULL;
if (temperature == null) {
return UnDefType.NULL;
}
TemperatureReport temperatureReport = temperature.getTemperatureReport();
if (temperatureReport == null) {
return temperature.getTemperatureState();
}
return new QuantityType<>(temperatureReport.getTemperature(), SIUnits.CELSIUS);
}
public State getTemperatureLastUpdatedState(ZoneId zoneId) {
Temperature temperature = this.temperature;
if (temperature == null) {
return UnDefType.NULL;
}
TemperatureReport temperatureReport = temperature.getTemperatureReport();
if (temperatureReport == null) {
return UnDefType.UNDEF;
}
Instant lastChanged = temperatureReport.getLastChanged();
if (Instant.EPOCH.equals(lastChanged)) {
return UnDefType.UNDEF;
}
return new DateTimeType(ZonedDateTime.ofInstant(lastChanged, zoneId));
}
public State getTemperatureValidState() {

@ -0,0 +1,50 @@
/**
* 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.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.hue.internal.dto.clip2.enums.RotationEventType;
/**
* DTO for CLIP 2 relative rotary report.
*
* @author Jacob Laursen - Initial contribution
*/
@NonNullByDefault
public class RotaryReport {
private @NonNullByDefault({}) Instant updated;
private @Nullable String action;
private @Nullable Rotation rotation;
/**
* @return last time the value of this property is changed.
*/
public Instant getLastChanged() {
return updated;
}
/**
* @return which type of rotary event is received
*/
public @Nullable RotationEventType getAction() {
String action = this.action;
return action == null ? null : RotationEventType.valueOf(action.toUpperCase());
}
public @Nullable Rotation getRotation() {
return rotation;
}
}

@ -13,6 +13,7 @@
package org.openhab.binding.hue.internal.dto.clip2;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.unit.SIUnits;
@ -30,11 +31,24 @@ import com.google.gson.annotations.SerializedName;
public class Temperature {
private float temperature;
private @SerializedName("temperature_valid") boolean temperatureValid;
private @Nullable @SerializedName("temperature_report") TemperatureReport temperatureReport;
/**
* The underlying field is deprecated in the CLIP 2 API.
* Moved to temperature_report/temperature.
* Should be used only as fallback for older firmwares.
*
* @return temperature in 1.00 degrees Celsius
*/
public float getTemperature() {
return temperature;
}
/**
* The underlying field is deprecated in the CLIP 2 API.
* Indication whether the value presented in temperature is valid
* Should be used only as fallback for older firmwares.
*/
public boolean isTemperatureValid() {
return temperatureValid;
}
@ -46,4 +60,8 @@ public class Temperature {
public State getTemperatureValidState() {
return OnOffType.from(temperatureValid);
}
public @Nullable TemperatureReport getTemperatureReport() {
return temperatureReport;
}
}

@ -0,0 +1,42 @@
/**
* 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;
/**
* DTO for CLIP 2 temperature sensor report.
*
* @author Jacob Laursen - Initial contribution
*/
@NonNullByDefault
public class TemperatureReport {
private @NonNullByDefault({}) Instant changed;
private float temperature;
/**
* @return last time the value of this property is changed.
*/
public Instant getLastChanged() {
return changed;
}
/**
* @return temperature in 1.00 degrees Celsius
*/
public float getTemperature() {
return temperature;
}
}

@ -37,6 +37,7 @@ import org.openhab.binding.hue.internal.handler.sensors.TapSwitchHandler;
import org.openhab.binding.hue.internal.handler.sensors.TemperatureHandler;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.i18n.LocaleProvider;
import org.openhab.core.i18n.TimeZoneProvider;
import org.openhab.core.i18n.TranslationProvider;
import org.openhab.core.io.net.http.HttpClientFactory;
import org.openhab.core.thing.Bridge;
@ -82,6 +83,7 @@ public class HueThingHandlerFactory extends BaseThingHandlerFactory {
private final Clip2StateDescriptionProvider clip2StateDescriptionProvider;
private final TranslationProvider i18nProvider;
private final LocaleProvider localeProvider;
private final TimeZoneProvider timeZoneProvider;
private final ThingRegistry thingRegistry;
private final ItemChannelLinkRegistry itemChannelLinkRegistry;
@ -90,13 +92,14 @@ public class HueThingHandlerFactory extends BaseThingHandlerFactory {
final @Reference HueStateDescriptionProvider stateDescriptionProvider,
final @Reference Clip2StateDescriptionProvider clip2StateDescriptionProvider,
final @Reference TranslationProvider i18nProvider, final @Reference LocaleProvider localeProvider,
final @Reference ThingRegistry thingRegistry,
final @Reference TimeZoneProvider timeZoneProvider, final @Reference ThingRegistry thingRegistry,
final @Reference ItemChannelLinkRegistry itemChannelLinkRegistry) {
this.httpClientFactory = httpClientFactory;
this.stateDescriptionProvider = stateDescriptionProvider;
this.clip2StateDescriptionProvider = clip2StateDescriptionProvider;
this.i18nProvider = i18nProvider;
this.localeProvider = localeProvider;
this.timeZoneProvider = timeZoneProvider;
this.thingRegistry = thingRegistry;
this.itemChannelLinkRegistry = itemChannelLinkRegistry;
}
@ -184,7 +187,8 @@ public class HueThingHandlerFactory extends BaseThingHandlerFactory {
return new Clip2BridgeHandler((Bridge) thing, httpClientFactory, thingRegistry, localeProvider,
i18nProvider);
} else if (Clip2ThingHandler.SUPPORTED_THING_TYPES.contains(thingTypeUID)) {
return new Clip2ThingHandler(thing, clip2StateDescriptionProvider, thingRegistry, itemChannelLinkRegistry);
return new Clip2ThingHandler(thing, clip2StateDescriptionProvider, timeZoneProvider, thingRegistry,
itemChannelLinkRegistry);
} else if (HueBridgeHandler.SUPPORTED_THING_TYPES.contains(thingTypeUID)) {
return new HueBridgeHandler((Bridge) thing, httpClientFactory.getCommonHttpClient(),
stateDescriptionProvider, i18nProvider, localeProvider);

@ -57,6 +57,7 @@ import org.openhab.binding.hue.internal.dto.clip2.enums.ZigbeeStatus;
import org.openhab.binding.hue.internal.dto.clip2.helper.Setters;
import org.openhab.binding.hue.internal.exceptions.ApiException;
import org.openhab.binding.hue.internal.exceptions.AssetNotLoadedException;
import org.openhab.core.i18n.TimeZoneProvider;
import org.openhab.core.library.types.DateTimeType;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.HSBType;
@ -160,11 +161,13 @@ public class Clip2ThingHandler extends BaseThingHandler {
private final ThingRegistry thingRegistry;
private final ItemChannelLinkRegistry itemChannelLinkRegistry;
private final Clip2StateDescriptionProvider stateDescriptionProvider;
private final TimeZoneProvider timeZoneProvider;
private String resourceId = "?";
private Resource thisResource;
private Duration dynamicsDuration = Duration.ZERO;
private Instant dynamicsExpireTime = Instant.MIN;
private Instant buttonGroupLastUpdated = Instant.MIN;
private boolean disposing;
private boolean hasConnectivityIssue;
@ -180,7 +183,8 @@ public class Clip2ThingHandler extends BaseThingHandler {
private @Nullable Future<?> updateServiceContributorsTask;
public Clip2ThingHandler(Thing thing, Clip2StateDescriptionProvider stateDescriptionProvider,
ThingRegistry thingRegistry, ItemChannelLinkRegistry itemChannelLinkRegistry) {
TimeZoneProvider timeZoneProvider, ThingRegistry thingRegistry,
ItemChannelLinkRegistry itemChannelLinkRegistry) {
super(thing);
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
@ -197,6 +201,7 @@ public class Clip2ThingHandler extends BaseThingHandler {
this.thingRegistry = thingRegistry;
this.itemChannelLinkRegistry = itemChannelLinkRegistry;
this.stateDescriptionProvider = stateDescriptionProvider;
this.timeZoneProvider = timeZoneProvider;
}
/**
@ -829,11 +834,23 @@ public class Clip2ThingHandler extends BaseThingHandler {
case BUTTON:
if (fullUpdate) {
addSupportedChannel(CHANNEL_2_BUTTON_LAST_EVENT);
addSupportedChannel(CHANNEL_2_BUTTON_LAST_UPDATED);
controlIds.put(resource.getId(), resource.getControlId());
} else {
State buttonState = resource.getButtonEventState(controlIds);
updateState(CHANNEL_2_BUTTON_LAST_EVENT, buttonState, fullUpdate);
}
// Update channel from timestamp if last button pressed.
State buttonLastUpdatedState = resource.getButtonLastUpdatedState(timeZoneProvider.getTimeZone());
if (buttonLastUpdatedState instanceof DateTimeType) {
Instant buttonLastUpdatedInstant = ((DateTimeType) buttonLastUpdatedState).getInstant();
if (buttonLastUpdatedInstant.isAfter(buttonGroupLastUpdated)) {
updateState(CHANNEL_2_BUTTON_LAST_UPDATED, buttonLastUpdatedState, fullUpdate);
buttonGroupLastUpdated = buttonLastUpdatedInstant;
}
} else if (Instant.MIN.equals(buttonGroupLastUpdated)) {
updateState(CHANNEL_2_BUTTON_LAST_UPDATED, buttonLastUpdatedState, fullUpdate);
}
break;
case DEVICE_POWER:
@ -865,24 +882,33 @@ public class Clip2ThingHandler extends BaseThingHandler {
case LIGHT_LEVEL:
updateState(CHANNEL_2_LIGHT_LEVEL, resource.getLightLevelState(), fullUpdate);
updateState(CHANNEL_2_LIGHT_LEVEL_LAST_UPDATED,
resource.getLightLevelLastUpdatedState(timeZoneProvider.getTimeZone()), fullUpdate);
updateState(CHANNEL_2_LIGHT_LEVEL_ENABLED, resource.getEnabledState(), fullUpdate);
break;
case MOTION:
updateState(CHANNEL_2_MOTION, resource.getMotionState(), fullUpdate);
updateState(CHANNEL_2_MOTION_LAST_UPDATED,
resource.getMotionLastUpdatedState(timeZoneProvider.getTimeZone()), fullUpdate);
updateState(CHANNEL_2_MOTION_ENABLED, resource.getEnabledState(), fullUpdate);
break;
case RELATIVE_ROTARY:
if (fullUpdate) {
addSupportedChannel(CHANNEL_2_ROTARY_STEPS);
addSupportedChannel(CHANNEL_2_ROTARY_STEPS_LAST_UPDATED);
} else {
updateState(CHANNEL_2_ROTARY_STEPS, resource.getRotaryStepsState(), fullUpdate);
}
updateState(CHANNEL_2_ROTARY_STEPS_LAST_UPDATED,
resource.getRotaryStepsLastUpdatedState(timeZoneProvider.getTimeZone()), fullUpdate);
break;
case TEMPERATURE:
updateState(CHANNEL_2_TEMPERATURE, resource.getTemperatureState(), fullUpdate);
updateState(CHANNEL_2_TEMPERATURE_LAST_UPDATED,
resource.getTemperatureLastUpdatedState(timeZoneProvider.getTimeZone()), fullUpdate);
updateState(CHANNEL_2_TEMPERATURE_ENABLED, resource.getEnabledState(), fullUpdate);
break;

@ -0,0 +1,45 @@
/**
* 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.serialization;
import java.lang.reflect.Type;
import java.time.Instant;
import java.time.format.DateTimeParseException;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
/**
* The {@link InstantDeserializer} converts a formatted UTC string to {@link Instant}.
*
* @author Jacob Laursen - Initial contribution
*/
@NonNullByDefault
public class InstantDeserializer implements JsonDeserializer<Instant> {
@Override
public @Nullable Instant deserialize(JsonElement element, Type arg1, JsonDeserializationContext arg2)
throws JsonParseException {
String content = element.getAsString();
try {
return Instant.parse(content);
} catch (DateTimeParseException e) {
throw new JsonParseException("Could not parse as Instant: " + content, e);
}
}
}

@ -40,16 +40,26 @@ thing-type.hue.bridge.description = The Hue Bridge represents the Philips Hue Br
thing-type.hue.device.label = Hue Device
thing-type.hue.device.description = A Hue API v2 device with channels depending on its actual capabilities.
thing-type.hue.device.channel.alert.description = Activate the alert for the light.
thing-type.hue.device.channel.button-last-updated.label = Button Last Updated
thing-type.hue.device.channel.button-last-updated.description = The date and time when a button was last pressed.
thing-type.hue.device.channel.color-xy-only.description = Set the color xy parameter of the light without changing other state parameters.
thing-type.hue.device.channel.dimming-only.description = Set the dimming parameter of the light without changing other state parameters.
thing-type.hue.device.channel.effect.description = Activate the effect for the light.
thing-type.hue.device.channel.light-level.description = Current light level.
thing-type.hue.device.channel.light-level-enabled.description = Light level sensor enabled.
thing-type.hue.device.channel.light-level-last-updated.label = Light Level Last Updated
thing-type.hue.device.channel.light-level-last-updated.description = The date and time when the light level was last updated.
thing-type.hue.device.channel.motion-enabled.description = Motion sensor enabled.
thing-type.hue.device.channel.motion-last-updated.label = Motion Last Updated
thing-type.hue.device.channel.motion-last-updated.description = The date and time when the motion value was last updated.
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.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.
thing-type.hue.device.channel.temperature-last-updated.label = Temperature Last Updated
thing-type.hue.device.channel.temperature-last-updated.description = The date and time when the temperature was last updated.
thing-type.hue.geofencesensor.label = Geofence Sensor
thing-type.hue.geofencesensor.description = A sensor providing geofence based presence detection.
thing-type.hue.group.label = Hue Group

@ -24,14 +24,30 @@
<description>Activate the effect for the light.</description>
</channel>
<channel id="button-last-event" typeId="button-last-event"/>
<channel id="button-last-updated" typeId="last-updated-v2">
<label>Button Last Updated</label>
<description>The date and time when a button was last pressed.</description>
</channel>
<channel id="rotary-steps" typeId="rotary-steps"/>
<channel id="rotary-steps-last-updated" typeId="last-updated-v2">
<label>Rotary Steps Last Updated</label>
<description>The date and time when the rotary steps were last updated.</description>
</channel>
<channel id="motion" typeId="system.motion"/>
<channel id="motion-last-updated" typeId="last-updated-v2">
<label>Motion Last Updated</label>
<description>The date and time when the motion value was last updated.</description>
</channel>
<channel id="motion-enabled" typeId="sensor-enabled">
<description>Motion sensor enabled.</description>
</channel>
<channel id="light-level" typeId="illuminance">
<description>Current light level.</description>
</channel>
<channel id="light-level-last-updated" typeId="last-updated-v2">
<label>Light Level Last Updated</label>
<description>The date and time when the light level was last updated.</description>
</channel>
<channel id="light-level-enabled" typeId="sensor-enabled">
<description>Light level sensor enabled.</description>
</channel>
@ -39,6 +55,10 @@
<label>Temperature</label>
<description>Temperature at the sensor location.</description>
</channel>
<channel id="temperature-last-updated" typeId="last-updated-v2">
<label>Temperature Last Updated</label>
<description>The date and time when the temperature was last updated.</description>
</channel>
<channel id="temperature-enabled" typeId="sensor-enabled">
<description>Temperature sensor enabled.</description>
</channel>
@ -58,6 +78,10 @@
</channel>
</channels>
<properties>
<property name="thingTypeVersion">1</property>
</properties>
<representation-property>resourceId</representation-property>
<config-description>

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<update:update-descriptions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:update="https://openhab.org/schemas/update-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/update-description/v1.0.0 https://openhab.org/schemas/update-description-1.0.0.xsd">
<thing-type uid="hue:device">
<instruction-set targetVersion="1">
<add-channel id="motion-last-updated">
<type>hue:last-updated-v2</type>
<label>Motion Last Updated</label>
<description>The date and time when the motion value was last updated.</description>
</add-channel>
<add-channel id="temperature-last-updated">
<type>hue:last-updated-v2</type>
<label>Temperature Last Updated</label>
<description>The date and time when the temperature was last updated.</description>
</add-channel>
<add-channel id="light-level-last-updated">
<type>hue:last-updated-v2</type>
<label>Light Level Last Updated</label>
<description>The date and time when the light level was last updated.</description>
</add-channel>
<add-channel id="button-last-updated">
<type>hue:last-updated-v2</type>
<label>Button Last Updated</label>
<description>The date and time when a button was last pressed.</description>
</add-channel>
<add-channel id="rotary-steps-last-updated">
<type>hue:last-updated-v2</type>
<label>Rotary Steps Last Updated</label>
<description>The date and time when the rotary steps were last updated.</description>
</add-channel>
</instruction-set>
</thing-type>
</update:update-descriptions>

@ -19,7 +19,10 @@ import java.io.FileReader;
import java.io.IOException;
import java.lang.reflect.Field;
import java.time.Duration;
import java.time.Instant;
import java.time.ZoneId;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
@ -55,6 +58,8 @@ import org.openhab.binding.hue.internal.dto.clip2.enums.ResourceType;
import org.openhab.binding.hue.internal.dto.clip2.enums.RotationEventType;
import org.openhab.binding.hue.internal.dto.clip2.enums.ZigbeeStatus;
import org.openhab.binding.hue.internal.dto.clip2.helper.Setters;
import org.openhab.binding.hue.internal.serialization.InstantDeserializer;
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;
@ -66,6 +71,7 @@ import org.openhab.core.types.UnDefType;
import org.openhab.core.util.ColorUtil;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
@ -79,7 +85,8 @@ import com.google.gson.JsonSyntaxException;
@NonNullByDefault
class Clip2DtoTest {
private static final Gson GSON = new Gson();
private static final Gson GSON = new GsonBuilder().registerTypeAdapter(Instant.class, new InstantDeserializer())
.create();
private static final Double MINIMUM_DIMMING_LEVEL = Double.valueOf(12.34f);
/**
@ -107,6 +114,24 @@ class Clip2DtoTest {
assertNotNull(resources);
List<Resource> list = resources.getResources();
assertNotNull(list);
assertEquals(1, list.size());
Resource item = list.get(0);
assertEquals(ResourceType.BUTTON, item.getType());
Button button = item.getButton();
assertNotNull(button);
assertEquals(new DecimalType(2003),
item.getButtonEventState(Map.of("00000000-0000-0000-0000-000000000001", 2)));
assertEquals(new DateTimeType("2023-09-17T18:51:36.959+0000"),
item.getButtonLastUpdatedState(ZoneId.of("UTC")));
}
@Test
void testButtonDeprecated() {
String json = load(ResourceType.BUTTON.name().toLowerCase() + "_deprecated");
Resources resources = GSON.fromJson(json, Resources.class);
assertNotNull(resources);
List<Resource> list = resources.getResources();
assertNotNull(list);
assertEquals(43, list.size());
Resource item = list.get(0);
assertEquals(ResourceType.BUTTON, item.getType());
@ -317,6 +342,24 @@ class Clip2DtoTest {
Boolean enabled = item.getEnabled();
assertNotNull(enabled);
assertTrue(enabled);
assertEquals(QuantityType.valueOf("1.2792921774337476 lx"), item.getLightLevelState());
assertEquals(new DateTimeType("2023-09-11T19:20:02.958+0000"),
item.getLightLevelLastUpdatedState(ZoneId.of("UTC")));
}
@Test
void testLightLevelDeprecated() {
String json = load(ResourceType.LIGHT_LEVEL.name().toLowerCase() + "_deprecated");
Resources resources = GSON.fromJson(json, Resources.class);
assertNotNull(resources);
List<Resource> list = resources.getResources();
assertNotNull(list);
assertEquals(1, list.size());
Resource item = list.get(0);
assertEquals(ResourceType.LIGHT_LEVEL, item.getType());
Boolean enabled = item.getEnabled();
assertNotNull(enabled);
assertTrue(enabled);
LightLevel lightLevel = item.getLightLevel();
assertNotNull(lightLevel);
assertEquals(12725, lightLevel.getLightLevel());
@ -324,8 +367,8 @@ class Clip2DtoTest {
}
@Test
void testRelativeRotary() {
String json = load(ResourceType.RELATIVE_ROTARY.name().toLowerCase());
void testRelativeRotaryDeprecated() {
String json = load(ResourceType.RELATIVE_ROTARY.name().toLowerCase() + "_deprecated");
Resources resources = GSON.fromJson(json, Resources.class);
assertNotNull(resources);
List<Resource> list = resources.getResources();
@ -493,6 +536,24 @@ class Clip2DtoTest {
Boolean enabled = item.getEnabled();
assertNotNull(enabled);
assertTrue(enabled);
assertEquals(OnOffType.ON, item.getMotionState());
assertEquals(new DateTimeType("2023-09-04T20:04:30.395+0000"),
item.getMotionLastUpdatedState(ZoneId.of("UTC")));
}
@Test
void testSensor2MotionDeprecated() {
String json = load(ResourceType.MOTION.name().toLowerCase() + "_deprecated");
Resources resources = GSON.fromJson(json, Resources.class);
assertNotNull(resources);
List<Resource> list = resources.getResources();
assertNotNull(list);
assertEquals(1, list.size());
Resource item = list.get(0);
assertEquals(ResourceType.MOTION, item.getType());
Boolean enabled = item.getEnabled();
assertNotNull(enabled);
assertTrue(enabled);
Motion motion = item.getMotion();
assertNotNull(motion);
assertTrue(motion.isMotion());
@ -563,6 +624,27 @@ class Clip2DtoTest {
assertEquals(1, list.size());
Resource item = list.get(0);
assertEquals(ResourceType.TEMPERATURE, item.getType());
Boolean enabled = item.getEnabled();
assertNotNull(enabled);
assertTrue(enabled);
assertEquals(QuantityType.valueOf("23.34 °C"), item.getTemperatureState());
assertEquals(new DateTimeType("2023-09-06T18:22:07.016+0000"),
item.getTemperatureLastUpdatedState(ZoneId.of("UTC")));
}
@Test
void testTemperatureDeprecated() {
String json = load(ResourceType.TEMPERATURE.name().toLowerCase() + "_deprecated");
Resources resources = GSON.fromJson(json, Resources.class);
assertNotNull(resources);
List<Resource> list = resources.getResources();
assertNotNull(list);
assertEquals(1, list.size());
Resource item = list.get(0);
assertEquals(ResourceType.TEMPERATURE, item.getType());
Boolean enabled = item.getEnabled();
assertNotNull(enabled);
assertTrue(enabled);
Temperature temperature = item.getTemperature();
assertNotNull(temperature);
assertEquals(17.2, temperature.getTemperature(), 0.1);

@ -1,551 +1,32 @@
{
"errors": [],
"data": [
{
"id": "d1ae958e-8908-449a-9897-7f10f9b8d4c2",
"id_v1": "/sensors/110",
"owner": {
"rid": "112853f9-c4c4-4d65-ba96-b4c2ab26d94d",
"rtype": "device"
},
"metadata": {
"control_id": 1
},
"button": {
"last_event": "short_release"
},
"type": "button"
},
{
"id": "a83354b7-bae5-4618-8c8d-079c83e0ca2b",
"id_v1": "/sensors/236",
"owner": {
"rid": "cfecbbd0-e918-42a2-b714-2bad33061d95",
"rtype": "device"
},
"metadata": {
"control_id": 1
},
"type": "button"
},
{
"id": "e20d90ae-b2d8-40ea-9905-66bcde9fd863",
"id_v1": "/sensors/63",
"owner": {
"rid": "0e22f8de-eff5-440a-a9ed-06d547d125d7",
"rtype": "device"
},
"metadata": {
"control_id": 1
},
"button": {
"last_event": "short_release"
},
"type": "button"
},
{
"id": "4e61a948-22aa-4332-9b97-76245dd9fb87",
"id_v1": "/sensors/6",
"owner": {
"rid": "b56191ea-8d18-4257-b5d2-cfc3fc86a1ee",
"rtype": "device"
},
"metadata": {
"control_id": 1
},
"type": "button"
},
{
"id": "2f78855c-717a-4b77-8513-dd68ca5337b8",
"id_v1": "/sensors/6",
"owner": {
"rid": "b56191ea-8d18-4257-b5d2-cfc3fc86a1ee",
"rtype": "device"
},
"metadata": {
"control_id": 2
},
"type": "button"
},
{
"id": "7c495cbd-4dd1-4f22-950c-4dd6017b81ec",
"id_v1": "/sensors/6",
"owner": {
"rid": "b56191ea-8d18-4257-b5d2-cfc3fc86a1ee",
"rtype": "device"
},
"metadata": {
"control_id": 3
},
"type": "button"
},
{
"id": "a15c9be9-14b9-4d98-8d9f-989d6cb54496",
"id_v1": "/sensors/6",
"owner": {
"rid": "b56191ea-8d18-4257-b5d2-cfc3fc86a1ee",
"rtype": "device"
},
"metadata": {
"control_id": 4
},
"button": {
"last_event": "short_release"
},
"type": "button"
},
{
"id": "e1e6b2f9-3ace-41ae-b16c-e68f82b5a949",
"id_v1": "/sensors/24",
"owner": {
"rid": "b5fe0539-171c-4733-bf0b-244635a309be",
"rtype": "device"
},
"metadata": {
"control_id": 1
},
"type": "button"
},
{
"id": "717c978e-383d-4900-ab2a-2685983f6174",
"id_v1": "/sensors/24",
"owner": {
"rid": "b5fe0539-171c-4733-bf0b-244635a309be",
"rtype": "device"
},
"metadata": {
"control_id": 2
},
"type": "button"
},
{
"id": "0456ba22-2f1d-4375-8e33-31465420cd96",
"id_v1": "/sensors/24",
"owner": {
"rid": "b5fe0539-171c-4733-bf0b-244635a309be",
"rtype": "device"
},
"metadata": {
"control_id": 3
},
"type": "button"
},
{
"id": "92c00fa4-ae65-462c-9130-a28f5bc52bd9",
"id_v1": "/sensors/24",
"owner": {
"rid": "b5fe0539-171c-4733-bf0b-244635a309be",
"rtype": "device"
},
"metadata": {
"control_id": 4
},
"type": "button"
},
{
"id": "85c18ad0-297d-4fb9-a33a-37f2da70752d",
"id_v1": "/sensors/118",
"owner": {
"rid": "a0509519-3ecb-47d0-9183-25db1e4ea2b2",
"rtype": "device"
},
"metadata": {
"control_id": 1
},
"button": {
"last_event": "short_release"
},
"type": "button"
},
{
"id": "79af0ff1-a4ed-476d-a381-eabd4d3ee6bd",
"id_v1": "/sensors/135",
"owner": {
"rid": "8c5b05ba-b4f4-47b2-8ba0-fc44363192bc",
"rtype": "device"
},
"metadata": {
"control_id": 1
},
"button": {
"last_event": "short_release"
},
"type": "button"
},
{
"id": "8a16c730-094a-4aa1-abd8-9cb758468d62",
"id_v1": "/sensors/8",
"owner": {
"rid": "68ff07d0-6543-4967-9889-e0bc0bc16c31",
"rtype": "device"
},
"metadata": {
"control_id": 1
},
"type": "button"
},
{
"id": "3fed0654-68ec-43e1-b3db-f1a3eba076b9",
"id_v1": "/sensors/8",
"owner": {
"rid": "68ff07d0-6543-4967-9889-e0bc0bc16c31",
"rtype": "device"
},
"metadata": {
"control_id": 2
},
"type": "button"
},
{
"id": "df6e2ba8-94f4-43ce-9bec-72c252616062",
"id_v1": "/sensors/8",
"owner": {
"rid": "68ff07d0-6543-4967-9889-e0bc0bc16c31",
"rtype": "device"
},
"metadata": {
"control_id": 3
},
"type": "button"
},
{
"id": "a46f615d-6760-479c-ba6d-2837a18902e8",
"id_v1": "/sensors/8",
"owner": {
"rid": "68ff07d0-6543-4967-9889-e0bc0bc16c31",
"rtype": "device"
},
"metadata": {
"control_id": 4
},
"type": "button"
},
{
"id": "c34bb678-b910-4a06-8019-3e0ce922f17f",
"id_v1": "/sensors/18",
"owner": {
"rid": "96bec26d-d7c6-4c00-98cd-6f96733296a0",
"rtype": "device"
},
"metadata": {
"control_id": 1
},
"type": "button"
},
{
"id": "286b1b18-ff43-40be-a2ff-b1edc24ecb7c",
"id_v1": "/sensors/18",
"owner": {
"rid": "96bec26d-d7c6-4c00-98cd-6f96733296a0",
"rtype": "device"
},
"metadata": {
"control_id": 2
},
"type": "button"
},
{
"id": "2be1abd3-00a4-4985-b2c9-9bdd4cbe1e4b",
"id_v1": "/sensors/18",
"owner": {
"rid": "96bec26d-d7c6-4c00-98cd-6f96733296a0",
"rtype": "device"
},
"metadata": {
"control_id": 3
},
"type": "button"
},
{
"id": "dc9f1653-11a8-4794-b706-464cf53e9722",
"id_v1": "/sensors/18",
"owner": {
"rid": "96bec26d-d7c6-4c00-98cd-6f96733296a0",
"rtype": "device"
},
"metadata": {
"control_id": 4
},
"type": "button"
},
{
"id": "ac4ebfeb-2045-4698-96de-0b6b5421a1d8",
"id_v1": "/sensors/4",
"owner": {
"rid": "81d9a9d5-228c-45df-828e-0d224929b3d1",
"rtype": "device"
},
"metadata": {
"control_id": 1
},
"type": "button"
},
{
"id": "8a5159f1-93de-4c07-9d32-1fe555fde285",
"id_v1": "/sensors/4",
"owner": {
"rid": "81d9a9d5-228c-45df-828e-0d224929b3d1",
"rtype": "device"
},
"metadata": {
"control_id": 2
},
"type": "button"
},
{
"id": "14debc09-496e-4429-8c22-4975c135bbdf",
"id_v1": "/sensors/4",
"owner": {
"rid": "81d9a9d5-228c-45df-828e-0d224929b3d1",
"rtype": "device"
},
"metadata": {
"control_id": 3
},
"type": "button"
},
{
"id": "9672e2a9-a3a9-4aec-b31e-f2338114b22b",
"id_v1": "/sensors/4",
"owner": {
"rid": "81d9a9d5-228c-45df-828e-0d224929b3d1",
"rtype": "device"
},
"metadata": {
"control_id": 4
},
"type": "button"
},
{
"id": "f385a946-c638-4631-b6a6-c490cf809606",
"id_v1": "/sensors/245",
"owner": {
"rid": "56b560bc-a127-4634-8d80-9946104a4028",
"rtype": "device"
},
"metadata": {
"control_id": 1
},
"button": {
"last_event": "short_release"
},
"type": "button"
},
{
"id": "ba204102-de9f-479e-818a-02ed880caac2",
"id_v1": "/sensors/12",
"owner": {
"rid": "a1155885-4bbe-469f-83bb-f964f8e13e82",
"rtype": "device"
},
"metadata": {
"control_id": 1
},
"type": "button"
},
{
"id": "0032a38b-cf67-478f-934a-520a156a2baf",
"id_v1": "/sensors/12",
"owner": {
"rid": "a1155885-4bbe-469f-83bb-f964f8e13e82",
"rtype": "device"
},
"metadata": {
"control_id": 2
},
"type": "button"
},
{
"id": "4ce58cbd-5d0f-45ec-920d-ad601fd9cc07",
"id_v1": "/sensors/12",
"owner": {
"rid": "a1155885-4bbe-469f-83bb-f964f8e13e82",
"rtype": "device"
},
"metadata": {
"control_id": 3
},
"type": "button"
},
{
"id": "813a331a-f26a-4ab2-948c-9b4c398939d0",
"id_v1": "/sensors/12",
"owner": {
"rid": "a1155885-4bbe-469f-83bb-f964f8e13e82",
"rtype": "device"
},
"metadata": {
"control_id": 4
},
"button": {
"last_event": "short_release"
},
"type": "button"
},
{
"id": "91ba8839-2bac-4175-9f8c-ed192842d549",
"id_v1": "/sensors/20",
"owner": {
"rid": "e130feac-3a5c-452e-a97d-5bca470783b3",
"rtype": "device"
},
"metadata": {
"control_id": 1
},
"type": "button"
},
{
"id": "f95addfc-2f7c-453f-924d-ba496e07e5f9",
"id_v1": "/sensors/20",
"owner": {
"rid": "e130feac-3a5c-452e-a97d-5bca470783b3",
"rtype": "device"
},
"metadata": {
"control_id": 2
},
"type": "button"
},
{
"id": "6615f1f1-f3f1-4a05-b8f7-581097458e34",
"id_v1": "/sensors/20",
"owner": {
"rid": "e130feac-3a5c-452e-a97d-5bca470783b3",
"rtype": "device"
},
"metadata": {
"control_id": 3
},
"type": "button"
},
{
"id": "b0d5a0af-31fd-4189-9150-c551ff9033d7",
"id_v1": "/sensors/20",
"owner": {
"rid": "e130feac-3a5c-452e-a97d-5bca470783b3",
"rtype": "device"
},
"metadata": {
"control_id": 4
},
"button": {
"last_event": "short_release"
},
"type": "button"
},
{
"id": "9d3820db-4a20-4925-80f6-c74582ffb871",
"id_v1": "/sensors/26",
"owner": {
"rid": "e12b3d9c-0b23-4edd-a967-66f5b5cefa92",
"rtype": "device"
},
"metadata": {
"control_id": 1
},
"type": "button"
},
{
"id": "a9e1a40b-a13d-4966-b0b1-2b0b5dfe1986",
"id_v1": "/sensors/26",
"owner": {
"rid": "e12b3d9c-0b23-4edd-a967-66f5b5cefa92",
"rtype": "device"
},
"metadata": {
"control_id": 2
},
"type": "button"
},
{
"id": "8ba063d7-df16-48f5-bb9b-f45849ec1bd3",
"id_v1": "/sensors/26",
"owner": {
"rid": "e12b3d9c-0b23-4edd-a967-66f5b5cefa92",
"rtype": "device"
},
"metadata": {
"control_id": 3
},
"type": "button"
},
{
"id": "4fc9dcd2-6f71-46c4-b3d0-5d7b496be6f9",
"id_v1": "/sensors/26",
"owner": {
"rid": "e12b3d9c-0b23-4edd-a967-66f5b5cefa92",
"rtype": "device"
},
"metadata": {
"control_id": 4
},
"type": "button"
},
{
"id": "824ea347-28d1-4e45-b886-1555a160190b",
"id_v1": "/sensors/14",
"owner": {
"rid": "431026fb-298c-4726-8ce4-47450fea13c4",
"rtype": "device"
},
"metadata": {
"control_id": 1
},
"button": {
"last_event": "short_release"
},
"type": "button"
},
{
"id": "801ede68-21b0-4e6e-98be-eb1a60557ac6",
"id_v1": "/sensors/14",
"owner": {
"rid": "431026fb-298c-4726-8ce4-47450fea13c4",
"rtype": "device"
},
"metadata": {
"control_id": 2
},
"type": "button"
},
{
"id": "1218d0c3-9a9c-4984-84a5-c12ad085ef2d",
"id_v1": "/sensors/14",
"owner": {
"rid": "431026fb-298c-4726-8ce4-47450fea13c4",
"rtype": "device"
},
"metadata": {
"control_id": 3
},
"type": "button"
},
{
"id": "eb2c07bc-1d09-4096-a044-dec29b40619a",
"id_v1": "/sensors/14",
"owner": {
"rid": "431026fb-298c-4726-8ce4-47450fea13c4",
"rtype": "device"
},
"metadata": {
"control_id": 4
},
"type": "button"
},
{
"id": "6bae5b99-349c-4045-8c8f-d4a60562b1d3",
"id_v1": "/sensors/124",
"owner": {
"rid": "f78c5b4b-2f52-4bc3-8097-1ddf97949cc5",
"rtype": "device"
},
"metadata": {
"control_id": 1
},
"button": {
"last_event": "short_release"
},
"type": "button"
}
]
}
"errors": [],
"data": [
{
"id": "00000000-0000-0000-0000-000000000001",
"id_v1": "/sensors/76",
"owner": {
"rid": "00000000-0000-0000-0000-000000000000",
"rtype": "device"
},
"metadata": {
"control_id": 2
},
"button": {
"last_event": "long_release",
"button_report": {
"updated": "2023-09-17T18:51:36.959Z",
"event": "long_release"
},
"repeat_interval": 800,
"event_values": [
"initial_press",
"repeat",
"short_release",
"long_release",
"long_press"
]
},
"type": "button"
}
]
}

@ -0,0 +1,551 @@
{
"errors": [],
"data": [
{
"id": "d1ae958e-8908-449a-9897-7f10f9b8d4c2",
"id_v1": "/sensors/110",
"owner": {
"rid": "112853f9-c4c4-4d65-ba96-b4c2ab26d94d",
"rtype": "device"
},
"metadata": {
"control_id": 1
},
"button": {
"last_event": "short_release"
},
"type": "button"
},
{
"id": "a83354b7-bae5-4618-8c8d-079c83e0ca2b",
"id_v1": "/sensors/236",
"owner": {
"rid": "cfecbbd0-e918-42a2-b714-2bad33061d95",
"rtype": "device"
},
"metadata": {
"control_id": 1
},
"type": "button"
},
{
"id": "e20d90ae-b2d8-40ea-9905-66bcde9fd863",
"id_v1": "/sensors/63",
"owner": {
"rid": "0e22f8de-eff5-440a-a9ed-06d547d125d7",
"rtype": "device"
},
"metadata": {
"control_id": 1
},
"button": {
"last_event": "short_release"
},
"type": "button"
},
{
"id": "4e61a948-22aa-4332-9b97-76245dd9fb87",
"id_v1": "/sensors/6",
"owner": {
"rid": "b56191ea-8d18-4257-b5d2-cfc3fc86a1ee",
"rtype": "device"
},
"metadata": {
"control_id": 1
},
"type": "button"
},
{
"id": "2f78855c-717a-4b77-8513-dd68ca5337b8",
"id_v1": "/sensors/6",
"owner": {
"rid": "b56191ea-8d18-4257-b5d2-cfc3fc86a1ee",
"rtype": "device"
},
"metadata": {
"control_id": 2
},
"type": "button"
},
{
"id": "7c495cbd-4dd1-4f22-950c-4dd6017b81ec",
"id_v1": "/sensors/6",
"owner": {
"rid": "b56191ea-8d18-4257-b5d2-cfc3fc86a1ee",
"rtype": "device"
},
"metadata": {
"control_id": 3
},
"type": "button"
},
{
"id": "a15c9be9-14b9-4d98-8d9f-989d6cb54496",
"id_v1": "/sensors/6",
"owner": {
"rid": "b56191ea-8d18-4257-b5d2-cfc3fc86a1ee",
"rtype": "device"
},
"metadata": {
"control_id": 4
},
"button": {
"last_event": "short_release"
},
"type": "button"
},
{
"id": "e1e6b2f9-3ace-41ae-b16c-e68f82b5a949",
"id_v1": "/sensors/24",
"owner": {
"rid": "b5fe0539-171c-4733-bf0b-244635a309be",
"rtype": "device"
},
"metadata": {
"control_id": 1
},
"type": "button"
},
{
"id": "717c978e-383d-4900-ab2a-2685983f6174",
"id_v1": "/sensors/24",
"owner": {
"rid": "b5fe0539-171c-4733-bf0b-244635a309be",
"rtype": "device"
},
"metadata": {
"control_id": 2
},
"type": "button"
},
{
"id": "0456ba22-2f1d-4375-8e33-31465420cd96",
"id_v1": "/sensors/24",
"owner": {
"rid": "b5fe0539-171c-4733-bf0b-244635a309be",
"rtype": "device"
},
"metadata": {
"control_id": 3
},
"type": "button"
},
{
"id": "92c00fa4-ae65-462c-9130-a28f5bc52bd9",
"id_v1": "/sensors/24",
"owner": {
"rid": "b5fe0539-171c-4733-bf0b-244635a309be",
"rtype": "device"
},
"metadata": {
"control_id": 4
},
"type": "button"
},
{
"id": "85c18ad0-297d-4fb9-a33a-37f2da70752d",
"id_v1": "/sensors/118",
"owner": {
"rid": "a0509519-3ecb-47d0-9183-25db1e4ea2b2",
"rtype": "device"
},
"metadata": {
"control_id": 1
},
"button": {
"last_event": "short_release"
},
"type": "button"
},
{
"id": "79af0ff1-a4ed-476d-a381-eabd4d3ee6bd",
"id_v1": "/sensors/135",
"owner": {
"rid": "8c5b05ba-b4f4-47b2-8ba0-fc44363192bc",
"rtype": "device"
},
"metadata": {
"control_id": 1
},
"button": {
"last_event": "short_release"
},
"type": "button"
},
{
"id": "8a16c730-094a-4aa1-abd8-9cb758468d62",
"id_v1": "/sensors/8",
"owner": {
"rid": "68ff07d0-6543-4967-9889-e0bc0bc16c31",
"rtype": "device"
},
"metadata": {
"control_id": 1
},
"type": "button"
},
{
"id": "3fed0654-68ec-43e1-b3db-f1a3eba076b9",
"id_v1": "/sensors/8",
"owner": {
"rid": "68ff07d0-6543-4967-9889-e0bc0bc16c31",
"rtype": "device"
},
"metadata": {
"control_id": 2
},
"type": "button"
},
{
"id": "df6e2ba8-94f4-43ce-9bec-72c252616062",
"id_v1": "/sensors/8",
"owner": {
"rid": "68ff07d0-6543-4967-9889-e0bc0bc16c31",
"rtype": "device"
},
"metadata": {
"control_id": 3
},
"type": "button"
},
{
"id": "a46f615d-6760-479c-ba6d-2837a18902e8",
"id_v1": "/sensors/8",
"owner": {
"rid": "68ff07d0-6543-4967-9889-e0bc0bc16c31",
"rtype": "device"
},
"metadata": {
"control_id": 4
},
"type": "button"
},
{
"id": "c34bb678-b910-4a06-8019-3e0ce922f17f",
"id_v1": "/sensors/18",
"owner": {
"rid": "96bec26d-d7c6-4c00-98cd-6f96733296a0",
"rtype": "device"
},
"metadata": {
"control_id": 1
},
"type": "button"
},
{
"id": "286b1b18-ff43-40be-a2ff-b1edc24ecb7c",
"id_v1": "/sensors/18",
"owner": {
"rid": "96bec26d-d7c6-4c00-98cd-6f96733296a0",
"rtype": "device"
},
"metadata": {
"control_id": 2
},
"type": "button"
},
{
"id": "2be1abd3-00a4-4985-b2c9-9bdd4cbe1e4b",
"id_v1": "/sensors/18",
"owner": {
"rid": "96bec26d-d7c6-4c00-98cd-6f96733296a0",
"rtype": "device"
},
"metadata": {
"control_id": 3
},
"type": "button"
},
{
"id": "dc9f1653-11a8-4794-b706-464cf53e9722",
"id_v1": "/sensors/18",
"owner": {
"rid": "96bec26d-d7c6-4c00-98cd-6f96733296a0",
"rtype": "device"
},
"metadata": {
"control_id": 4
},
"type": "button"
},
{
"id": "ac4ebfeb-2045-4698-96de-0b6b5421a1d8",
"id_v1": "/sensors/4",
"owner": {
"rid": "81d9a9d5-228c-45df-828e-0d224929b3d1",
"rtype": "device"
},
"metadata": {
"control_id": 1
},
"type": "button"
},
{
"id": "8a5159f1-93de-4c07-9d32-1fe555fde285",
"id_v1": "/sensors/4",
"owner": {
"rid": "81d9a9d5-228c-45df-828e-0d224929b3d1",
"rtype": "device"
},
"metadata": {
"control_id": 2
},
"type": "button"
},
{
"id": "14debc09-496e-4429-8c22-4975c135bbdf",
"id_v1": "/sensors/4",
"owner": {
"rid": "81d9a9d5-228c-45df-828e-0d224929b3d1",
"rtype": "device"
},
"metadata": {
"control_id": 3
},
"type": "button"
},
{
"id": "9672e2a9-a3a9-4aec-b31e-f2338114b22b",
"id_v1": "/sensors/4",
"owner": {
"rid": "81d9a9d5-228c-45df-828e-0d224929b3d1",
"rtype": "device"
},
"metadata": {
"control_id": 4
},
"type": "button"
},
{
"id": "f385a946-c638-4631-b6a6-c490cf809606",
"id_v1": "/sensors/245",
"owner": {
"rid": "56b560bc-a127-4634-8d80-9946104a4028",
"rtype": "device"
},
"metadata": {
"control_id": 1
},
"button": {
"last_event": "short_release"
},
"type": "button"
},
{
"id": "ba204102-de9f-479e-818a-02ed880caac2",
"id_v1": "/sensors/12",
"owner": {
"rid": "a1155885-4bbe-469f-83bb-f964f8e13e82",
"rtype": "device"
},
"metadata": {
"control_id": 1
},
"type": "button"
},
{
"id": "0032a38b-cf67-478f-934a-520a156a2baf",
"id_v1": "/sensors/12",
"owner": {
"rid": "a1155885-4bbe-469f-83bb-f964f8e13e82",
"rtype": "device"
},
"metadata": {
"control_id": 2
},
"type": "button"
},
{
"id": "4ce58cbd-5d0f-45ec-920d-ad601fd9cc07",
"id_v1": "/sensors/12",
"owner": {
"rid": "a1155885-4bbe-469f-83bb-f964f8e13e82",
"rtype": "device"
},
"metadata": {
"control_id": 3
},
"type": "button"
},
{
"id": "813a331a-f26a-4ab2-948c-9b4c398939d0",
"id_v1": "/sensors/12",
"owner": {
"rid": "a1155885-4bbe-469f-83bb-f964f8e13e82",
"rtype": "device"
},
"metadata": {
"control_id": 4
},
"button": {
"last_event": "short_release"
},
"type": "button"
},
{
"id": "91ba8839-2bac-4175-9f8c-ed192842d549",
"id_v1": "/sensors/20",
"owner": {
"rid": "e130feac-3a5c-452e-a97d-5bca470783b3",
"rtype": "device"
},
"metadata": {
"control_id": 1
},
"type": "button"
},
{
"id": "f95addfc-2f7c-453f-924d-ba496e07e5f9",
"id_v1": "/sensors/20",
"owner": {
"rid": "e130feac-3a5c-452e-a97d-5bca470783b3",
"rtype": "device"
},
"metadata": {
"control_id": 2
},
"type": "button"
},
{
"id": "6615f1f1-f3f1-4a05-b8f7-581097458e34",
"id_v1": "/sensors/20",
"owner": {
"rid": "e130feac-3a5c-452e-a97d-5bca470783b3",
"rtype": "device"
},
"metadata": {
"control_id": 3
},
"type": "button"
},
{
"id": "b0d5a0af-31fd-4189-9150-c551ff9033d7",
"id_v1": "/sensors/20",
"owner": {
"rid": "e130feac-3a5c-452e-a97d-5bca470783b3",
"rtype": "device"
},
"metadata": {
"control_id": 4
},
"button": {
"last_event": "short_release"
},
"type": "button"
},
{
"id": "9d3820db-4a20-4925-80f6-c74582ffb871",
"id_v1": "/sensors/26",
"owner": {
"rid": "e12b3d9c-0b23-4edd-a967-66f5b5cefa92",
"rtype": "device"
},
"metadata": {
"control_id": 1
},
"type": "button"
},
{
"id": "a9e1a40b-a13d-4966-b0b1-2b0b5dfe1986",
"id_v1": "/sensors/26",
"owner": {
"rid": "e12b3d9c-0b23-4edd-a967-66f5b5cefa92",
"rtype": "device"
},
"metadata": {
"control_id": 2
},
"type": "button"
},
{
"id": "8ba063d7-df16-48f5-bb9b-f45849ec1bd3",
"id_v1": "/sensors/26",
"owner": {
"rid": "e12b3d9c-0b23-4edd-a967-66f5b5cefa92",
"rtype": "device"
},
"metadata": {
"control_id": 3
},
"type": "button"
},
{
"id": "4fc9dcd2-6f71-46c4-b3d0-5d7b496be6f9",
"id_v1": "/sensors/26",
"owner": {
"rid": "e12b3d9c-0b23-4edd-a967-66f5b5cefa92",
"rtype": "device"
},
"metadata": {
"control_id": 4
},
"type": "button"
},
{
"id": "824ea347-28d1-4e45-b886-1555a160190b",
"id_v1": "/sensors/14",
"owner": {
"rid": "431026fb-298c-4726-8ce4-47450fea13c4",
"rtype": "device"
},
"metadata": {
"control_id": 1
},
"button": {
"last_event": "short_release"
},
"type": "button"
},
{
"id": "801ede68-21b0-4e6e-98be-eb1a60557ac6",
"id_v1": "/sensors/14",
"owner": {
"rid": "431026fb-298c-4726-8ce4-47450fea13c4",
"rtype": "device"
},
"metadata": {
"control_id": 2
},
"type": "button"
},
{
"id": "1218d0c3-9a9c-4984-84a5-c12ad085ef2d",
"id_v1": "/sensors/14",
"owner": {
"rid": "431026fb-298c-4726-8ce4-47450fea13c4",
"rtype": "device"
},
"metadata": {
"control_id": 3
},
"type": "button"
},
{
"id": "eb2c07bc-1d09-4096-a044-dec29b40619a",
"id_v1": "/sensors/14",
"owner": {
"rid": "431026fb-298c-4726-8ce4-47450fea13c4",
"rtype": "device"
},
"metadata": {
"control_id": 4
},
"type": "button"
},
{
"id": "6bae5b99-349c-4045-8c8f-d4a60562b1d3",
"id_v1": "/sensors/124",
"owner": {
"rid": "f78c5b4b-2f52-4bc3-8097-1ddf97949cc5",
"rtype": "device"
},
"metadata": {
"control_id": 1
},
"button": {
"last_event": "short_release"
},
"type": "button"
}
]
}

@ -1,19 +1,23 @@
{
"errors": [],
"data": [
{
"id": "18597697-61dc-4090-9bc3-c7f5704f020e",
"id_v1": "/sensors/32",
"owner": {
"rid": "70660557-692d-4d37-8b6b-e3ec63716a72",
"rtype": "device"
},
"enabled": true,
"light": {
"light_level": 12725,
"light_level_valid": true
},
"type": "light_level"
}
]
}
"errors": [],
"data": [
{
"id": "00000000-0000-0000-0000-000000000002",
"id_v1": "/sensors/4",
"owner": {
"rid": "00000000-0000-0000-0000-000000000000",
"rtype": "device"
},
"enabled": true,
"light": {
"light_level": 2578,
"light_level_valid": true,
"light_level_report": {
"changed": "2023-09-11T19:20:02.958Z",
"light_level": 3578
}
},
"type": "light_level"
}
]
}

@ -0,0 +1,19 @@
{
"errors": [],
"data": [
{
"id": "18597697-61dc-4090-9bc3-c7f5704f020e",
"id_v1": "/sensors/32",
"owner": {
"rid": "70660557-692d-4d37-8b6b-e3ec63716a72",
"rtype": "device"
},
"enabled": true,
"light": {
"light_level": 12725,
"light_level_valid": true
},
"type": "light_level"
}
]
}

@ -1,19 +1,28 @@
{
"errors": [],
"data": [
{
"id": "97244487-dd25-4f4c-b829-8eb5ffb82c29",
"id_v1": "/sensors/30",
"owner": {
"rid": "70660557-692d-4d37-8b6b-e3ec63716a72",
"rtype": "device"
},
"enabled": true,
"motion": {
"motion": true,
"motion_valid": true
},
"type": "motion"
}
]
}
"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": "2023-09-04T20:04:30.395Z",
"motion": true
}
},
"sensitivity": {
"status": "set",
"sensitivity": 2,
"sensitivity_max": 4
},
"type": "motion"
}
]
}

@ -0,0 +1,19 @@
{
"errors": [],
"data": [
{
"id": "97244487-dd25-4f4c-b829-8eb5ffb82c29",
"id_v1": "/sensors/30",
"owner": {
"rid": "70660557-692d-4d37-8b6b-e3ec63716a72",
"rtype": "device"
},
"enabled": true,
"motion": {
"motion": true,
"motion_valid": true
},
"type": "motion"
}
]
}

@ -1,24 +1 @@
{
"errors": [],
"data": [
{
"id": "12345678-7bd4d062-a33f-40e3-a7c0-91f64c39ac1a",
"id_v1": "/sensors/2",
"owner": {
"rid": "497051e4-29c3-419b-babd-d916cffcf3a1",
"rtype": "device"
},
"relative_rotary": {
"last_event": {
"action": "repeat",
"rotation": {
"direction": "clock_wise",
"duration": 400,
"steps": 30
}
}
},
"type": "relative_rotary"
}
]
}
{}

@ -0,0 +1,24 @@
{
"errors": [],
"data": [
{
"id": "12345678-7bd4d062-a33f-40e3-a7c0-91f64c39ac1a",
"id_v1": "/sensors/2",
"owner": {
"rid": "497051e4-29c3-419b-babd-d916cffcf3a1",
"rtype": "device"
},
"relative_rotary": {
"last_event": {
"action": "repeat",
"rotation": {
"direction": "clock_wise",
"duration": 400,
"steps": 30
}
}
},
"type": "relative_rotary"
}
]
}

@ -1,19 +1,23 @@
{
"errors": [],
"data": [
{
"id": "f5f8010d-8356-4604-88dc-4f73912cbb6a",
"id_v1": "/sensors/33",
"owner": {
"rid": "70660557-692d-4d37-8b6b-e3ec63716a72",
"rtype": "device"
},
"enabled": true,
"temperature": {
"temperature": 17.26,
"temperature_valid": true
},
"type": "temperature"
}
]
}
"errors": [],
"data": [
{
"id": "00000000-0000-0000-0000-000000000004",
"id_v1": "/sensors/5",
"owner": {
"rid": "00000000-0000-0000-0000-000000000000",
"rtype": "device"
},
"enabled": true,
"temperature": {
"temperature": 23.35,
"temperature_valid": true,
"temperature_report": {
"changed": "2023-09-06T18:22:07.016Z",
"temperature": 23.34
}
},
"type": "temperature"
}
]
}

@ -0,0 +1,19 @@
{
"errors": [],
"data": [
{
"id": "f5f8010d-8356-4604-88dc-4f73912cbb6a",
"id_v1": "/sensors/33",
"owner": {
"rid": "70660557-692d-4d37-8b6b-e3ec63716a72",
"rtype": "device"
},
"enabled": true,
"temperature": {
"temperature": 17.26,
"temperature_valid": true
},
"type": "temperature"
}
]
}