2 * Copyright (c) 2010-2023 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.hue.internal.api.dto.clip2;
15 import java.math.BigDecimal;
16 import java.math.MathContext;
17 import java.math.RoundingMode;
18 import java.time.Duration;
19 import java.time.Instant;
20 import java.time.ZoneId;
21 import java.time.ZonedDateTime;
22 import java.util.List;
24 import java.util.Objects;
25 import java.util.Optional;
27 import org.eclipse.jdt.annotation.NonNullByDefault;
28 import org.eclipse.jdt.annotation.Nullable;
29 import org.openhab.binding.hue.internal.api.dto.clip2.enums.ActionType;
30 import org.openhab.binding.hue.internal.api.dto.clip2.enums.ButtonEventType;
31 import org.openhab.binding.hue.internal.api.dto.clip2.enums.ContactStateType;
32 import org.openhab.binding.hue.internal.api.dto.clip2.enums.EffectType;
33 import org.openhab.binding.hue.internal.api.dto.clip2.enums.ResourceType;
34 import org.openhab.binding.hue.internal.api.dto.clip2.enums.SceneRecallAction;
35 import org.openhab.binding.hue.internal.api.dto.clip2.enums.SmartSceneRecallAction;
36 import org.openhab.binding.hue.internal.api.dto.clip2.enums.SmartSceneState;
37 import org.openhab.binding.hue.internal.api.dto.clip2.enums.TamperStateType;
38 import org.openhab.binding.hue.internal.api.dto.clip2.enums.ZigbeeStatus;
39 import org.openhab.binding.hue.internal.exceptions.DTOPresentButEmptyException;
40 import org.openhab.core.library.types.DateTimeType;
41 import org.openhab.core.library.types.DecimalType;
42 import org.openhab.core.library.types.HSBType;
43 import org.openhab.core.library.types.OnOffType;
44 import org.openhab.core.library.types.OpenClosedType;
45 import org.openhab.core.library.types.PercentType;
46 import org.openhab.core.library.types.QuantityType;
47 import org.openhab.core.library.types.StringType;
48 import org.openhab.core.library.unit.SIUnits;
49 import org.openhab.core.library.unit.Units;
50 import org.openhab.core.types.Command;
51 import org.openhab.core.types.State;
52 import org.openhab.core.types.UnDefType;
53 import org.openhab.core.util.ColorUtil;
54 import org.openhab.core.util.ColorUtil.Gamut;
56 import com.google.gson.JsonElement;
57 import com.google.gson.JsonObject;
58 import com.google.gson.annotations.SerializedName;
61 * Complete Resource information DTO for CLIP 2.
63 * Note: all fields are @Nullable because some cases do not (must not) use them.
65 * @author Andrew Fiddian-Green - Initial contribution
68 public class Resource {
70 public static final double PERCENT_DELTA = 30f;
71 public static final MathContext PERCENT_MATH_CONTEXT = new MathContext(4, RoundingMode.HALF_UP);
74 * The SSE event mechanism sends resources in a sparse (skeleton) format that only includes state fields whose
75 * values have changed. A sparse resource does not contain the full state of the resource. And the absence of any
76 * field from such a resource does not indicate that the field value is UNDEF, but rather that the value is the same
77 * as what it was previously set to by the last non-sparse resource.
79 private transient boolean hasSparseData;
81 private @Nullable String type;
82 private @Nullable String id;
83 private @Nullable @SerializedName("bridge_id") String bridgeId;
84 private @Nullable @SerializedName("id_v1") String idV1;
85 private @Nullable ResourceReference owner;
86 private @Nullable MetaData metadata;
87 private @Nullable @SerializedName("product_data") ProductData productData;
88 private @Nullable List<ResourceReference> services;
89 private @Nullable OnState on;
90 private @Nullable Dimming dimming;
91 private @Nullable @SerializedName("color_temperature") ColorTemperature colorTemperature;
92 private @Nullable ColorXy color;
93 private @Nullable Alerts alert;
94 private @Nullable Effects effects;
95 private @Nullable @SerializedName("timed_effects") TimedEffects timedEffects;
96 private @Nullable ResourceReference group;
97 private @Nullable List<ActionEntry> actions;
98 private @Nullable Recall recall;
99 private @Nullable Boolean enabled;
100 private @Nullable LightLevel light;
101 private @Nullable Button button;
102 private @Nullable Temperature temperature;
103 private @Nullable Motion motion;
104 private @Nullable @SerializedName("power_state") Power powerState;
105 private @Nullable @SerializedName("relative_rotary") RelativeRotary relativeRotary;
106 private @Nullable List<ResourceReference> children;
107 private @Nullable JsonElement status;
108 private @Nullable @SuppressWarnings("unused") Dynamics dynamics;
109 private @Nullable @SerializedName("contact_report") ContactReport contactReport;
110 private @Nullable @SerializedName("tamper_reports") List<TamperReport> tamperReports;
111 private @Nullable String state;
116 * @param resourceType
118 public Resource(@Nullable ResourceType resourceType) {
119 if (Objects.nonNull(resourceType)) {
120 setType(resourceType);
124 public @Nullable List<ActionEntry> getActions() {
128 public @Nullable Alerts getAlerts() {
132 public State getAlertState() {
133 Alerts alerts = this.alert;
134 if (Objects.nonNull(alerts)) {
135 if (!alerts.getActionValues().isEmpty()) {
136 ActionType alertType = alerts.getAction();
137 if (Objects.nonNull(alertType)) {
138 return new StringType(alertType.name());
140 return new StringType(ActionType.NO_ACTION.name());
143 return UnDefType.NULL;
146 public String getArchetype() {
147 MetaData metaData = getMetaData();
148 if (Objects.nonNull(metaData)) {
149 return metaData.getArchetype().toString();
151 return getType().toString();
154 public State getBatteryLevelState() {
155 Power powerState = this.powerState;
156 return Objects.nonNull(powerState) ? powerState.getBatteryLevelState() : UnDefType.NULL;
159 public State getBatteryLowState() {
160 Power powerState = this.powerState;
161 return Objects.nonNull(powerState) ? powerState.getBatteryLowState() : UnDefType.NULL;
164 public @Nullable String getBridgeId() {
165 String bridgeId = this.bridgeId;
166 return Objects.isNull(bridgeId) || bridgeId.isBlank() ? null : bridgeId;
170 * Get the brightness as a PercentType. If off the brightness is 0, otherwise use dimming value.
172 * @return a PercentType with the dimming state, or UNDEF, or NULL
174 public State getBrightnessState() {
175 Dimming dimming = this.dimming;
176 if (Objects.nonNull(dimming)) {
178 // if off the brightness is 0, otherwise it is dimming value
179 OnState on = this.on;
180 double brightness = Objects.nonNull(on) && !on.isOn() ? 0f
181 : Math.max(0f, Math.min(100f, dimming.getBrightness()));
182 return new PercentType(new BigDecimal(brightness, PERCENT_MATH_CONTEXT));
183 } catch (DTOPresentButEmptyException e) {
184 return UnDefType.UNDEF; // indicates the DTO is present but its inner fields are missing
187 return UnDefType.NULL;
190 public @Nullable Button getButton() {
195 * Get the state corresponding to a button's last event value multiplied by the controlId found for it in the given
196 * controlIds map. States are decimal values formatted like '1002' where the first digit is the button's controlId
197 * and the last digit is the ordinal value of the button's last event.
199 * @param controlIds the map of control ids to be referenced.
202 public State getButtonEventState(Map<String, Integer> controlIds) {
203 Button button = this.button;
204 if (button == null) {
205 return UnDefType.NULL;
207 ButtonEventType event;
208 ButtonReport buttonReport = button.getButtonReport();
209 if (buttonReport == null) {
210 event = button.getLastEvent();
212 event = buttonReport.getLastEvent();
215 return UnDefType.NULL;
217 return new DecimalType((controlIds.getOrDefault(getId(), 0).intValue() * 1000) + event.ordinal());
220 public State getButtonLastUpdatedState(ZoneId zoneId) {
221 Button button = this.button;
222 if (button == null) {
223 return UnDefType.NULL;
225 ButtonReport buttonReport = button.getButtonReport();
226 if (buttonReport == null) {
227 return UnDefType.UNDEF;
229 Instant lastChanged = buttonReport.getLastChanged();
230 if (Instant.EPOCH.equals(lastChanged)) {
231 return UnDefType.UNDEF;
233 return new DateTimeType(ZonedDateTime.ofInstant(lastChanged, zoneId));
236 public List<ResourceReference> getChildren() {
237 List<ResourceReference> children = this.children;
238 return Objects.nonNull(children) ? children : List.of();
242 * Get the color as an HSBType. This returns an HSB that is based on an amalgamation of the color xy, dimming, and
243 * on/off JSON elements. It takes its 'H' and 'S' parts from the 'ColorXy' JSON element, and its 'B' part from the
244 * on/off resp. dimming JSON elements. If off the B part is 0, otherwise it is the dimming element value. Note: this
245 * method is only to be used on cached state DTOs which already have a defined color gamut.
247 * @return an HSBType containing the current color and brightness level, or UNDEF or NULL.
249 public State getColorState() {
250 ColorXy color = this.color;
251 if (Objects.nonNull(color)) {
253 Gamut gamut = color.getGamut();
254 gamut = Objects.nonNull(gamut) ? gamut : ColorUtil.DEFAULT_GAMUT;
255 HSBType hsb = ColorUtil.xyToHsb(color.getXY(), gamut);
256 OnState on = this.on;
257 Dimming dimming = this.dimming;
258 double brightness = Objects.nonNull(on) && !on.isOn() ? 0
259 : Objects.nonNull(dimming) ? Math.max(0, Math.min(100, dimming.getBrightness())) : 50;
260 return new HSBType(hsb.getHue(), hsb.getSaturation(),
261 new PercentType(new BigDecimal(brightness, PERCENT_MATH_CONTEXT)));
262 } catch (DTOPresentButEmptyException e) {
263 return UnDefType.UNDEF; // indicates the DTO is present but its inner fields are missing
266 return UnDefType.NULL;
269 public @Nullable ColorTemperature getColorTemperature() {
270 return colorTemperature;
273 public State getColorTemperatureAbsoluteState() {
274 ColorTemperature colorTemp = colorTemperature;
275 if (Objects.nonNull(colorTemp)) {
277 QuantityType<?> colorTemperature = colorTemp.getAbsolute();
278 if (Objects.nonNull(colorTemperature)) {
279 return colorTemperature;
281 } catch (DTOPresentButEmptyException e) {
282 return UnDefType.UNDEF; // indicates the DTO is present but its inner fields are missing
285 return UnDefType.NULL;
289 * Get the colour temperature in percent. Note: this method is only to be used on cached state DTOs which already
290 * have a defined mirek schema.
292 * @return a PercentType with the colour temperature percentage.
294 public State getColorTemperaturePercentState() {
295 ColorTemperature colorTemperature = this.colorTemperature;
296 if (Objects.nonNull(colorTemperature)) {
298 Double percent = colorTemperature.getPercent();
299 if (Objects.nonNull(percent)) {
300 return new PercentType(new BigDecimal(percent, PERCENT_MATH_CONTEXT));
302 } catch (DTOPresentButEmptyException e) {
303 return UnDefType.UNDEF; // indicates the DTO is present but its inner fields are missing
306 return UnDefType.NULL;
309 public @Nullable ColorXy getColorXy() {
314 * Return an HSB where the HS part is derived from the color xy JSON element (only), so the B part is 100%
316 * @return an HSBType.
318 public State getColorXyState() {
319 ColorXy color = this.color;
320 if (Objects.nonNull(color)) {
322 Gamut gamut = color.getGamut();
323 gamut = Objects.nonNull(gamut) ? gamut : ColorUtil.DEFAULT_GAMUT;
324 HSBType hsb = ColorUtil.xyToHsb(color.getXY(), gamut);
325 return new HSBType(hsb.getHue(), hsb.getSaturation(), PercentType.HUNDRED);
326 } catch (DTOPresentButEmptyException e) {
327 return UnDefType.UNDEF; // indicates the DTO is present but its inner fields are missing
330 return UnDefType.NULL;
333 public State getContactLastUpdatedState(ZoneId zoneId) {
334 ContactReport contactReport = this.contactReport;
335 return Objects.nonNull(contactReport)
336 ? new DateTimeType(ZonedDateTime.ofInstant(contactReport.getLastChanged(), zoneId))
340 public State getContactState() {
341 ContactReport contactReport = this.contactReport;
342 return Objects.isNull(contactReport) ? UnDefType.NULL
343 : ContactStateType.CONTACT == contactReport.getContactState() ? OpenClosedType.CLOSED
344 : OpenClosedType.OPEN;
347 public int getControlId() {
348 MetaData metadata = this.metadata;
349 return Objects.nonNull(metadata) ? metadata.getControlId() : 0;
352 public @Nullable Dimming getDimming() {
357 * Return a PercentType which is derived from the dimming JSON element (only).
359 * @return a PercentType.
361 public State getDimmingState() {
362 Dimming dimming = this.dimming;
363 if (Objects.nonNull(dimming)) {
365 double dimmingValue = Math.max(0f, Math.min(100f, dimming.getBrightness()));
366 return new PercentType(new BigDecimal(dimmingValue, PERCENT_MATH_CONTEXT));
367 } catch (DTOPresentButEmptyException e) {
368 return UnDefType.UNDEF; // indicates the DTO is present but its inner fields are missing
371 return UnDefType.NULL;
374 public @Nullable Effects getFixedEffects() {
379 * Get the amalgamated effect state. The result may be either from an 'effects' field or from a 'timedEffects'
380 * field. If both fields are missing it returns UnDefType.NULL, otherwise if either field is present and has an
381 * active value (other than EffectType.NO_EFFECT) it returns a StringType of the name of the respective active
382 * effect; and if none of the above apply, it returns a StringType of 'NO_EFFECT'.
384 * @return either a StringType value or UnDefType.NULL
386 public State getEffectState() {
387 Effects effects = this.effects;
388 TimedEffects timedEffects = this.timedEffects;
389 if (Objects.isNull(effects) && Objects.isNull(timedEffects)) {
390 return UnDefType.NULL;
392 EffectType effect = Objects.nonNull(effects) ? effects.getStatus() : null;
393 if (Objects.nonNull(effect) && effect != EffectType.NO_EFFECT) {
394 return new StringType(effect.name());
396 EffectType timedEffect = Objects.nonNull(timedEffects) ? timedEffects.getStatus() : null;
397 if (Objects.nonNull(timedEffect) && timedEffect != EffectType.NO_EFFECT) {
398 return new StringType(timedEffect.name());
400 return new StringType(EffectType.NO_EFFECT.name());
403 public @Nullable Boolean getEnabled() {
407 public State getEnabledState() {
408 Boolean enabled = this.enabled;
409 return Objects.nonNull(enabled) ? OnOffType.from(enabled.booleanValue()) : UnDefType.NULL;
412 public @Nullable Gamut getGamut() {
413 ColorXy color = this.color;
414 return Objects.nonNull(color) ? color.getGamut() : null;
417 public @Nullable ResourceReference getGroup() {
421 public String getId() {
423 return Objects.nonNull(id) ? id : "";
426 public String getIdV1() {
427 String idV1 = this.idV1;
428 return Objects.nonNull(idV1) ? idV1 : "";
431 public @Nullable LightLevel getLightLevel() {
435 public State getLightLevelState() {
436 LightLevel lightLevel = this.light;
437 if (lightLevel == null) {
438 return UnDefType.NULL;
440 LightLevelReport lightLevelReport = lightLevel.getLightLevelReport();
441 if (lightLevelReport == null) {
442 return lightLevel.getLightLevelState();
444 return new QuantityType<>(Math.pow(10f, (double) lightLevelReport.getLightLevel() / 10000f) - 1f, Units.LUX);
447 public State getLightLevelLastUpdatedState(ZoneId zoneId) {
448 LightLevel lightLevel = this.light;
449 if (lightLevel == null) {
450 return UnDefType.NULL;
452 LightLevelReport lightLevelReport = lightLevel.getLightLevelReport();
453 if (lightLevelReport == null) {
454 return UnDefType.UNDEF;
456 Instant lastChanged = lightLevelReport.getLastChanged();
457 if (Instant.EPOCH.equals(lastChanged)) {
458 return UnDefType.UNDEF;
460 return new DateTimeType(ZonedDateTime.ofInstant(lastChanged, zoneId));
463 public @Nullable MetaData getMetaData() {
467 public @Nullable Double getMinimumDimmingLevel() {
468 Dimming dimming = this.dimming;
469 return Objects.nonNull(dimming) ? dimming.getMinimumDimmingLevel() : null;
472 public @Nullable MirekSchema getMirekSchema() {
473 ColorTemperature colorTemp = this.colorTemperature;
474 return Objects.nonNull(colorTemp) ? colorTemp.getMirekSchema() : null;
477 public @Nullable Motion getMotion() {
481 public State getMotionState() {
482 Motion motion = this.motion;
483 if (motion == null) {
484 return UnDefType.NULL;
486 MotionReport motionReport = motion.getMotionReport();
487 if (motionReport == null) {
488 return motion.getMotionState();
490 return OnOffType.from(motionReport.isMotion());
493 public State getMotionLastUpdatedState(ZoneId zoneId) {
494 Motion motion = this.motion;
495 if (motion == null) {
496 return UnDefType.NULL;
498 MotionReport motionReport = motion.getMotionReport();
499 if (motionReport == null) {
500 return UnDefType.UNDEF;
502 Instant lastChanged = motionReport.getLastChanged();
503 if (Instant.EPOCH.equals(lastChanged)) {
504 return UnDefType.UNDEF;
506 return new DateTimeType(ZonedDateTime.ofInstant(lastChanged, zoneId));
509 public State getMotionValidState() {
510 Motion motion = this.motion;
511 return Objects.nonNull(motion) ? motion.getMotionValidState() : UnDefType.NULL;
514 public String getName() {
515 MetaData metaData = getMetaData();
516 if (Objects.nonNull(metaData)) {
517 String name = metaData.getName();
518 if (Objects.nonNull(name)) {
522 return getType().toString();
526 * Return the state of the On/Off element (only).
528 public State getOnOffState() {
530 OnState on = this.on;
531 return Objects.nonNull(on) ? OnOffType.from(on.isOn()) : UnDefType.NULL;
532 } catch (DTOPresentButEmptyException e) {
533 return UnDefType.UNDEF; // indicates the DTO is present but its inner fields are missing
537 public @Nullable OnState getOnState() {
541 public @Nullable ResourceReference getOwner() {
545 public @Nullable Power getPowerState() {
549 public @Nullable ProductData getProductData() {
553 public String getProductName() {
554 ProductData productData = getProductData();
555 if (Objects.nonNull(productData)) {
556 return productData.getProductName();
558 return getType().toString();
561 public @Nullable Recall getRecall() {
565 public @Nullable RelativeRotary getRelativeRotary() {
566 return relativeRotary;
569 public State getRotaryStepsState() {
570 RelativeRotary relativeRotary = this.relativeRotary;
571 if (relativeRotary == null) {
572 return UnDefType.NULL;
574 RotaryReport rotaryReport = relativeRotary.getRotaryReport();
575 if (rotaryReport == null) {
576 return relativeRotary.getStepsState();
578 Rotation rotation = rotaryReport.getRotation();
579 if (rotation == null) {
580 return UnDefType.NULL;
582 return rotation.getStepsState();
585 public State getRotaryStepsLastUpdatedState(ZoneId zoneId) {
586 RelativeRotary relativeRotary = this.relativeRotary;
587 if (relativeRotary == null) {
588 return UnDefType.NULL;
590 RotaryReport rotaryReport = relativeRotary.getRotaryReport();
591 if (rotaryReport == null) {
592 return UnDefType.UNDEF;
594 Instant lastChanged = rotaryReport.getLastChanged();
595 if (Instant.EPOCH.equals(lastChanged)) {
596 return UnDefType.UNDEF;
598 return new DateTimeType(ZonedDateTime.ofInstant(lastChanged, zoneId));
602 * Check if the scene resource contains a 'status.active' element. If such an element is present, returns a Boolean
603 * Optional whose value depends on the value of that element, or an empty Optional if it is not.
605 * @return true, false, or empty.
607 public Optional<Boolean> getSceneActive() {
608 if (ResourceType.SCENE == getType()) {
609 JsonElement status = this.status;
610 if (Objects.nonNull(status) && status.isJsonObject()) {
611 JsonElement active = ((JsonObject) status).get("active");
612 if (Objects.nonNull(active) && active.isJsonPrimitive()) {
613 return Optional.of(!"inactive".equalsIgnoreCase(active.getAsString()));
617 return Optional.empty();
621 * If the getSceneActive() optional result is empty return 'UnDefType.NULL'. Otherwise if the optional result is
622 * present and 'true' (i.e. the scene is active) return the scene name. Or finally (the optional result is present
623 * and 'false') return 'UnDefType.UNDEF'.
625 * @return either 'UnDefType.NULL', a StringType containing the (active) scene name, or 'UnDefType.UNDEF'.
627 public State getSceneState() {
628 return getSceneActive().map(a -> a ? new StringType(getName()) : UnDefType.UNDEF).orElse(UnDefType.NULL);
632 * Check if the smart scene resource contains a 'state' element. If such an element is present, returns a Boolean
633 * Optional whose value depends on the value of that element, or an empty Optional if it is not.
635 * @return true, false, or empty.
637 public Optional<Boolean> getSmartSceneActive() {
638 if (ResourceType.SMART_SCENE == getType()) {
639 String state = this.state;
640 if (Objects.nonNull(state)) {
641 return Optional.of(SmartSceneState.ACTIVE == SmartSceneState.of(state));
644 return Optional.empty();
648 * If the getSmartSceneActive() optional result is empty return 'UnDefType.NULL'. Otherwise if the optional result
649 * is present and 'true' (i.e. the scene is active) return the smart scene name. Or finally (the optional result is
650 * present and 'false') return 'UnDefType.UNDEF'.
652 * @return either 'UnDefType.NULL', a StringType containing the (active) scene name, or 'UnDefType.UNDEF'.
654 public State getSmartSceneState() {
655 return getSmartSceneActive().map(a -> a ? new StringType(getName()) : UnDefType.UNDEF).orElse(UnDefType.NULL);
658 public List<ResourceReference> getServiceReferences() {
659 List<ResourceReference> services = this.services;
660 return Objects.nonNull(services) ? services : List.of();
663 public JsonObject getStatus() {
664 JsonElement status = this.status;
665 if (Objects.nonNull(status) && status.isJsonObject()) {
666 return status.getAsJsonObject();
668 return new JsonObject();
671 public State getTamperLastUpdatedState(ZoneId zoneId) {
672 TamperReport report = getTamperReportsLatest();
673 return Objects.nonNull(report) ? new DateTimeType(ZonedDateTime.ofInstant(report.getLastChanged(), zoneId))
678 * The the Hue bridge could return its raw list of tamper reports in any order, so sort the list (latest entry
679 * first) according to the respective 'changed' instant and return the first entry i.e. the latest changed entry.
681 * @return the latest changed tamper report
683 private @Nullable TamperReport getTamperReportsLatest() {
684 List<TamperReport> reports = this.tamperReports;
685 return Objects.nonNull(reports)
686 ? reports.stream().sorted((e1, e2) -> e2.getLastChanged().compareTo(e1.getLastChanged())).findFirst()
691 public State getTamperState() {
692 TamperReport report = getTamperReportsLatest();
693 return Objects.nonNull(report)
694 ? TamperStateType.TAMPERED == report.getTamperState() ? OpenClosedType.OPEN : OpenClosedType.CLOSED
698 public @Nullable Temperature getTemperature() {
702 public State getTemperatureState() {
703 Temperature temperature = this.temperature;
704 if (temperature == null) {
705 return UnDefType.NULL;
707 TemperatureReport temperatureReport = temperature.getTemperatureReport();
708 if (temperatureReport == null) {
709 return temperature.getTemperatureState();
711 return new QuantityType<>(temperatureReport.getTemperature(), SIUnits.CELSIUS);
714 public State getTemperatureLastUpdatedState(ZoneId zoneId) {
715 Temperature temperature = this.temperature;
716 if (temperature == null) {
717 return UnDefType.NULL;
719 TemperatureReport temperatureReport = temperature.getTemperatureReport();
720 if (temperatureReport == null) {
721 return UnDefType.UNDEF;
723 Instant lastChanged = temperatureReport.getLastChanged();
724 if (Instant.EPOCH.equals(lastChanged)) {
725 return UnDefType.UNDEF;
727 return new DateTimeType(ZonedDateTime.ofInstant(lastChanged, zoneId));
730 public State getTemperatureValidState() {
731 Temperature temperature = this.temperature;
732 return Objects.nonNull(temperature) ? temperature.getTemperatureValidState() : UnDefType.NULL;
735 public @Nullable TimedEffects getTimedEffects() {
739 public ResourceType getType() {
740 return ResourceType.of(type);
743 public State getZigbeeState() {
744 ZigbeeStatus zigbeeStatus = getZigbeeStatus();
745 return Objects.nonNull(zigbeeStatus) ? new StringType(zigbeeStatus.toString()) : UnDefType.NULL;
748 public @Nullable ZigbeeStatus getZigbeeStatus() {
749 JsonElement status = this.status;
750 if (Objects.nonNull(status) && status.isJsonPrimitive()) {
751 return ZigbeeStatus.of(status.getAsString());
756 public boolean hasFullState() {
757 return !hasSparseData;
761 * Mark that the resource has sparse data.
763 * @return this instance.
765 public Resource markAsSparse() {
766 hasSparseData = true;
770 public Resource setAlerts(Alerts alert) {
775 public Resource setColorTemperature(ColorTemperature colorTemperature) {
776 this.colorTemperature = colorTemperature;
780 public Resource setColorXy(ColorXy color) {
785 public Resource setContactReport(ContactReport contactReport) {
786 this.contactReport = contactReport;
790 public Resource setDimming(Dimming dimming) {
791 this.dimming = dimming;
795 public Resource setDynamicsDuration(Duration duration) {
796 dynamics = new Dynamics().setDuration(duration);
800 public Resource setFixedEffects(Effects effect) {
801 this.effects = effect;
805 public Resource setEnabled(Command command) {
806 if (command instanceof OnOffType) {
807 this.enabled = ((OnOffType) command) == OnOffType.ON;
812 public Resource setId(String id) {
817 public Resource setMetadata(MetaData metadata) {
818 this.metadata = metadata;
822 public Resource setMirekSchema(@Nullable MirekSchema schema) {
823 ColorTemperature colorTemperature = this.colorTemperature;
824 if (Objects.nonNull(colorTemperature)) {
825 colorTemperature.setMirekSchema(schema);
831 * Set the on/off JSON element (only).
833 * @param command an OnOffTypee command value.
834 * @return this resource instance.
836 public Resource setOnOff(Command command) {
837 if (command instanceof OnOffType) {
838 OnOffType onOff = (OnOffType) command;
839 OnState on = this.on;
840 on = Objects.nonNull(on) ? on : new OnState();
841 on.setOn(OnOffType.ON.equals(onOff));
847 public void setOnState(OnState on) {
851 public Resource setRecallAction(SceneRecallAction recallAction) {
852 Recall recall = this.recall;
853 this.recall = ((Objects.nonNull(recall) ? recall : new Recall())).setAction(recallAction);
857 public Resource setRecallAction(SmartSceneRecallAction recallAction) {
858 Recall recall = this.recall;
859 this.recall = ((Objects.nonNull(recall) ? recall : new Recall())).setAction(recallAction);
863 public Resource setRecallDuration(Duration recallDuration) {
864 Recall recall = this.recall;
865 this.recall = ((Objects.nonNull(recall) ? recall : new Recall())).setDuration(recallDuration);
869 public Resource setTamperReports(List<TamperReport> tamperReports) {
870 this.tamperReports = tamperReports;
874 public Resource setTimedEffects(TimedEffects timedEffects) {
875 this.timedEffects = timedEffects;
879 public Resource setTimedEffectsDuration(Duration dynamicsDuration) {
880 TimedEffects timedEffects = this.timedEffects;
881 if (Objects.nonNull(timedEffects)) {
882 timedEffects.setDuration(dynamicsDuration);
887 public Resource setType(ResourceType resourceType) {
888 this.type = resourceType.name().toLowerCase();
893 public String toString() {
895 return String.format("id:%s, type:%s", Objects.nonNull(id) ? id : "?" + " ".repeat(35),
896 getType().name().toLowerCase());