From 02e88c41cdd5f29f239cb694a5f01d4367989fc3 Mon Sep 17 00:00:00 2001 From: Laurent Garnier Date: Wed, 13 Nov 2024 23:22:34 +0100 Subject: [PATCH] New channel color temperature in mirek Signed-off-by: Laurent Garnier --- .../internal/MatterBindingConstants.java | 28 ++---------- .../MatterStateDescriptionOptionProvider.java | 44 +++++++++++++++++++ .../converter/ColorControlConverter.java | 39 +++++++++++++++- .../internal/handler/EndpointHandler.java | 20 ++++++++- .../main/resources/OH-INF/thing/channels.xml | 25 +++++++++++ 5 files changed, 129 insertions(+), 27 deletions(-) diff --git a/bundles/org.openhab.binding.matter/src/main/java/org/openhab/binding/matter/internal/MatterBindingConstants.java b/bundles/org.openhab.binding.matter/src/main/java/org/openhab/binding/matter/internal/MatterBindingConstants.java index 1b633167a898f..ceedfd873394b 100644 --- a/bundles/org.openhab.binding.matter/src/main/java/org/openhab/binding/matter/internal/MatterBindingConstants.java +++ b/bundles/org.openhab.binding.matter/src/main/java/org/openhab/binding/matter/internal/MatterBindingConstants.java @@ -39,113 +39,93 @@ public class MatterBindingConstants { // This was borrowed from the zigbee binding as Matter uses the same cluster API model // List of Channel UIDs - public static final String CHANNEL_NAME_ONOFF_ONOFF = "onoff"; public static final String CHANNEL_LABEL_ONOFF_ONOFF = "On Off"; public static final ChannelTypeUID CHANNEL_ONOFF_ONOFF = new ChannelTypeUID("matter:onoffcontrol-onoff"); - public static final String CHANNEL_NAME_LEVEL_LEVEL = "level"; public static final String CHANNEL_LABEL_LEVEL_LEVEL = "Level Control"; public static final ChannelTypeUID CHANNEL_LEVEL_LEVEL = new ChannelTypeUID("matter:levelcontrol-level"); - public static final String CHANNEL_NAME_COLOR_COLOR = "color"; public static final String CHANNEL_LABEL_COLOR_COLOR = "Color Control"; public static final ChannelTypeUID CHANNEL_COLOR_COLOR = new ChannelTypeUID("matter:colorcontrol-color"); - public static final String CHANNEL_NAME_COLOR_TEMPERATURE = "colortemperature"; public static final String CHANNEL_LABEL_COLOR_TEMPERATURE = "Color Temperature"; public static final ChannelTypeUID CHANNEL_COLOR_TEMPERATURE = new ChannelTypeUID( "matter:colorcontrol-temperature"); - public static final String CHANNEL_NAME_POWER_BATTERYPERCENT = "powersourcebatpercentremaining"; + public static final String CHANNEL_LABEL_COLOR_TEMPERATURE_ABS = "Color Temperature"; + public static final ChannelTypeUID CHANNEL_COLOR_TEMPERATURE_ABS = new ChannelTypeUID( + "matter:colorcontrol-temperature-abs"); + public static final String CHANNEL_LABEL_POWER_BATTERYPERCENT = "Battery Percent Remaining"; public static final ChannelTypeUID CHANNEL_POWER_BATTERYPERCENT = new ChannelTypeUID( "matter:powersource-batpercentremaining"); - public static final String CHANNEL_NAME_POWER_CHARGELEVEL = "powersourcechargelevel"; public static final String CHANNEL_LABEL_POWER_CHARGELEVEL = "Battery Charge Level"; public static final ChannelTypeUID CHANNEL_POWER_CHARGELEVEL = new ChannelTypeUID( "matter:powersource-batchargelevel"); - public static final String CHANNEL_NAME_THERMOSTAT_LOCALTEMPERATURE = "thermostatlocaltemperature"; public static final String CHANNEL_LABEL_THERMOSTAT_LOCALTEMPERATURE = "Local Temperature"; public static final ChannelTypeUID CHANNEL_THERMOSTAT_LOCALTEMPERATURE = new ChannelTypeUID( "matter:thermostat-localtemperature"); - public static final String CHANNEL_NAME_THERMOSTAT_OUTDOORTEMPERATURE = "thermostatoutdoortemperature"; public static final String CHANNEL_LABEL_THERMOSTAT_OUTDOORTEMPERATURE = "Outdoor Temperature"; public static final ChannelTypeUID CHANNEL_THERMOSTAT_OUTDOORTEMPERATURE = new ChannelTypeUID( "matter:thermostat-outdoortemperature"); - public static final String CHANNEL_NAME_THERMOSTAT_OCCUPIEDCOOLING = "thermostatoccupiedcooling"; public static final String CHANNEL_LABEL_THERMOSTAT_OCCUPIEDCOOLING = "Occupied Cooling Setpoint"; public static final ChannelTypeUID CHANNEL_THERMOSTAT_OCCUPIEDCOOLING = new ChannelTypeUID( "matter:thermostat-occupiedcooling"); - public static final String CHANNEL_NAME_THERMOSTAT_OCCUPIEDHEATING = "thermostatoccupiedheating"; public static final String CHANNEL_LABEL_THERMOSTAT_OCCUPIEDHEATING = "Occupied Heating Setpoint"; public static final ChannelTypeUID CHANNEL_THERMOSTAT_OCCUPIEDHEATING = new ChannelTypeUID( "matter:thermostat-occupiedheating"); - public static final String CHANNEL_NAME_THERMOSTAT_UNOCCUPIEDCOOLING = "thermostatunoccupiedcooling"; public static final String CHANNEL_LABEL_THERMOSTAT_UNOCCUPIEDCOOLING = "Unoccupied Cooling Setpoint"; public static final ChannelTypeUID CHANNEL_THERMOSTAT_UNOCCUPIEDCOOLING = new ChannelTypeUID( "matter:thermostat-unoccupiedcooling"); - public static final String CHANNEL_NAME_THERMOSTAT_UNOCCUPIEDHEATING = "thermostatunoccupiedheating"; public static final String CHANNEL_LABEL_THERMOSTAT_UNOCCUPIEDHEATING = "Unoccupied Heating Setpoint"; public static final ChannelTypeUID CHANNEL_THERMOSTAT_UNOCCUPIEDHEATING = new ChannelTypeUID( "matter:thermostat-unoccupiedheating"); - public static final String CHANNEL_NAME_THERMOSTAT_SYSTEMMODE = "thermostatsystemmode"; public static final String CHANNEL_LABEL_THERMOSTAT_SYSTEMMODE = "System Mode"; public static final ChannelTypeUID CHANNEL_THERMOSTAT_SYSTEMMODE = new ChannelTypeUID( "matter:thermostat-systemmode"); - public static final String CHANNEL_NAME_THERMOSTAT_RUNNINGMODE = "thermostatrunningmode"; public static final String CHANNEL_LABEL_THERMOSTAT_RUNNINGMODE = "Running Mode"; public static final ChannelTypeUID CHANNEL_THERMOSTAT_RUNNINGMODE = new ChannelTypeUID( "matter:thermostat-runningmode"); - public static final String CHANNEL_NAME_THERMOSTAT_HEATING_DEMAND = "thermostatheatingdemand"; public static final String CHANNEL_LABEL_THERMOSTAT_HEATING_DEMAND = "Heating Demand"; public static final ChannelTypeUID CHANNEL_THERMOSTAT_HEATING_DEMAND = new ChannelTypeUID( "matter:thermostat-heatingdemand"); - public static final String CHANNEL_NAME_THERMOSTAT_COOLING_DEMAND = "thermostatcoolingdemand"; public static final String CHANNEL_LABEL_THERMOSTAT_COOLING_DEMAND = "Cooling Demand"; public static final ChannelTypeUID CHANNEL_THERMOSTAT_COOLING_DEMAND = new ChannelTypeUID( "matter:thermostat-coolingdemand"); - public static final String CHANNEL_NAME_DOORLOCK_STATE = "doorlockstate"; public static final String CHANNEL_LABEL_DOORLOCK_STATE = "Door Lock State"; public static final ChannelTypeUID CHANNEL_DOORLOCK_STATE = new ChannelTypeUID("matter:door-state"); - public static final String CHANNEL_NAME_WINDOWCOVERING_LIFT = "windowcoveringlift"; public static final String CHANNEL_LABEL_WINDOWCOVERING_LIFT = "Window Covering Lift"; public static final ChannelTypeUID CHANNEL_WINDOWCOVERING_LIFT = new ChannelTypeUID("matter:windowcovering-lift"); - public static final String CHANNEL_NAME_FANCONTROL_PERCENT = "fancontrolpercent"; public static final String CHANNEL_LABEL_FANCONTROL_PERCENT = "Fan Control Percent"; public static final ChannelTypeUID CHANNEL_FANCONTROL_PERCENT = new ChannelTypeUID("matter:fancontrol-percent"); - public static final String CHANNEL_NAME_FANCONTROL_MODE = "fancontrolmode"; public static final String CHANNEL_LABEL_FANCONTROL_MODE = "Fan Control Mode"; public static final ChannelTypeUID CHANNEL_FANCONTROL_MODE = new ChannelTypeUID("matter:fancontrol-mode"); - public static final String CHANNEL_NAME_TEMPERATUREMEASURMENT_MEASUREDVALUE = "temperaturemeasurementmeasuredvalue"; public static final String CHANNEL_LABEL_TEMPERATUREMEASURMENT_MEASUREDVALUE = "Temperature"; public static final ChannelTypeUID CHANNEL_TEMPERATUREMEASURMENT_MEASUREDVALUE = new ChannelTypeUID( "matter:temperaturemeasurement-measuredvalue"); - public static final String CHANNEL_NAME_OCCUPANCYSENSING_OCCUPIED = "occupancysensingoccupied"; public static final String CHANNEL_LABEL_OCCUPANCYSENSING_OCCUPIED = "Occupied"; public static final ChannelTypeUID CHANNEL_OCCUPANCYSENSING_OCCUPIED = new ChannelTypeUID( "matter:occupancysensing-occupied"); - public static final String CHANNEL_NAME_MODESELECT_MODE = "MODESELECT_MODE"; public static final ChannelTypeUID CHANNEL_MODESELECT_MODE = new ChannelTypeUID("matter:modeselect-mode"); - public static final String CHANNEL_NAME_SWITCH_SWITCH = "SWITCH_SWITCH"; public static final String CHANNEL_LABEL_SWITCH_SWITCH = "Switch"; public static final ChannelTypeUID CHANNEL_SWITCH_SWITCH = new ChannelTypeUID("matter:switch-switch"); public static final ChannelTypeUID CHANNEL_SWITCH_SWITCHLATECHED = new ChannelTypeUID( diff --git a/bundles/org.openhab.binding.matter/src/main/java/org/openhab/binding/matter/internal/MatterStateDescriptionOptionProvider.java b/bundles/org.openhab.binding.matter/src/main/java/org/openhab/binding/matter/internal/MatterStateDescriptionOptionProvider.java index b7424651c2b65..7aab01a9f8701 100644 --- a/bundles/org.openhab.binding.matter/src/main/java/org/openhab/binding/matter/internal/MatterStateDescriptionOptionProvider.java +++ b/bundles/org.openhab.binding.matter/src/main/java/org/openhab/binding/matter/internal/MatterStateDescriptionOptionProvider.java @@ -12,12 +12,25 @@ */ package org.openhab.binding.matter.internal; +import java.math.BigDecimal; +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.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; @@ -31,6 +44,8 @@ @NonNullByDefault public class MatterStateDescriptionOptionProvider extends BaseDynamicStateDescriptionProvider { + private final Map stateDescriptionFragments = new ConcurrentHashMap<>(); + @Activate public MatterStateDescriptionOptionProvider(final @Reference EventPublisher eventPublisher, // final @Reference ItemChannelLinkRegistry itemChannelLinkRegistry, // @@ -39,4 +54,33 @@ public MatterStateDescriptionOptionProvider(final @Reference EventPublisher even this.itemChannelLinkRegistry = itemChannelLinkRegistry; this.channelTypeI18nLocalizationService = channelTypeI18nLocalizationService; } + + @Override + 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, original, locale); + } + + public void setMinMax(ChannelUID channelUID, BigDecimal min, BigDecimal max, @Nullable BigDecimal step, + @Nullable String pattern) { + StateDescriptionFragment oldStateDescriptionFragment = stateDescriptionFragments.get(channelUID); + StateDescriptionFragmentBuilder builder = StateDescriptionFragmentBuilder.create().withMinimum(min) + .withMaximum(max); + if (step != null) { + builder = builder.withStep(step); + } + if (pattern != null) { + builder = builder.withPattern(pattern); + } + StateDescriptionFragment newStateDescriptionFragment = builder.build(); + if (!newStateDescriptionFragment.equals(oldStateDescriptionFragment)) { + stateDescriptionFragments.put(channelUID, newStateDescriptionFragment); + ItemChannelLinkRegistry itemChannelLinkRegistry = this.itemChannelLinkRegistry; + postEvent(ThingEventFactory.createChannelDescriptionChangedEvent(channelUID, + itemChannelLinkRegistry != null ? itemChannelLinkRegistry.getLinkedItemNames(channelUID) : Set.of(), + newStateDescriptionFragment, oldStateDescriptionFragment)); + } + } } diff --git a/bundles/org.openhab.binding.matter/src/main/java/org/openhab/binding/matter/internal/devices/converter/ColorControlConverter.java b/bundles/org.openhab.binding.matter/src/main/java/org/openhab/binding/matter/internal/devices/converter/ColorControlConverter.java index 3d66278b01666..941e9e3946c50 100644 --- a/bundles/org.openhab.binding.matter/src/main/java/org/openhab/binding/matter/internal/devices/converter/ColorControlConverter.java +++ b/bundles/org.openhab.binding.matter/src/main/java/org/openhab/binding/matter/internal/devices/converter/ColorControlConverter.java @@ -14,6 +14,7 @@ import static org.openhab.binding.matter.internal.MatterBindingConstants.*; +import java.math.BigDecimal; import java.util.HashMap; import java.util.Map; import java.util.Optional; @@ -36,12 +37,15 @@ 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.unit.Units; import org.openhab.core.thing.Channel; import org.openhab.core.thing.ChannelUID; import org.openhab.core.thing.ThingUID; import org.openhab.core.thing.binding.builder.ChannelBuilder; import org.openhab.core.types.Command; import org.openhab.core.types.StateDescription; +import org.openhab.core.types.StateDescriptionFragmentBuilder; import org.openhab.core.types.UnDefType; import org.openhab.core.util.ColorUtil; import org.slf4j.Logger; @@ -50,7 +54,7 @@ /** * @author Dan Cunningham - Initial contribution * @author Chris Jackson - Original Zigbee binding color logic - * + * */ @NonNullByDefault public class ColorControlConverter extends GenericConverter { @@ -89,6 +93,22 @@ public ColorControlConverter(ColorControlCluster cluster, EndpointHandler handle if (cluster.featureMap.colorTemperature) { map.put(ChannelBuilder.create(new ChannelUID(thingUID, CHANNEL_COLOR_TEMPERATURE.getId()), ITEM_TYPE_DIMMER) .withType(CHANNEL_COLOR_TEMPERATURE).withLabel(CHANNEL_LABEL_COLOR_TEMPERATURE).build(), null); + + Optional.ofNullable(cluster.colorTempPhysicalMinMireds) + .ifPresent(temp -> colorTempPhysicalMinMireds = temp); + Optional.ofNullable(cluster.colorTempPhysicalMaxMireds) + .ifPresent(temp -> colorTempPhysicalMaxMireds = temp); + StateDescription stateDescription = null; + if (colorTempPhysicalMinMireds < colorTempPhysicalMaxMireds) { + stateDescription = StateDescriptionFragmentBuilder.create().withPattern("%.0f mirek") + .withMinimum(BigDecimal.valueOf(colorTempPhysicalMinMireds)) + .withMaximum(BigDecimal.valueOf(colorTempPhysicalMaxMireds)).build().toStateDescription(); + } + map.put(ChannelBuilder + .create(new ChannelUID(thingUID, CHANNEL_COLOR_TEMPERATURE_ABS.getId()), + ITEM_TYPE_NUMBER_TEMPERATURE) + .withType(CHANNEL_COLOR_TEMPERATURE_ABS).withLabel(CHANNEL_LABEL_COLOR_TEMPERATURE_ABS).build(), + stateDescription); } return map; } @@ -125,6 +145,19 @@ public void handleCommand(ChannelUID channelUID, Command command) { optionsBitmap, optionsBitmap); handler.sendClusterCommand(LevelControlCluster.CLUSTER_NAME, levelCommand); } + } else if (channelUID.getId().equals(CHANNEL_COLOR_TEMPERATURE_ABS.getId()) + && command instanceof DecimalType decimal) { + ClusterCommand tempCommand = ColorControlCluster.moveToColorTemperature(decimal.intValue(), 0, optionsMask, + optionsMask); + handler.sendClusterCommand(ColorControlCluster.CLUSTER_NAME, tempCommand); + } else if (channelUID.getId().equals(CHANNEL_COLOR_TEMPERATURE_ABS.getId()) + && command instanceof QuantityType quantity) { + quantity = quantity.toInvertibleUnit(Units.MIRED); + if (quantity != null) { + ClusterCommand tempCommand = ColorControlCluster.moveToColorTemperature(quantity.intValue(), 0, + optionsMask, optionsMask); + handler.sendClusterCommand(ColorControlCluster.CLUSTER_NAME, tempCommand); + } } } @@ -151,6 +184,8 @@ public void onEvent(AttributeChangedMessage message) { case "colorTemperatureMireds": updateState(CHANNEL_COLOR_TEMPERATURE, numberValue == 0 ? UnDefType.UNDEF : miredsToPercenType(numberValue)); + updateState(CHANNEL_COLOR_TEMPERATURE_ABS, numberValue == 0 ? UnDefType.UNDEF + : QuantityType.valueOf(Double.valueOf(numberValue), Units.MIRED)); break; case "enhancedCurrentHue": break; @@ -191,6 +226,8 @@ public void updateCluster(ColorControlCluster cluster) { if (cluster.colorTemperatureMireds != null) { updateState(CHANNEL_COLOR_TEMPERATURE, cluster.colorTemperatureMireds == 0 ? UnDefType.UNDEF : miredsToPercenType(cluster.colorTemperatureMireds)); + updateState(CHANNEL_COLOR_TEMPERATURE_ABS, cluster.colorTemperatureMireds == 0 ? UnDefType.UNDEF + : QuantityType.valueOf(Double.valueOf(cluster.colorTemperatureMireds), Units.MIRED)); } } diff --git a/bundles/org.openhab.binding.matter/src/main/java/org/openhab/binding/matter/internal/handler/EndpointHandler.java b/bundles/org.openhab.binding.matter/src/main/java/org/openhab/binding/matter/internal/handler/EndpointHandler.java index d9ba5f3a0cf5a..958cea1cbe255 100644 --- a/bundles/org.openhab.binding.matter/src/main/java/org/openhab/binding/matter/internal/handler/EndpointHandler.java +++ b/bundles/org.openhab.binding.matter/src/main/java/org/openhab/binding/matter/internal/handler/EndpointHandler.java @@ -14,8 +14,12 @@ import java.math.BigDecimal; import java.math.BigInteger; -import java.util.*; +import java.util.Collection; +import java.util.Map; import java.util.Map.Entry; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; @@ -39,7 +43,12 @@ import org.openhab.binding.matter.internal.devices.types.DeviceTypeRegistry; import org.openhab.core.config.core.Configuration; import org.openhab.core.config.core.validation.ConfigValidationException; -import org.openhab.core.thing.*; +import org.openhab.core.thing.Bridge; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingStatus; +import org.openhab.core.thing.ThingStatusDetail; +import org.openhab.core.thing.ThingStatusInfo; import org.openhab.core.thing.binding.BaseThingHandler; import org.openhab.core.thing.binding.BridgeHandler; import org.openhab.core.thing.binding.ThingHandlerService; @@ -249,6 +258,13 @@ public void updateEndpoint(Endpoint endpoint) { Optional.ofNullable(stateDescription.getPattern()) .ifPresent(pattern -> stateDescriptionProvider.setStatePattern(channelUID, pattern)); + + BigDecimal min = stateDescription.getMinimum(); + BigDecimal max = stateDescription.getMaximum(); + if (min != null && max != null) { + stateDescriptionProvider.setMinMax(channelUID, min, max, stateDescription.getStep(), + stateDescription.getPattern()); + } } }); this.deviceType = deviceType; diff --git a/bundles/org.openhab.binding.matter/src/main/resources/OH-INF/thing/channels.xml b/bundles/org.openhab.binding.matter/src/main/resources/OH-INF/thing/channels.xml index 00f20d147a342..492b1d038b8c2 100644 --- a/bundles/org.openhab.binding.matter/src/main/resources/OH-INF/thing/channels.xml +++ b/bundles/org.openhab.binding.matter/src/main/resources/OH-INF/thing/channels.xml @@ -71,6 +71,10 @@ light on and off. ColorLight + + Control + Light + @@ -78,6 +82,23 @@ Sets the color temperature of the light ColorLight + + Control + ColorTemperature + + + + + + Number:Temperature + + Sets the color temperature of the light in mirek + ColorLight + + Control + ColorTemperature + + @@ -120,6 +141,10 @@ Sets the level of the light Light + + Control + Light +