From db34aa3a987fdd362d3538fe4df9f0ec301f695d Mon Sep 17 00:00:00 2001 From: AndrewFG Date: Sun, 27 Oct 2024 10:36:26 +0000 Subject: [PATCH 1/3] [hue] provide color temperature min/max values dynamically in state description Signed-off-by: AndrewFG --- .../Clip2StateDescriptionProvider.java | 36 +++++++++++++++++-- .../internal/handler/Clip2ThingHandler.java | 30 ++++++++++++++++ 2 files changed, 64 insertions(+), 2 deletions(-) diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/Clip2StateDescriptionProvider.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/Clip2StateDescriptionProvider.java index 32523ffba9357..4d899e90e093c 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/Clip2StateDescriptionProvider.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/Clip2StateDescriptionProvider.java @@ -12,19 +12,30 @@ */ package org.openhab.binding.hue.internal.handler; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; import org.openhab.core.events.EventPublisher; +import org.openhab.core.thing.Channel; +import org.openhab.core.thing.ChannelUID; import org.openhab.core.thing.binding.BaseDynamicStateDescriptionProvider; +import org.openhab.core.thing.events.ThingEventFactory; import org.openhab.core.thing.i18n.ChannelTypeI18nLocalizationService; import org.openhab.core.thing.link.ItemChannelLinkRegistry; import org.openhab.core.thing.type.DynamicStateDescriptionProvider; +import org.openhab.core.types.StateDescription; +import org.openhab.core.types.StateDescriptionFragment; import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Reference; /** - * The {@link Clip2StateDescriptionProvider} provides dynamic state descriptions of scene channels whose list of options - * is determined at runtime. + * The {@link Clip2StateDescriptionProvider} provides dynamic state descriptions of alert, effect, scene, and colour + * temperature channels whose capabilities are dynamically determined at runtime. * * @author Andrew Fiddian-Green - Initial contribution * @@ -33,6 +44,8 @@ @Component(service = { DynamicStateDescriptionProvider.class, Clip2StateDescriptionProvider.class }) public class Clip2StateDescriptionProvider extends BaseDynamicStateDescriptionProvider { + private final Map stateDescriptionFragments = new ConcurrentHashMap<>(); + @Activate public Clip2StateDescriptionProvider(final @Reference EventPublisher eventPublisher, final @Reference ItemChannelLinkRegistry itemChannelLinkRegistry, @@ -41,4 +54,23 @@ public Clip2StateDescriptionProvider(final @Reference EventPublisher eventPublis this.itemChannelLinkRegistry = itemChannelLinkRegistry; this.channelTypeI18nLocalizationService = channelTypeI18nLocalizationService; } + + @Override + public @Nullable StateDescription getStateDescription(Channel channel, + @Nullable StateDescription originalStateDescription, @Nullable Locale locale) { + StateDescriptionFragment stateDescriptionFragment = stateDescriptionFragments.get(channel.getUID()); + return stateDescriptionFragment != null ? stateDescriptionFragment.toStateDescription() + : super.getStateDescription(channel, originalStateDescription, locale); + } + + public void setStateDescriptionFragment(ChannelUID channelUID, StateDescriptionFragment stateDescriptionFragment) { + StateDescriptionFragment oldStateDescriptionFragment = stateDescriptionFragments.get(channelUID); + if (!stateDescriptionFragment.equals(oldStateDescriptionFragment)) { + stateDescriptionFragments.put(channelUID, stateDescriptionFragment); + ItemChannelLinkRegistry itemChannelLinkRegistry = this.itemChannelLinkRegistry; + postEvent(ThingEventFactory.createChannelDescriptionChangedEvent(channelUID, + itemChannelLinkRegistry != null ? itemChannelLinkRegistry.getLinkedItemNames(channelUID) : Set.of(), + stateDescriptionFragment, oldStateDescriptionFragment)); + } + } } 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 ddbe824eaa192..97484484b91fb 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 @@ -38,6 +38,7 @@ import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.hue.internal.action.DynamicsActions; import org.openhab.binding.hue.internal.api.dto.clip2.Alerts; +import org.openhab.binding.hue.internal.api.dto.clip2.ColorTemperature; import org.openhab.binding.hue.internal.api.dto.clip2.ColorXy; import org.openhab.binding.hue.internal.api.dto.clip2.Dimming; import org.openhab.binding.hue.internal.api.dto.clip2.Effects; @@ -91,6 +92,8 @@ import org.openhab.core.types.Command; import org.openhab.core.types.RefreshType; import org.openhab.core.types.State; +import org.openhab.core.types.StateDescriptionFragment; +import org.openhab.core.types.StateDescriptionFragmentBuilder; import org.openhab.core.types.StateOption; import org.openhab.core.types.UnDefType; import org.slf4j.Logger; @@ -945,6 +948,7 @@ private boolean updateChannels(Resource resource) { case LIGHT: if (fullUpdate) { updateEffectChannel(resource); + updateColorTempAbsChannel(resource); } updateState(CHANNEL_2_COLOR_TEMP_PERCENT, resource.getColorTemperaturePercentState(), fullUpdate); updateState(CHANNEL_2_COLOR_TEMP_ABSOLUTE, resource.getColorTemperatureAbsoluteState(), fullUpdate); @@ -1114,6 +1118,32 @@ public void updateEffectChannel(Resource resource) { } } + /** + * Process the incoming Resource to initialize the colour temperature absolute channel's state description based on + * the minimum and maximum values supported by the lamp's Mirek schema. + * + * @param resource a Resource possibly containing a color temperature element and respective Mirek schema element. + */ + private void updateColorTempAbsChannel(Resource resource) { + ColorTemperature colorTemperature = resource.getColorTemperature(); + if (colorTemperature == null) { + return; + } + MirekSchema mirekSchema = colorTemperature.getMirekSchema(); + if (mirekSchema == null) { + return; + } + StateDescriptionFragment stateDescriptionFragment = StateDescriptionFragmentBuilder.create() + .withMinimum(BigDecimal.valueOf(1000000 / mirekSchema.getMirekMaximum())) // + .withMaximum(BigDecimal.valueOf(1000000 / mirekSchema.getMirekMinimum())) // + .withPattern("%.0f K") // + .withReadOnly(false) // + .build(); + stateDescriptionProvider.setStateDescriptionFragment( + new ChannelUID(thing.getUID(), CHANNEL_2_COLOR_TEMP_ABSOLUTE), stateDescriptionFragment); + logger.debug("{} -> updateColorTempAbsChannel() {} ", resource.getId(), stateDescriptionFragment); + } + /** * Update the light properties. * From 150bf02d24896c8ce668ee2bd211b535d9027795 Mon Sep 17 00:00:00 2001 From: AndrewFG Date: Sun, 27 Oct 2024 19:13:56 +0000 Subject: [PATCH 2/3] Performance improvements Signed-off-by: AndrewFG --- .../Clip2StateDescriptionProvider.java | 38 +++++++++++++++---- .../internal/handler/Clip2ThingHandler.java | 28 +++++--------- 2 files changed, 39 insertions(+), 27 deletions(-) diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/Clip2StateDescriptionProvider.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/Clip2StateDescriptionProvider.java index 4d899e90e093c..2b17f4847182c 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/Clip2StateDescriptionProvider.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/Clip2StateDescriptionProvider.java @@ -12,6 +12,7 @@ */ package org.openhab.binding.hue.internal.handler; +import java.math.BigDecimal; import java.util.Locale; import java.util.Map; import java.util.Set; @@ -29,6 +30,7 @@ import org.openhab.core.thing.type.DynamicStateDescriptionProvider; import org.openhab.core.types.StateDescription; import org.openhab.core.types.StateDescriptionFragment; +import org.openhab.core.types.StateDescriptionFragmentBuilder; import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Reference; @@ -56,21 +58,41 @@ public Clip2StateDescriptionProvider(final @Reference EventPublisher eventPublis } @Override - public @Nullable StateDescription getStateDescription(Channel channel, - @Nullable StateDescription originalStateDescription, @Nullable Locale locale) { + public @Nullable StateDescription getStateDescription(Channel channel, @Nullable StateDescription original, + @Nullable Locale locale) { StateDescriptionFragment stateDescriptionFragment = stateDescriptionFragments.get(channel.getUID()); - return stateDescriptionFragment != null ? stateDescriptionFragment.toStateDescription() - : super.getStateDescription(channel, originalStateDescription, locale); + if (stateDescriptionFragment != null) { + StateDescriptionFragmentBuilder builder = original == null ? StateDescriptionFragmentBuilder.create() + : StateDescriptionFragmentBuilder.create(original); + String pattern = original != null ? original.getPattern() : null; + pattern = pattern != null ? pattern : "%.0f K"; + builder.withPattern(pattern); + BigDecimal minimum = stateDescriptionFragment.getMinimum(); + if (minimum != null) { + builder.withMinimum(minimum); + } + BigDecimal maximum = stateDescriptionFragment.getMaximum(); + if (maximum != null) { + builder.withMaximum(maximum); + } + return builder.build().toStateDescription(); + } + return super.getStateDescription(channel, original, locale); } - public void setStateDescriptionFragment(ChannelUID channelUID, StateDescriptionFragment stateDescriptionFragment) { + /** + * Set the state description minimum and maximum values for the given channel UID + */ + public void setMinMax(ChannelUID channelUID, long min, long max) { StateDescriptionFragment oldStateDescriptionFragment = stateDescriptionFragments.get(channelUID); - if (!stateDescriptionFragment.equals(oldStateDescriptionFragment)) { - stateDescriptionFragments.put(channelUID, stateDescriptionFragment); + StateDescriptionFragment newStateDescriptionFragment = StateDescriptionFragmentBuilder.create() + .withMinimum(BigDecimal.valueOf(min)).withMaximum(BigDecimal.valueOf(max)).build(); + if (!newStateDescriptionFragment.equals(oldStateDescriptionFragment)) { + stateDescriptionFragments.put(channelUID, newStateDescriptionFragment); ItemChannelLinkRegistry itemChannelLinkRegistry = this.itemChannelLinkRegistry; postEvent(ThingEventFactory.createChannelDescriptionChangedEvent(channelUID, itemChannelLinkRegistry != null ? itemChannelLinkRegistry.getLinkedItemNames(channelUID) : Set.of(), - stateDescriptionFragment, oldStateDescriptionFragment)); + newStateDescriptionFragment, oldStateDescriptionFragment)); } } } 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 97484484b91fb..a56d72efe6c6f 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 @@ -92,8 +92,6 @@ import org.openhab.core.types.Command; import org.openhab.core.types.RefreshType; import org.openhab.core.types.State; -import org.openhab.core.types.StateDescriptionFragment; -import org.openhab.core.types.StateDescriptionFragmentBuilder; import org.openhab.core.types.StateOption; import org.openhab.core.types.UnDefType; import org.slf4j.Logger; @@ -948,7 +946,7 @@ private boolean updateChannels(Resource resource) { case LIGHT: if (fullUpdate) { updateEffectChannel(resource); - updateColorTempAbsChannel(resource); + updateColorTemperatureAbsoluteChannel(resource); } updateState(CHANNEL_2_COLOR_TEMP_PERCENT, resource.getColorTemperaturePercentState(), fullUpdate); updateState(CHANNEL_2_COLOR_TEMP_ABSOLUTE, resource.getColorTemperatureAbsoluteState(), fullUpdate); @@ -1124,24 +1122,16 @@ public void updateEffectChannel(Resource resource) { * * @param resource a Resource possibly containing a color temperature element and respective Mirek schema element. */ - private void updateColorTempAbsChannel(Resource resource) { + private void updateColorTemperatureAbsoluteChannel(Resource resource) { ColorTemperature colorTemperature = resource.getColorTemperature(); - if (colorTemperature == null) { - return; - } - MirekSchema mirekSchema = colorTemperature.getMirekSchema(); - if (mirekSchema == null) { - return; + if (colorTemperature != null) { + MirekSchema mirekSchema = colorTemperature.getMirekSchema(); + if (mirekSchema != null) { + stateDescriptionProvider.setMinMax(new ChannelUID(thing.getUID(), CHANNEL_2_COLOR_TEMP_ABSOLUTE), + 1000000 / mirekSchema.getMirekMaximum(), 1000000 / mirekSchema.getMirekMinimum()); + logger.debug("{} -> updateColorTempAbsChannel() done", resource.getId()); + } } - StateDescriptionFragment stateDescriptionFragment = StateDescriptionFragmentBuilder.create() - .withMinimum(BigDecimal.valueOf(1000000 / mirekSchema.getMirekMaximum())) // - .withMaximum(BigDecimal.valueOf(1000000 / mirekSchema.getMirekMinimum())) // - .withPattern("%.0f K") // - .withReadOnly(false) // - .build(); - stateDescriptionProvider.setStateDescriptionFragment( - new ChannelUID(thing.getUID(), CHANNEL_2_COLOR_TEMP_ABSOLUTE), stateDescriptionFragment); - logger.debug("{} -> updateColorTempAbsChannel() {} ", resource.getId(), stateDescriptionFragment); } /** From ce8c4e63ea78d10a441c83f83ab92f17a1bc72b1 Mon Sep 17 00:00:00 2001 From: AndrewFG Date: Mon, 28 Oct 2024 06:20:27 +0000 Subject: [PATCH 3/3] Adopt reviewer suggestion Signed-off-by: AndrewFG --- .../Clip2StateDescriptionProvider.java | 26 +++++-------------- .../internal/handler/Clip2ThingHandler.java | 2 +- 2 files changed, 7 insertions(+), 21 deletions(-) diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/Clip2StateDescriptionProvider.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/Clip2StateDescriptionProvider.java index 2b17f4847182c..6e7c9d516539f 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/Clip2StateDescriptionProvider.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/Clip2StateDescriptionProvider.java @@ -61,32 +61,18 @@ public Clip2StateDescriptionProvider(final @Reference EventPublisher eventPublis public @Nullable StateDescription getStateDescription(Channel channel, @Nullable StateDescription original, @Nullable Locale locale) { StateDescriptionFragment stateDescriptionFragment = stateDescriptionFragments.get(channel.getUID()); - if (stateDescriptionFragment != null) { - StateDescriptionFragmentBuilder builder = original == null ? StateDescriptionFragmentBuilder.create() - : StateDescriptionFragmentBuilder.create(original); - String pattern = original != null ? original.getPattern() : null; - pattern = pattern != null ? pattern : "%.0f K"; - builder.withPattern(pattern); - BigDecimal minimum = stateDescriptionFragment.getMinimum(); - if (minimum != null) { - builder.withMinimum(minimum); - } - BigDecimal maximum = stateDescriptionFragment.getMaximum(); - if (maximum != null) { - builder.withMaximum(maximum); - } - return builder.build().toStateDescription(); - } - return super.getStateDescription(channel, original, locale); + return stateDescriptionFragment != null ? stateDescriptionFragment.toStateDescription() + : super.getStateDescription(channel, original, locale); } /** - * Set the state description minimum and maximum values for the given channel UID + * Set the state description minimum and maximum values and pattern in Kelvin for the given channel UID */ - public void setMinMax(ChannelUID channelUID, long min, long max) { + public void setMinMaxKelvin(ChannelUID channelUID, long minKelvin, long maxKelvin) { StateDescriptionFragment oldStateDescriptionFragment = stateDescriptionFragments.get(channelUID); StateDescriptionFragment newStateDescriptionFragment = StateDescriptionFragmentBuilder.create() - .withMinimum(BigDecimal.valueOf(min)).withMaximum(BigDecimal.valueOf(max)).build(); + .withMinimum(BigDecimal.valueOf(minKelvin)).withMaximum(BigDecimal.valueOf(maxKelvin)) + .withStep(BigDecimal.valueOf(100)).withPattern("%.0f K").build(); if (!newStateDescriptionFragment.equals(oldStateDescriptionFragment)) { stateDescriptionFragments.put(channelUID, newStateDescriptionFragment); ItemChannelLinkRegistry itemChannelLinkRegistry = this.itemChannelLinkRegistry; 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 a56d72efe6c6f..15e2c8b484e9f 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 @@ -1127,7 +1127,7 @@ private void updateColorTemperatureAbsoluteChannel(Resource resource) { if (colorTemperature != null) { MirekSchema mirekSchema = colorTemperature.getMirekSchema(); if (mirekSchema != null) { - stateDescriptionProvider.setMinMax(new ChannelUID(thing.getUID(), CHANNEL_2_COLOR_TEMP_ABSOLUTE), + stateDescriptionProvider.setMinMaxKelvin(new ChannelUID(thing.getUID(), CHANNEL_2_COLOR_TEMP_ABSOLUTE), 1000000 / mirekSchema.getMirekMaximum(), 1000000 / mirekSchema.getMirekMinimum()); logger.debug("{} -> updateColorTempAbsChannel() done", resource.getId()); }