From 33d8a7d8f7980bfefebe777e3ac6617ef42df10b Mon Sep 17 00:00:00 2001 From: Jacob Laursen Date: Thu, 16 Nov 2023 22:25:32 +0100 Subject: [PATCH] Fix multiple state updates Fixes #15700 Signed-off-by: Jacob Laursen --- .../hue/internal/api/dto/clip2/Resource.java | 50 ++- .../api/dto/clip2/helper/Setters.java | 45 ++- .../internal/handler/Clip2BridgeHandler.java | 15 +- .../internal/handler/Clip2ThingHandler.java | 75 ++-- .../resources/OH-INF/thing/Clip2Thing.xml | 32 +- .../main/resources/OH-INF/thing/channels.xml | 6 + .../resources/OH-INF/update/instructions.xml | 18 + .../hue/internal/clip2/SettersTest.java | 328 ++++++++++++++++++ 8 files changed, 511 insertions(+), 58 deletions(-) create mode 100644 bundles/org.openhab.binding.hue/src/test/java/org/openhab/binding/hue/internal/clip2/SettersTest.java diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/Resource.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/Resource.java index 04e92905ba6cc..70d6e8a9fc036 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/Resource.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/Resource.java @@ -121,6 +121,45 @@ public Resource(@Nullable ResourceType resourceType) { } } + /** + * Create an empty clone of this object. + * The clone will carry the same id, resource type and status. + * + * @return cloned object + */ + public Resource cloneEmpty() { + Resource clonedResource = new Resource(getType()); + clonedResource.id = id; + clonedResource.hasSparseData = true; + + JsonElement status = this.status; + clonedResource.status = status == null ? null : status.deepCopy(); + + return clonedResource; + } + + /** + * Check if resource contains any state information. + * + * @return true is resource doesn't contain any state information + */ + public boolean isEmpty() { + return metadata == null && on == null && dimming == null && colorTemperature == null && color == null + && alert == null && effects == null && timedEffects == null && actions == null && recall == null + && enabled == null && light == null && button == null && temperature == null && motion == null + && powerState == null && relativeRotary == null && contactReport == null && tamperReports == null + && status == null; + } + + /** + * Check if resource contains any HSB component (on, dimming or color). + * + * @return true if resource has any HSB component + */ + public boolean hasHSBComponent() { + return on != null || dimming != null || color != null; + } + public @Nullable List getActions() { return actions; } @@ -777,7 +816,7 @@ public Resource setColorTemperature(ColorTemperature colorTemperature) { return this; } - public Resource setColorXy(ColorXy color) { + public Resource setColorXy(@Nullable ColorXy color) { this.color = color; return this; } @@ -787,7 +826,7 @@ public Resource setContactReport(ContactReport contactReport) { return this; } - public Resource setDimming(Dimming dimming) { + public Resource setDimming(@Nullable Dimming dimming) { this.dimming = dimming; return this; } @@ -844,7 +883,7 @@ public Resource setOnOff(Command command) { return this; } - public void setOnState(OnState on) { + public void setOnState(@Nullable OnState on) { this.on = on; } @@ -889,6 +928,11 @@ public Resource setType(ResourceType resourceType) { return this; } + public Resource setStatus(JsonElement status) { + this.status = status.deepCopy(); + return this; + } + @Override public String toString() { String id = this.id; diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/helper/Setters.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/helper/Setters.java index 641a8025718c6..4d83c26ab1a03 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/helper/Setters.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/api/dto/clip2/helper/Setters.java @@ -14,8 +14,12 @@ import java.math.BigDecimal; import java.time.Duration; +import java.util.Collection; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Objects; +import java.util.Set; import javax.measure.Unit; @@ -33,6 +37,7 @@ import org.openhab.binding.hue.internal.api.dto.clip2.TimedEffects; import org.openhab.binding.hue.internal.api.dto.clip2.enums.ActionType; import org.openhab.binding.hue.internal.api.dto.clip2.enums.EffectType; +import org.openhab.binding.hue.internal.api.dto.clip2.enums.ResourceType; import org.openhab.core.library.types.DecimalType; import org.openhab.core.library.types.HSBType; import org.openhab.core.library.types.PercentType; @@ -52,6 +57,8 @@ @NonNullByDefault public class Setters { + private static final Set LIGHT_TYPES = Set.of(ResourceType.LIGHT, ResourceType.GROUPED_LIGHT); + /** * Setter for Alert field: * Use the given command value to set the target resource DTO value based on the attributes of the source resource @@ -341,7 +348,43 @@ public static Resource setResource(Resource target, Resource source) { targetTimedEffects.setDuration(duration); } } - return target; } + + /** + * Extract on/dimming/color fields from light and grouped light resources + * and merge them into separate resources. + */ + public static Collection mergeLightResources(Collection resources) { + Map extractedResources = new HashMap<>(); + for (Resource resource : resources) { + if (!resource.hasFullState() && LIGHT_TYPES.contains(resource.getType()) && resource.hasHSBComponent()) { + String id = resource.getId(); + Resource extractedResource = extractedResources.get(id); + if (extractedResource == null) { + extractedResource = resource.cloneEmpty(); + extractedResources.put(id, extractedResource); + } + OnState onState = resource.getOnState(); + if (onState != null) { + extractedResource.setOnState(onState); + resource.setOnState(null); + } + Dimming dimming = resource.getDimming(); + if (dimming != null) { + extractedResource.setDimming(dimming); + resource.setDimming(null); + } + ColorXy colorXy = resource.getColorXy(); + if (colorXy != null) { + extractedResource.setColorXy(colorXy); + resource.setColorXy(null); + } + } + } + resources.removeIf(r -> r.isEmpty()); + resources.addAll(extractedResources.values()); + + return resources; + } } diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/Clip2BridgeHandler.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/Clip2BridgeHandler.java index c94d39ef85199..e6dd7181ea26a 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/Clip2BridgeHandler.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/Clip2BridgeHandler.java @@ -37,6 +37,7 @@ import org.openhab.binding.hue.internal.api.dto.clip2.Resources; import org.openhab.binding.hue.internal.api.dto.clip2.enums.Archetype; import org.openhab.binding.hue.internal.api.dto.clip2.enums.ResourceType; +import org.openhab.binding.hue.internal.api.dto.clip2.helper.Setters; import org.openhab.binding.hue.internal.config.Clip2BridgeConfig; import org.openhab.binding.hue.internal.connection.Clip2Bridge; import org.openhab.binding.hue.internal.connection.HueTlsTrustManagerProvider; @@ -528,13 +529,15 @@ public void onResourcesEvent(List resources) { } private void onResourcesEventTask(List resources) { - logger.debug("onResourcesEventTask() resource count {}", resources.size()); + int numberOfResources = resources.size(); + logger.debug("onResourcesEventTask() resource count {}", numberOfResources); + Setters.mergeLightResources(resources); + if (numberOfResources != resources.size()) { + logger.debug("onResourcesEventTask() merged to {} resources", resources.size()); + } getThing().getThings().forEach(thing -> { - ThingHandler handler = thing.getHandler(); - if (handler instanceof Clip2ThingHandler) { - resources.forEach(resource -> { - ((Clip2ThingHandler) handler).onResource(resource); - }); + if (thing.getHandler() instanceof Clip2ThingHandler clip2ThingHandler) { + resources.forEach(resource -> clip2ThingHandler.onResource(resource)); } }); } diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/Clip2ThingHandler.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/Clip2ThingHandler.java index aef0ff5bf27ae..292416ec94f90 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/Clip2ThingHandler.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/Clip2ThingHandler.java @@ -635,46 +635,53 @@ public void initialize() { * @param resource a Resource object containing the new state. */ public void onResource(Resource resource) { - if (!disposing) { - boolean resourceConsumed = false; - String incomingResourceId = resource.getId(); - if (resourceId.equals(incomingResourceId)) { - if (resource.hasFullState()) { - thisResource = resource; - if (!updatePropertiesDone) { - updateProperties(resource); - resourceConsumed = updatePropertiesDone; - } - } - if (!updateDependenciesDone) { - resourceConsumed = true; - cancelTask(updateDependenciesTask, false); - updateDependenciesTask = scheduler.submit(() -> updateDependencies()); - } - } else if (SUPPORTED_SCENE_TYPES.contains(resource.getType())) { - Resource cachedScene = sceneContributorsCache.get(incomingResourceId); - if (Objects.nonNull(cachedScene)) { - Setters.setResource(resource, cachedScene); - resourceConsumed = updateChannels(resource); - sceneContributorsCache.put(incomingResourceId, resource); - } - } else { - Resource cachedService = serviceContributorsCache.get(incomingResourceId); - if (Objects.nonNull(cachedService)) { - Setters.setResource(resource, cachedService); - resourceConsumed = updateChannels(resource); - serviceContributorsCache.put(incomingResourceId, resource); - if (ResourceType.LIGHT == resource.getType() && !updateLightPropertiesDone) { - updateLightProperties(resource); - } + if (disposing) { + return; + } + boolean resourceConsumed = false; + if (resourceId.equals(resource.getId())) { + if (resource.hasFullState()) { + thisResource = resource; + if (!updatePropertiesDone) { + updateProperties(resource); + resourceConsumed = updatePropertiesDone; } } - if (resourceConsumed) { - logger.debug("{} -> onResource() consumed resource {}", resourceId, resource); + if (!updateDependenciesDone) { + resourceConsumed = true; + cancelTask(updateDependenciesTask, false); + updateDependenciesTask = scheduler.submit(() -> updateDependencies()); } + } else { + Resource cachedResource = getResourceFromCache(resource); + if (cachedResource != null) { + Setters.setResource(resource, cachedResource); + resourceConsumed = updateChannels(resource); + putResourceToCache(resource); + if (ResourceType.LIGHT == resource.getType() && !updateLightPropertiesDone) { + updateLightProperties(resource); + } + } + } + if (resourceConsumed) { + logger.debug("{} -> onResource() consumed resource {}", resourceId, resource); + } + } + + private void putResourceToCache(Resource resource) { + if (SUPPORTED_SCENE_TYPES.contains(resource.getType())) { + sceneContributorsCache.put(resource.getId(), resource); + } else { + serviceContributorsCache.put(resource.getId(), resource); } } + private @Nullable Resource getResourceFromCache(Resource resource) { + return SUPPORTED_SCENE_TYPES.contains(resource.getType()) // + ? sceneContributorsCache.get(resource.getId()) + : serviceContributorsCache.get(resource.getId()); + } + /** * Update the thing internal state depending on a full list of resources sent from the bridge. If the resourceType * is SCENE then call updateScenes(), otherwise if the resource refers to this thing, consume it via onResource() as diff --git a/bundles/org.openhab.binding.hue/src/main/resources/OH-INF/thing/Clip2Thing.xml b/bundles/org.openhab.binding.hue/src/main/resources/OH-INF/thing/Clip2Thing.xml index 33f5d935c5608..ae1a34aeba55e 100644 --- a/bundles/org.openhab.binding.hue/src/main/resources/OH-INF/thing/Clip2Thing.xml +++ b/bundles/org.openhab.binding.hue/src/main/resources/OH-INF/thing/Clip2Thing.xml @@ -13,10 +13,18 @@ A Hue API v2 device with channels depending on its actual capabilities. - - - - + + veto + + + veto + + + veto + + + veto + Activate the alert for the light. @@ -85,20 +93,16 @@ - - - Set the color xy parameter of the light without changing other state parameters. - - - Set the dimming parameter of the light without changing other state parameters. - - - Set the on/off parameter of the light without changing other state parameters. + + veto + + + - 1 + 2 resourceId diff --git a/bundles/org.openhab.binding.hue/src/main/resources/OH-INF/thing/channels.xml b/bundles/org.openhab.binding.hue/src/main/resources/OH-INF/thing/channels.xml index c1f82b5516ac7..d4ba6e9b70f2e 100644 --- a/bundles/org.openhab.binding.hue/src/main/resources/OH-INF/thing/channels.xml +++ b/bundles/org.openhab.binding.hue/src/main/resources/OH-INF/thing/channels.xml @@ -240,20 +240,26 @@ Color + Set the color xy parameter of the light without changing other state parameters. ColorLight + veto Dimmer + Set the dimming parameter of the light without changing other state parameters. Light + veto Switch + Set the on/off parameter of the light without changing other state parameters. Switch + veto diff --git a/bundles/org.openhab.binding.hue/src/main/resources/OH-INF/update/instructions.xml b/bundles/org.openhab.binding.hue/src/main/resources/OH-INF/update/instructions.xml index 4e851e97e4e7c..935e61b44de81 100644 --- a/bundles/org.openhab.binding.hue/src/main/resources/OH-INF/update/instructions.xml +++ b/bundles/org.openhab.binding.hue/src/main/resources/OH-INF/update/instructions.xml @@ -33,6 +33,24 @@ + + + system:brightness + + + system:color + + + system:color-temperature + + + system:color-temperature-abs + + + system:power + + + diff --git a/bundles/org.openhab.binding.hue/src/test/java/org/openhab/binding/hue/internal/clip2/SettersTest.java b/bundles/org.openhab.binding.hue/src/test/java/org/openhab/binding/hue/internal/clip2/SettersTest.java new file mode 100644 index 0000000000000..94d0ceae78dc2 --- /dev/null +++ b/bundles/org.openhab.binding.hue/src/test/java/org/openhab/binding/hue/internal/clip2/SettersTest.java @@ -0,0 +1,328 @@ +/** + * 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.clip2; + +import static org.hamcrest.CoreMatchers.*; +import static org.hamcrest.MatcherAssert.assertThat; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Stream; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.openhab.binding.hue.internal.api.dto.clip2.ColorTemperature; +import org.openhab.binding.hue.internal.api.dto.clip2.Dimming; +import org.openhab.binding.hue.internal.api.dto.clip2.OnState; +import org.openhab.binding.hue.internal.api.dto.clip2.Resource; +import org.openhab.binding.hue.internal.api.dto.clip2.enums.ResourceType; +import org.openhab.binding.hue.internal.api.dto.clip2.helper.Setters; +import org.openhab.binding.hue.internal.exceptions.DTOPresentButEmptyException; + +import com.google.gson.JsonParser; + +/** + * Tests for {@link Setters}. + * + * @author Jacob Laursen - Initial contribution + */ +@NonNullByDefault +public class SettersTest { + + /** + * Tests merging of on state and dimming for same resource. + * + * Input: + * - Resource 1: type=light/grouped_light, sparse, id=1, on=on + * - Resource 2: type=light/grouped_light, sparse, id=1, dimming=50 + * + * Expected output: + * - Resource 1: type=light/grouped_light, sparse, id=1, on=on, dimming=50 + * + * @throws DTOPresentButEmptyException + */ + @ParameterizedTest + @MethodSource("provideLightResourceTypes") + void mergeLightResourcesMergeOnStateAndDimmingWhenSparseAndSameId(ResourceType resourceType) + throws DTOPresentButEmptyException { + List resources = new ArrayList<>(); + + Resource resource1 = createResource(resourceType, "1", true); + resource1.setOnState(createOnState(true)); + resources.add(resource1); + + Resource resource2 = createResource(resourceType, "1", true); + resource2.setDimming(createDimming(50)); + resources.add(resource2); + + Setters.mergeLightResources(resources); + + assertThat(resources.size(), is(equalTo(1))); + Resource mergedResource = resources.get(0); + assertThat(mergedResource.getId(), is(equalTo(resource1.getId()))); + assertThat(mergedResource.getType(), is(equalTo(resourceType))); + assertThat(mergedResource.hasFullState(), is(false)); + OnState actualOnState = mergedResource.getOnState(); + assertThat(actualOnState, is(notNullValue())); + if (actualOnState != null) { + assertThat(actualOnState.isOn(), is(true)); + } + Dimming actualDimming = mergedResource.getDimming(); + assertThat(actualDimming, is(notNullValue())); + if (actualDimming != null) { + assertThat(actualDimming.getBrightness(), is(equalTo(50.0))); + } + } + + private static Stream provideLightResourceTypes() { + return Stream.of(Arguments.of(ResourceType.LIGHT), Arguments.of(ResourceType.GROUPED_LIGHT)); + } + + /** + * Tests leaving different resources separated. + * + * Input: + * - Resource 1: type=light, sparse, id=1, on=on + * - Resource 2: type=light, sparse, id=2, dimming=50 + * + * Expected output: + * - Resource 1: type=light, sparse, id=1, on=on + * - Resource 2: type=light, sparse, id=2, dimming=50 + * + * @throws DTOPresentButEmptyException + */ + @Test + void mergeLightResourcesDoNotMergeOnStateAndDimmingWhenSparseAndDifferentId() throws DTOPresentButEmptyException { + List resources = new ArrayList<>(); + + Resource resource1 = createResource(ResourceType.LIGHT, "1", true); + resource1.setOnState(createOnState(true)); + resources.add(resource1); + + Resource resource2 = createResource(ResourceType.LIGHT, "2", true); + resource2.setDimming(createDimming(50)); + resources.add(resource2); + + Setters.mergeLightResources(resources); + + assertThat(resources.size(), is(equalTo(2))); + Resource firstResource = resources.get(0); + OnState actualOnState = firstResource.getOnState(); + assertThat(actualOnState, is(notNullValue())); + if (actualOnState != null) { + assertThat(actualOnState.isOn(), is(true)); + } + assertThat(firstResource.getDimming(), is(nullValue())); + + Resource secondResource = resources.get(1); + assertThat(secondResource.getOnState(), is(nullValue())); + Dimming actualDimming = secondResource.getDimming(); + assertThat(actualDimming, is(notNullValue())); + if (actualDimming != null) { + assertThat(actualDimming.getBrightness(), is(equalTo(50.0))); + } + } + + /** + * Tests merging of state and dimming for same resource. + * + * Input: + * - Resource 1: type=light, full, id=1, on=on + * - Resource 2: type=light, sparse, id=1, dimming=50 + * + * Expected output: + * - Resource 1: type=light, full, id=1, on=on + * - Resource 2: type=light, sparse, id=1, dimming=50 + * + * @throws DTOPresentButEmptyException + */ + @Test + void mergeLightResourcesDoNotMergeOnStateAndDimmingWhenFullStateAndSameId() throws DTOPresentButEmptyException { + List resources = new ArrayList<>(); + + Resource resource1 = createResource(ResourceType.LIGHT, "1", false); + resource1.setOnState(createOnState(true)); + resources.add(resource1); + + Resource resource2 = createResource(ResourceType.LIGHT, "1", true); + resource2.setDimming(createDimming(50)); + resources.add(resource2); + + Setters.mergeLightResources(resources); + + assertThat(resources.size(), is(equalTo(2))); + Resource firstResource = resources.get(0); + OnState actualOnState = firstResource.getOnState(); + assertThat(actualOnState, is(notNullValue())); + if (actualOnState != null) { + assertThat(actualOnState.isOn(), is(true)); + } + assertThat(firstResource.getDimming(), is(nullValue())); + + Resource secondResource = resources.get(1); + assertThat(secondResource.getOnState(), is(nullValue())); + Dimming actualDimming = secondResource.getDimming(); + assertThat(actualDimming, is(notNullValue())); + if (actualDimming != null) { + assertThat(actualDimming.getBrightness(), is(equalTo(50.0))); + } + } + + /** + * Tests leaving resources with on state and color temperature separated. + * + * Input: + * - Resource 1: type=light, sparse, id=1, on=on + * - Resource 2: type=light, sparse, id=1, color temperature=370 mirek + * + * Expected output: + * - Resource 1: type=light, sparse, id=1, on=on + * - Resource 2: type=light, sparse, id=1, color temperature=370 mirek + * + * @throws DTOPresentButEmptyException + */ + @Test + void mergeLightResourcesDoNotMergeOnStateAndColorTemperatureWhenSparseAndSameId() + throws DTOPresentButEmptyException { + List resources = new ArrayList<>(); + + Resource resource1 = createResource(ResourceType.LIGHT, "1", true); + resource1.setOnState(createOnState(true)); + resources.add(resource1); + + Resource resource2 = createResource(ResourceType.LIGHT, "1", true); + resource2.setColorTemperature(createColorTemperature(370)); + resources.add(resource2); + + Setters.mergeLightResources(resources); + + assertThat(resources.size(), is(equalTo(2))); + Resource firstResource = resources.get(1); + OnState actualOnState = firstResource.getOnState(); + assertThat(actualOnState, is(notNullValue())); + if (actualOnState != null) { + assertThat(actualOnState.isOn(), is(true)); + } + assertThat(firstResource.getColorTemperature(), is(nullValue())); + + Resource secondResource = resources.get(0); + assertThat(secondResource.getOnState(), is(nullValue())); + ColorTemperature actualColorTemperature = secondResource.getColorTemperature(); + assertThat(actualColorTemperature, is(notNullValue())); + if (actualColorTemperature != null) { + assertThat(actualColorTemperature.getMirek(), is(equalTo(370L))); + } + } + + /** + * Tests separating resource with on state and color temperature. + * + * Input: + * - Resource 1: type=light, sparse, id=1, on=on, color temperature=370 + * + * Expected output: + * - Resource 1: type=light, sparse, id=1, color temperature=370 + * - Resource 2: type=light, sparse, id=1, on=on + * + * @throws DTOPresentButEmptyException + */ + @Test + void mergeLightResourcesSeparateOnStateAndColorTemperatureWhenSparseAndSameId() throws DTOPresentButEmptyException { + List resources = new ArrayList<>(); + + Resource resource = createResource(ResourceType.LIGHT, "1", true); + resource.setOnState(createOnState(true)); + resource.setColorTemperature(createColorTemperature(370)); + resources.add(resource); + + Setters.mergeLightResources(resources); + + assertThat(resources.size(), is(equalTo(2))); + + Resource firstResource = resources.get(0); + assertThat(firstResource.getOnState(), is(nullValue())); + ColorTemperature actualColorTemperature = firstResource.getColorTemperature(); + assertThat(actualColorTemperature, is(notNullValue())); + if (actualColorTemperature != null) { + assertThat(actualColorTemperature.getMirek(), is(equalTo(370L))); + } + + Resource secondResource = resources.get(1); + OnState actualOnState = secondResource.getOnState(); + assertThat(actualOnState, is(notNullValue())); + if (actualOnState != null) { + assertThat(actualOnState.isOn(), is(true)); + } + assertThat(secondResource.getColorTemperature(), is(nullValue())); + } + + /** + * Tests that Zigbee connectivity resource is preserved. + * + * Input: + * - Resource 1: type=zigbee_connectivity, sparse, id=1, connected + * + * Expected output: + * - Resource 1: type=zigbee_connectivity, sparse, id=1, connected + * + * @throws DTOPresentButEmptyException + */ + @Test + void mergeLightResourcesZigbeeStatusOnlyIsPreserved() throws DTOPresentButEmptyException { + List resources = new ArrayList<>(); + + Resource resource = createResource(ResourceType.ZIGBEE_CONNECTIVITY, "1", true); + resource.setStatus(JsonParser.parseString("connected")); + resources.add(resource); + + Setters.mergeLightResources(resources); + + assertThat(resources.size(), is(equalTo(1))); + Resource firstResource = resources.get(0); + assertThat(firstResource.getType(), is(equalTo(ResourceType.ZIGBEE_CONNECTIVITY))); + } + + private OnState createOnState(boolean on) { + OnState onState = new OnState(); + onState.setOn(on); + + return onState; + } + + private Dimming createDimming(double brightness) { + Dimming dimming = new Dimming(); + dimming.setBrightness(brightness); + + return dimming; + } + + private ColorTemperature createColorTemperature(double mirek) { + ColorTemperature colorTemperature = new ColorTemperature(); + colorTemperature.setMirek(mirek); + + return colorTemperature; + } + + private Resource createResource(ResourceType resourceType, String id, boolean sparse) { + Resource resource = new Resource(resourceType); + resource.setId(id); + if (sparse) { + resource.markAsSparse(); + } + + return resource; + } +}