diff --git a/bundles/org.openhab.binding.mqtt.generic/src/main/java/org/openhab/binding/mqtt/generic/ChannelState.java b/bundles/org.openhab.binding.mqtt.generic/src/main/java/org/openhab/binding/mqtt/generic/ChannelState.java index 62361348d26aa..681e5073e9af0 100644 --- a/bundles/org.openhab.binding.mqtt.generic/src/main/java/org/openhab/binding/mqtt/generic/ChannelState.java +++ b/bundles/org.openhab.binding.mqtt.generic/src/main/java/org/openhab/binding/mqtt/generic/ChannelState.java @@ -51,13 +51,13 @@ public class ChannelState implements MqttMessageSubscriber { // Immutable channel configuration protected final boolean readOnly; - protected final ChannelUID channelUID; protected final ChannelConfig config; /** Channel value **/ protected final Value cachedValue; // Runtime variables + protected ChannelUID channelUID; private @Nullable MqttBrokerConnection connection; protected final ChannelTransformation incomingTransformation; protected final ChannelTransformation outgoingTransformation; @@ -132,6 +132,11 @@ public ChannelUID channelUID() { return channelUID; } + // If the UID of the channel changed after it was initially created + public void setChannelUID(ChannelUID channelUID) { + this.channelUID = channelUID; + } + /** * Incoming message from the MqttBrokerConnection * diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/ComponentChannel.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/ComponentChannel.java index 0131823c4ccdb..ac27112bff1ba 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/ComponentChannel.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/ComponentChannel.java @@ -58,7 +58,7 @@ @NonNullByDefault public class ComponentChannel { private final ChannelState channelState; - private final Channel channel; + private Channel channel; private final @Nullable StateDescription stateDescription; private final @Nullable CommandDescription commandDescription; private final ChannelStateUpdateListener channelStateUpdateListener; @@ -77,6 +77,18 @@ public Channel getChannel() { return channel; } + public void resetUID(ChannelUID channelUID) { + channel = ChannelBuilder.create(channelUID, channel.getAcceptedItemType()).withType(channel.getChannelTypeUID()) + .withKind(channel.getKind()).withLabel(Objects.requireNonNull(channel.getLabel())) + .withConfiguration(channel.getConfiguration()).withAutoUpdatePolicy(channel.getAutoUpdatePolicy()) + .build(); + channelState.setChannelUID(channelUID); + } + + public void clearConfiguration() { + channel = ChannelBuilder.create(channel).withConfiguration(new Configuration()).build(); + } + public ChannelState getState() { return channelState; } diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/AbstractComponent.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/AbstractComponent.java index 111322a253921..877303c91f103 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/AbstractComponent.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/AbstractComponent.java @@ -24,6 +24,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.mqtt.generic.AvailabilityTracker; +import org.openhab.binding.mqtt.generic.ChannelState; import org.openhab.binding.mqtt.generic.ChannelStateUpdateListener; import org.openhab.binding.mqtt.generic.MqttChannelStateDescriptionProvider; import org.openhab.binding.mqtt.generic.values.Value; @@ -39,7 +40,6 @@ import org.openhab.binding.mqtt.homeassistant.internal.config.dto.Device; import org.openhab.core.io.transport.mqtt.MqttBrokerConnection; import org.openhab.core.thing.Channel; -import org.openhab.core.thing.ChannelGroupUID; import org.openhab.core.thing.ChannelUID; import org.openhab.core.thing.binding.generic.ChannelTransformation; import org.openhab.core.thing.type.ChannelDefinition; @@ -65,7 +65,6 @@ public abstract class AbstractComponent // Component location fields protected final ComponentConfiguration componentConfiguration; - protected final @Nullable ChannelGroupUID channelGroupUID; protected final HaID haID; // Channels and configuration @@ -79,14 +78,10 @@ public abstract class AbstractComponent protected final C channelConfiguration; protected boolean configSeen; - protected final boolean singleChannelComponent; - protected final String groupId; + protected final boolean newStyleChannels; protected final String uniqueId; - - public AbstractComponent(ComponentFactory.ComponentConfiguration componentConfiguration, Class clazz, - boolean newStyleChannels) { - this(componentConfiguration, clazz, newStyleChannels, false); - } + protected @Nullable String groupId; + protected String componentId; /** * Creates component based on generic configuration and component configuration type. @@ -98,9 +93,9 @@ public AbstractComponent(ComponentFactory.ComponentConfiguration componentConfig * (only if newStyleChannels is true) */ public AbstractComponent(ComponentFactory.ComponentConfiguration componentConfiguration, Class clazz, - boolean newStyleChannels, boolean singleChannelComponent) { + boolean newStyleChannels) { this.componentConfiguration = componentConfiguration; - this.singleChannelComponent = newStyleChannels && singleChannelComponent; + this.newStyleChannels = newStyleChannels; this.channelConfigurationJson = componentConfiguration.getConfigJSON(); this.channelConfiguration = componentConfiguration.getConfig(clazz); @@ -109,14 +104,16 @@ public AbstractComponent(ComponentFactory.ComponentConfiguration componentConfig this.haID = componentConfiguration.getHaID(); String name = channelConfiguration.getName(); - if (name != null && !name.isEmpty()) { - groupId = this.haID.getGroupId(channelConfiguration.getUniqueId(), newStyleChannels); - - this.channelGroupUID = this.singleChannelComponent ? null - : new ChannelGroupUID(componentConfiguration.getThingUID(), groupId); + if (newStyleChannels) { + // try for a simple component/group ID first; if there are conflicts + // (components of different types, but the same object id) + // we'll resolve them later + groupId = componentId = haID.objectID.replace('-', '_'); + } else if (name != null && !name.isEmpty()) { + groupId = componentId = this.haID.getGroupId(channelConfiguration.getUniqueId(), false); } else { - this.groupId = this.singleChannelComponent ? haID.component : ""; - this.channelGroupUID = null; + groupId = null; + componentId = ""; } uniqueId = this.haID.getGroupId(channelConfiguration.getUniqueId(), false); @@ -155,10 +152,30 @@ public AbstractComponent(ComponentFactory.ComponentConfiguration componentConfig } } + protected void finalizeChannels() { + if (!newStyleChannels) { + return; + } + if (channels.size() == 1) { + groupId = null; + channels.values().forEach(c -> c.resetUID(buildChannelUID(componentId))); + } else { + // only the first channel needs to persist the configuration + channels.values().stream().skip(1).forEach(c -> { + c.clearConfiguration(); + }); + } + } + + public void resolveConflict() { + componentId = this.haID.getGroupId(channelConfiguration.getUniqueId(), newStyleChannels); + channels.values().forEach(c -> c.resetUID(buildChannelUID(c.getChannel().getUID().getIdWithoutGroup()))); + } + protected ComponentChannel.Builder buildChannel(String channelID, ComponentChannelType channelType, Value valueState, String label, ChannelStateUpdateListener channelStateUpdateListener) { - if (singleChannelComponent) { - channelID = groupId; + if (groupId == null) { + channelID = componentId; } return new ComponentChannel.Builder(this, channelID, channelType.getChannelTypeUID(), valueState, label, channelStateUpdateListener); @@ -216,15 +233,19 @@ public void addStateDescriptions(MqttChannelStateDescriptionProvider stateDescri } public ChannelUID buildChannelUID(String channelID) { - final ChannelGroupUID groupUID = channelGroupUID; - if (groupUID != null) { - return new ChannelUID(groupUID, channelID); + final String localGroupID = groupId; + if (localGroupID != null) { + return new ChannelUID(componentConfiguration.getThingUID(), localGroupID, channelID); } return new ChannelUID(componentConfiguration.getThingUID(), channelID); } - public String getGroupId() { - return groupId; + public String getComponentId() { + return componentId; + } + + public String getUniqueId() { + return uniqueId; } /** @@ -273,7 +294,7 @@ public int getConfigHash() { * Return the channel group type. */ public @Nullable ChannelGroupType getChannelGroupType(String prefix) { - if (channelGroupUID == null) { + if (groupId == null) { return null; } return ChannelGroupTypeBuilder.instance(getChannelGroupTypeUID(prefix), getName()) @@ -281,7 +302,7 @@ public int getConfigHash() { } public List getChannelDefinitions() { - if (channelGroupUID != null) { + if (groupId != null) { return List.of(); } return getAllChannelDefinitions(); @@ -295,6 +316,10 @@ public List getChannels() { return channels.values().stream().map(ComponentChannel::getChannel).toList(); } + public void getChannelStates(Map states) { + channels.values().forEach(c -> states.put(c.getChannel().getUID(), c.getState())); + } + /** * Resets all channel states to state UNDEF. Call this method after the connection * to the MQTT broker got lost. @@ -307,14 +332,15 @@ public void resetState() { * Return the channel group definition for this component. */ public @Nullable ChannelGroupDefinition getGroupDefinition(String prefix) { - if (channelGroupUID == null) { + String localGroupId = groupId; + if (localGroupId == null) { return null; } - return new ChannelGroupDefinition(channelGroupUID.getId(), getChannelGroupTypeUID(prefix), getName(), null); + return new ChannelGroupDefinition(localGroupId, getChannelGroupTypeUID(prefix), getName(), null); } public boolean hasGroup() { - return channelGroupUID != null; + return groupId != null; } public HaID getHaID() { diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/AlarmControlPanel.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/AlarmControlPanel.java index 026ce5048556c..a915c11f43b7c 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/AlarmControlPanel.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/AlarmControlPanel.java @@ -97,5 +97,6 @@ public AlarmControlPanel(ComponentFactory.ComponentConfiguration componentConfig componentConfiguration.getUpdateListener()) .commandTopic(commandTopic, channelConfiguration.isRetain(), channelConfiguration.getQos()).build(); } + finalizeChannels(); } } diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/BinarySensor.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/BinarySensor.java index b6df93f18d6cf..9d4c7b5b84c1d 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/BinarySensor.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/BinarySensor.java @@ -69,7 +69,7 @@ static class ChannelConfiguration extends AbstractChannelConfiguration { } public BinarySensor(ComponentFactory.ComponentConfiguration componentConfiguration, boolean newStyleChannels) { - super(componentConfiguration, ChannelConfiguration.class, newStyleChannels, true); + super(componentConfiguration, ChannelConfiguration.class, newStyleChannels); OnOffValue value = new OnOffValue(channelConfiguration.payloadOn, channelConfiguration.payloadOff); @@ -77,6 +77,7 @@ public BinarySensor(ComponentFactory.ComponentConfiguration componentConfigurati getListener(componentConfiguration, value)) .stateTopic(channelConfiguration.stateTopic, channelConfiguration.getValueTemplate()) .withAutoUpdatePolicy(AutoUpdatePolicy.VETO).build(); + finalizeChannels(); } private ChannelStateUpdateListener getListener(ComponentFactory.ComponentConfiguration componentConfiguration, diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Button.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Button.java index cff40ea8e4ade..7ac20139deb8e 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Button.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Button.java @@ -48,7 +48,7 @@ static class ChannelConfiguration extends AbstractChannelConfiguration { } public Button(ComponentFactory.ComponentConfiguration componentConfiguration, boolean newStyleChannels) { - super(componentConfiguration, ChannelConfiguration.class, newStyleChannels, true); + super(componentConfiguration, ChannelConfiguration.class, newStyleChannels); TextValue value = new TextValue(new String[] { channelConfiguration.payloadPress }); @@ -57,5 +57,6 @@ public Button(ComponentFactory.ComponentConfiguration componentConfiguration, bo .commandTopic(channelConfiguration.commandTopic, channelConfiguration.isRetain(), channelConfiguration.getQos()) .withAutoUpdatePolicy(AutoUpdatePolicy.VETO).build(); + finalizeChannels(); } } diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Camera.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Camera.java index c1e934d432459..6bfe3ad98b2bb 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Camera.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Camera.java @@ -46,5 +46,6 @@ public Camera(ComponentFactory.ComponentConfiguration componentConfiguration, bo buildChannel(CAMERA_CHANNEL_ID, ComponentChannelType.IMAGE, value, getName(), componentConfiguration.getUpdateListener()).stateTopic(channelConfiguration.topic).build(); + finalizeChannels(); } } diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Climate.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Climate.java index 1c7726bc52ced..1a21945962c48 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Climate.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Climate.java @@ -284,6 +284,7 @@ public Climate(ComponentFactory.ComponentConfiguration componentConfiguration, b buildOptionalChannel(POWER_CH_ID, ComponentChannelType.SWITCH, new OnOffValue(), updateListener, null, channelConfiguration.powerCommandTopic, null, null, null); + finalizeChannels(); } @Nullable diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Cover.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Cover.java index 077a7b94cd51e..f365ec33ead36 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Cover.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Cover.java @@ -150,5 +150,6 @@ public Cover(ComponentFactory.ComponentConfiguration componentConfiguration, boo } return true; }).build(); + finalizeChannels(); } } diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/DefaultSchemaLight.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/DefaultSchemaLight.java index 9d412c1a14ba3..ef5e12786273a 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/DefaultSchemaLight.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/DefaultSchemaLight.java @@ -112,6 +112,7 @@ protected void buildChannels() { .build(); } + boolean hasColorChannel = false; if (channelConfiguration.rgbStateTopic != null || channelConfiguration.rgbCommandTopic != null) { hasColorChannel = true; hiddenChannels.add(rgbChannel = buildChannel(RGB_CHANNEL_ID, ComponentChannelType.COLOR, @@ -167,7 +168,7 @@ protected void buildChannels() { if (localBrightnessChannel != null) { hiddenChannels.add(localBrightnessChannel); } - buildChannel(COLOR_CHANNEL_ID, ComponentChannelType.COLOR, colorValue, "Color", this) + colorChannel = buildChannel(COLOR_CHANNEL_ID, ComponentChannelType.COLOR, colorValue, "Color", this) .commandTopic(DUMMY_TOPIC, channelConfiguration.isRetain(), channelConfiguration.getQos()) .commandFilter(this::handleColorCommand).build(); } else if (localBrightnessChannel != null) { @@ -280,74 +281,74 @@ private boolean handleColorCommand(Command command) { @Override public void updateChannelState(ChannelUID channel, State state) { ChannelStateUpdateListener listener = this.channelStateUpdateListener; - switch (channel.getIdWithoutGroup()) { - case ON_OFF_CHANNEL_ID: - if (hasColorChannel) { - HSBType newOnState = colorValue.getChannelState() instanceof HSBType - ? (HSBType) colorValue.getChannelState() - : HSBType.WHITE; - if (state.equals(OnOffType.ON)) { - colorValue.update(newOnState); - } - - listener.updateChannelState(buildChannelUID(COLOR_CHANNEL_ID), - state.equals(OnOffType.ON) ? newOnState : HSBType.BLACK); - } else if (brightnessChannel != null) { - listener.updateChannelState(new ChannelUID(channel.getThingUID(), BRIGHTNESS_CHANNEL_ID), - state.equals(OnOffType.ON) ? brightnessValue.getChannelState() : PercentType.ZERO); - } else { - listener.updateChannelState(channel, state); - } - return; - case BRIGHTNESS_CHANNEL_ID: - onOffValue.update(Objects.requireNonNull(state.as(OnOffType.class))); - if (hasColorChannel) { - if (colorValue.getChannelState() instanceof HSBType) { - HSBType hsb = (HSBType) (colorValue.getChannelState()); - colorValue.update(new HSBType(hsb.getHue(), hsb.getSaturation(), - (PercentType) brightnessValue.getChannelState())); - } else { - colorValue.update(new HSBType(DecimalType.ZERO, PercentType.ZERO, - (PercentType) brightnessValue.getChannelState())); - } - listener.updateChannelState(buildChannelUID(COLOR_CHANNEL_ID), colorValue.getChannelState()); - } else { - listener.updateChannelState(channel, state); - } - return; - case COLOR_TEMP_CHANNEL_ID: - case EFFECT_CHANNEL_ID: - // Real channels; pass through - listener.updateChannelState(channel, state); - return; - case HS_CHANNEL_ID: - case XY_CHANNEL_ID: - if (brightnessValue.getChannelState() instanceof UnDefType) { - brightnessValue.update(PercentType.HUNDRED); - } - String[] split = state.toString().split(","); - if (split.length != 2) { - throw new IllegalArgumentException(state.toString() + " is not a valid string syntax"); + String id = channel.getIdWithoutGroup(); + ComponentChannel localBrightnessChannel = brightnessChannel; + ComponentChannel localColorChannel = colorChannel; + ChannelUID primaryChannelUID; + if (localColorChannel != null) { + primaryChannelUID = localColorChannel.getChannel().getUID(); + } else if (localBrightnessChannel != null) { + primaryChannelUID = localBrightnessChannel.getChannel().getUID(); + } else { + primaryChannelUID = onOffChannel.getChannel().getUID(); + } + // on_off, brightness, and color might exist as a sole channel, which means + // they got renamed. they need to be compared against the actual UID of the + // channel. all the rest we can just check against the basic ID + if (channel.equals(onOffChannel.getChannel().getUID())) { + if (localColorChannel != null) { + HSBType newOnState = colorValue.getChannelState() instanceof HSBType newOnStateTmp ? newOnStateTmp + : HSBType.WHITE; + if (state.equals(OnOffType.ON)) { + colorValue.update(newOnState); } - float x = Float.parseFloat(split[0]); - float y = Float.parseFloat(split[1]); - PercentType brightness = (PercentType) brightnessValue.getChannelState(); - if (channel.getIdWithoutGroup().equals(HS_CHANNEL_ID)) { - colorValue.update(new HSBType(new DecimalType(x), new PercentType(new BigDecimal(y)), brightness)); + + listener.updateChannelState(primaryChannelUID, state.equals(OnOffType.ON) ? newOnState : HSBType.BLACK); + } else if (brightnessChannel != null) { + listener.updateChannelState(primaryChannelUID, + state.equals(OnOffType.ON) ? brightnessValue.getChannelState() : PercentType.ZERO); + } else { + listener.updateChannelState(primaryChannelUID, state); + } + } else if (localBrightnessChannel != null && localBrightnessChannel.getChannel().getUID().equals(channel)) { + onOffValue.update(Objects.requireNonNull(state.as(OnOffType.class))); + if (localColorChannel != null) { + if (colorValue.getChannelState() instanceof HSBType hsb) { + colorValue.update(new HSBType(hsb.getHue(), hsb.getSaturation(), + (PercentType) brightnessValue.getChannelState())); } else { - HSBType xyColor = HSBType.fromXY(x, y); - colorValue.update(new HSBType(xyColor.getHue(), xyColor.getSaturation(), brightness)); + colorValue.update(new HSBType(DecimalType.ZERO, PercentType.ZERO, + (PercentType) brightnessValue.getChannelState())); } - listener.updateChannelState(buildChannelUID(COLOR_CHANNEL_ID), colorValue.getChannelState()); - return; - case RGB_CHANNEL_ID: - colorValue.update((HSBType) state); - listener.updateChannelState(buildChannelUID(COLOR_CHANNEL_ID), colorValue.getChannelState()); - break; - case RGBW_CHANNEL_ID: - case RGBWW_CHANNEL_ID: - // TODO: update color value - break; + listener.updateChannelState(primaryChannelUID, colorValue.getChannelState()); + } else { + listener.updateChannelState(primaryChannelUID, state); + } + } else if (id.equals(COLOR_TEMP_CHANNEL_ID) || channel.getIdWithoutGroup().equals(EFFECT_CHANNEL_ID)) { + // Real channels; pass through + listener.updateChannelState(channel, state); + } else if (id.equals(HS_CHANNEL_ID) || id.equals(XY_CHANNEL_ID)) { + if (brightnessValue.getChannelState() instanceof UnDefType) { + brightnessValue.update(PercentType.HUNDRED); + } + String[] split = state.toString().split(","); + if (split.length != 2) { + throw new IllegalArgumentException(state.toString() + " is not a valid string syntax"); + } + float x = Float.parseFloat(split[0]); + float y = Float.parseFloat(split[1]); + PercentType brightness = (PercentType) brightnessValue.getChannelState(); + if (channel.getIdWithoutGroup().equals(HS_CHANNEL_ID)) { + colorValue.update(new HSBType(new DecimalType(x), new PercentType(new BigDecimal(y)), brightness)); + } else { + HSBType xyColor = HSBType.fromXY(x, y); + colorValue.update(new HSBType(xyColor.getHue(), xyColor.getSaturation(), brightness)); + } + listener.updateChannelState(primaryChannelUID, colorValue.getChannelState()); + } else if (id.equals(RGB_CHANNEL_ID)) { + colorValue.update((HSBType) state); + listener.updateChannelState(primaryChannelUID, colorValue.getChannelState()); } + // else rgbw channel, rgbww channel } } diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/DeviceTrigger.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/DeviceTrigger.java index 91ea0456b65a4..bd6beb4075391 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/DeviceTrigger.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/DeviceTrigger.java @@ -46,7 +46,7 @@ static class ChannelConfiguration extends AbstractChannelConfiguration { } public DeviceTrigger(ComponentFactory.ComponentConfiguration componentConfiguration, boolean newStyleChannels) { - super(componentConfiguration, ChannelConfiguration.class, newStyleChannels, true); + super(componentConfiguration, ChannelConfiguration.class, newStyleChannels); if (!"trigger".equals(channelConfiguration.automationType)) { throw new ConfigurationException("Component:DeviceTrigger must have automation_type 'trigger'"); @@ -69,5 +69,6 @@ public DeviceTrigger(ComponentFactory.ComponentConfiguration componentConfigurat buildChannel(channelConfiguration.type, ComponentChannelType.TRIGGER, value, getName(), componentConfiguration.getUpdateListener()) .stateTopic(channelConfiguration.topic, channelConfiguration.getValueTemplate()).trigger(true).build(); + finalizeChannels(); } } diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Fan.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Fan.java index 24f9d8ee16608..3c8ed4139aba0 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Fan.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Fan.java @@ -14,6 +14,7 @@ import java.math.BigDecimal; import java.util.List; +import java.util.Objects; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; @@ -118,6 +119,8 @@ static class ChannelConfiguration extends AbstractChannelConfiguration { private final PercentageValue speedValue; private State rawSpeedState; private final ComponentChannel onOffChannel; + private final @Nullable ComponentChannel speedChannel; + private final ComponentChannel primaryChannel; private final ChannelStateUpdateListener channelStateUpdateListener; public Fan(ComponentFactory.ComponentConfiguration componentConfiguration, boolean newStyleChannels) { @@ -144,11 +147,15 @@ public Fan(ComponentFactory.ComponentConfiguration componentConfiguration, boole if (channelConfiguration.percentageCommandTopic != null) { hiddenChannels.add(onOffChannel); - buildChannel(SPEED_CHANNEL_ID, ComponentChannelType.DIMMER, speedValue, "Speed", this) + primaryChannel = speedChannel = buildChannel(SPEED_CHANNEL_ID, ComponentChannelType.DIMMER, speedValue, + "Speed", this) .stateTopic(channelConfiguration.percentageStateTopic, channelConfiguration.percentageValueTemplate) .commandTopic(channelConfiguration.percentageCommandTopic, channelConfiguration.isRetain(), channelConfiguration.getQos(), channelConfiguration.percentageCommandTemplate) .commandFilter(this::handlePercentageCommand).build(); + } else { + primaryChannel = onOffChannel; + speedChannel = null; } List presetModes = channelConfiguration.presetModes; @@ -184,6 +191,7 @@ public Fan(ComponentFactory.ComponentConfiguration componentConfiguration, boole channelConfiguration.getQos(), channelConfiguration.directionCommandTemplate) .build(); } + finalizeChannels(); } private boolean handlePercentageCommand(Command command) { @@ -197,7 +205,7 @@ private boolean handlePercentageCommand(Command command) { @Override public void updateChannelState(ChannelUID channel, State state) { - if (channel.getIdWithoutGroup().equals(SWITCH_CHANNEL_ID)) { + if (onOffChannel.getChannel().getUID().equals(channel)) { if (rawSpeedState instanceof UnDefType && state.equals(OnOffType.ON)) { // Assume full on if we don't yet know the actual speed state = PercentType.HUNDRED; @@ -206,7 +214,7 @@ public void updateChannelState(ChannelUID channel, State state) { } else { state = rawSpeedState; } - } else if (channel.getIdWithoutGroup().equals(SPEED_CHANNEL_ID)) { + } else if (Objects.requireNonNull(speedChannel).getChannel().getUID().equals(channel)) { rawSpeedState = state; if (onOffValue.getChannelState().equals(OnOffType.OFF)) { // Don't pass on percentage values while the fan is off @@ -214,7 +222,7 @@ public void updateChannelState(ChannelUID channel, State state) { } } speedValue.update(state); - channelStateUpdateListener.updateChannelState(buildChannelUID(SPEED_CHANNEL_ID), state); + channelStateUpdateListener.updateChannelState(primaryChannel.getChannel().getUID(), state); } @Override diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/JSONSchemaLight.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/JSONSchemaLight.java index ac218c337a86d..006031478c007 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/JSONSchemaLight.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/JSONSchemaLight.java @@ -21,6 +21,7 @@ import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.mqtt.generic.ChannelStateUpdateListener; import org.openhab.binding.mqtt.generic.values.TextValue; +import org.openhab.binding.mqtt.homeassistant.internal.ComponentChannel; import org.openhab.binding.mqtt.homeassistant.internal.ComponentChannelType; import org.openhab.core.library.types.DecimalType; import org.openhab.core.library.types.HSBType; @@ -77,6 +78,7 @@ public JSONSchemaLight(ComponentFactory.ComponentConfiguration builder, boolean @Override protected void buildChannels() { + boolean hasColorChannel = false; List supportedColorModes = channelConfiguration.supportedColorModes; if (supportedColorModes != null) { if (LightColorMode.hasColorChannel(supportedColorModes)) { @@ -99,7 +101,7 @@ protected void buildChannels() { } if (hasColorChannel) { - buildChannel(COLOR_CHANNEL_ID, ComponentChannelType.COLOR, colorValue, "Color", this) + colorChannel = buildChannel(COLOR_CHANNEL_ID, ComponentChannelType.COLOR, colorValue, "Color", this) .commandTopic(DUMMY_TOPIC, true, 1).commandFilter(this::handleCommand).build(); } else if (channelConfiguration.brightness) { brightnessChannel = buildChannel(BRIGHTNESS_CHANNEL_ID, ComponentChannelType.DIMMER, brightnessValue, @@ -144,7 +146,7 @@ protected void publishState(HSBType state) { .divide(new BigDecimal(100), MathContext.DECIMAL128).intValue(); } - if (hasColorChannel) { + if (colorChannel != null) { json.color = new JSONState.Color(); if (channelConfiguration.supportedColorModes.contains(LightColorMode.COLOR_MODE_HS)) { json.color.h = state.getHue().toBigDecimal(); @@ -318,12 +320,15 @@ public void updateChannelState(ChannelUID channel, State state) { listener.updateChannelState(buildChannelUID(COLOR_MODE_CHANNEL_ID), colorModeValue.getChannelState()); - if (hasColorChannel) { - listener.updateChannelState(buildChannelUID(COLOR_CHANNEL_ID), colorValue.getChannelState()); - } else if (brightnessChannel != null) { - listener.updateChannelState(buildChannelUID(BRIGHTNESS_CHANNEL_ID), brightnessValue.getChannelState()); + ComponentChannel localBrightnessChannel = brightnessChannel; + ComponentChannel localColorChannel = colorChannel; + if (localColorChannel != null) { + listener.updateChannelState(localColorChannel.getChannel().getUID(), colorValue.getChannelState()); + } else if (localBrightnessChannel != null) { + listener.updateChannelState(localBrightnessChannel.getChannel().getUID(), + brightnessValue.getChannelState()); } else { - listener.updateChannelState(buildChannelUID(ON_OFF_CHANNEL_ID), onOffValue.getChannelState()); + listener.updateChannelState(onOffChannel.getChannel().getUID(), onOffValue.getChannelState()); } } } diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Light.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Light.java index ca47f7e3df3ee..46f26d530f757 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Light.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Light.java @@ -228,10 +228,10 @@ static class ChannelConfiguration extends AbstractChannelConfiguration { } protected final boolean optimistic; - protected boolean hasColorChannel = false; protected @Nullable ComponentChannel onOffChannel; protected @Nullable ComponentChannel brightnessChannel; + protected @Nullable ComponentChannel colorChannel; // State has to be stored here, in order to mux multiple // MQTT sources into single OpenHAB channels @@ -292,6 +292,7 @@ protected Light(ComponentFactory.ComponentConfiguration builder, boolean newStyl colorTempValue = new NumberValue(min, max, BigDecimal.ONE, Units.MIRED); buildChannels(); + finalizeChannels(); } protected abstract void buildChannels(); diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Lock.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Lock.java index ce3317256a620..bce7cef22889b 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Lock.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Lock.java @@ -121,6 +121,7 @@ public Lock(ComponentFactory.ComponentConfiguration componentConfiguration, bool } return true; }).build(); + finalizeChannels(); } private void autoUpdate(boolean locking) { diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Number.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Number.java index 1d03661f880e7..2912ac9b7cc37 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Number.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Number.java @@ -71,7 +71,7 @@ static class ChannelConfiguration extends AbstractChannelConfiguration { } public Number(ComponentFactory.ComponentConfiguration componentConfiguration, boolean newStyleChannels) { - super(componentConfiguration, ChannelConfiguration.class, newStyleChannels, true); + super(componentConfiguration, ChannelConfiguration.class, newStyleChannels); boolean optimistic = channelConfiguration.optimistic != null ? channelConfiguration.optimistic : channelConfiguration.stateTopic.isBlank(); @@ -89,5 +89,6 @@ public Number(ComponentFactory.ComponentConfiguration componentConfiguration, bo .commandTopic(channelConfiguration.commandTopic, channelConfiguration.isRetain(), channelConfiguration.getQos(), channelConfiguration.commandTemplate) .build(); + finalizeChannels(); } } diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Scene.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Scene.java index d6e66a2bcb258..0ca4f1ab209c9 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Scene.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Scene.java @@ -46,7 +46,7 @@ static class ChannelConfiguration extends AbstractChannelConfiguration { } public Scene(ComponentFactory.ComponentConfiguration componentConfiguration, boolean newStyleChannels) { - super(componentConfiguration, ChannelConfiguration.class, newStyleChannels, true); + super(componentConfiguration, ChannelConfiguration.class, newStyleChannels); TextValue value = new TextValue(new String[] { channelConfiguration.payloadOn }); @@ -55,5 +55,6 @@ public Scene(ComponentFactory.ComponentConfiguration componentConfiguration, boo .commandTopic(channelConfiguration.commandTopic, channelConfiguration.isRetain(), channelConfiguration.getQos()) .withAutoUpdatePolicy(AutoUpdatePolicy.VETO).build(); + finalizeChannels(); } } diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Select.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Select.java index b3ceccafe67c3..ad650c63117b0 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Select.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Select.java @@ -56,7 +56,7 @@ static class ChannelConfiguration extends AbstractChannelConfiguration { } public Select(ComponentFactory.ComponentConfiguration componentConfiguration, boolean newStyleChannels) { - super(componentConfiguration, ChannelConfiguration.class, newStyleChannels, true); + super(componentConfiguration, ChannelConfiguration.class, newStyleChannels); boolean optimistic = channelConfiguration.optimistic != null ? channelConfiguration.optimistic : channelConfiguration.stateTopic.isBlank(); @@ -73,5 +73,6 @@ public Select(ComponentFactory.ComponentConfiguration componentConfiguration, bo .commandTopic(channelConfiguration.commandTopic, channelConfiguration.isRetain(), channelConfiguration.getQos(), channelConfiguration.commandTemplate) .build(); + finalizeChannels(); } } diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Sensor.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Sensor.java index 9472490ca68c0..2320019b871d8 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Sensor.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Sensor.java @@ -69,7 +69,7 @@ static class ChannelConfiguration extends AbstractChannelConfiguration { } public Sensor(ComponentFactory.ComponentConfiguration componentConfiguration, boolean newStyleChannels) { - super(componentConfiguration, ChannelConfiguration.class, newStyleChannels, true); + super(componentConfiguration, ChannelConfiguration.class, newStyleChannels); Value value; String uom = channelConfiguration.unitOfMeasurement; @@ -96,6 +96,7 @@ public Sensor(ComponentFactory.ComponentConfiguration componentConfiguration, bo buildChannel(SENSOR_CHANNEL_ID, type, value, getName(), getListener(componentConfiguration, value)) .stateTopic(channelConfiguration.stateTopic, channelConfiguration.getValueTemplate())// .trigger(trigger).build(); + finalizeChannels(); } private ChannelStateUpdateListener getListener(ComponentFactory.ComponentConfiguration componentConfiguration, diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Switch.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Switch.java index b1288c151d374..0027ffe1c77ef 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Switch.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Switch.java @@ -61,7 +61,7 @@ static class ChannelConfiguration extends AbstractChannelConfiguration { } public Switch(ComponentFactory.ComponentConfiguration componentConfiguration, boolean newStyleChannels) { - super(componentConfiguration, ChannelConfiguration.class, newStyleChannels, true); + super(componentConfiguration, ChannelConfiguration.class, newStyleChannels); boolean optimistic = channelConfiguration.optimistic != null ? channelConfiguration.optimistic : channelConfiguration.stateTopic.isBlank(); @@ -79,5 +79,6 @@ public Switch(ComponentFactory.ComponentConfiguration componentConfiguration, bo .commandTopic(channelConfiguration.commandTopic, channelConfiguration.isRetain(), channelConfiguration.getQos()) .build(); + finalizeChannels(); } } diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/TemplateSchemaLight.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/TemplateSchemaLight.java index 89e46bd6295a9..3a056cb36f8a2 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/TemplateSchemaLight.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/TemplateSchemaLight.java @@ -23,6 +23,7 @@ import org.openhab.binding.mqtt.generic.values.OnOffValue; import org.openhab.binding.mqtt.generic.values.PercentageValue; import org.openhab.binding.mqtt.generic.values.TextValue; +import org.openhab.binding.mqtt.homeassistant.internal.ComponentChannel; import org.openhab.binding.mqtt.homeassistant.internal.ComponentChannelType; import org.openhab.binding.mqtt.homeassistant.internal.HomeAssistantChannelTransformation; import org.openhab.binding.mqtt.homeassistant.internal.exception.UnsupportedComponentException; @@ -85,8 +86,7 @@ protected void buildChannels() { if (channelConfiguration.redTemplate != null && channelConfiguration.greenTemplate != null && channelConfiguration.blueTemplate != null) { - hasColorChannel = true; - buildChannel(COLOR_CHANNEL_ID, ComponentChannelType.COLOR, colorValue, "Color", this) + colorChannel = buildChannel(COLOR_CHANNEL_ID, ComponentChannelType.COLOR, colorValue, "Color", this) .commandTopic(DUMMY_TOPIC, true, 1).commandFilter(command -> handleCommand(command)).build(); } else if (channelConfiguration.brightnessTemplate != null) { brightnessChannel = buildChannel(BRIGHTNESS_CHANNEL_ID, ComponentChannelType.DIMMER, brightnessValue, @@ -127,7 +127,7 @@ protected void publishState(HSBType state) { binding.put(TemplateVariables.BRIGHTNESS, state.getBrightness().toBigDecimal().multiply(factor).intValue()); } - if (hasColorChannel) { + if (colorChannel != null) { int[] rgb = ColorUtil.hsbToRgb(state); binding.put(TemplateVariables.RED, rgb[0]); binding.put(TemplateVariables.GREEN, rgb[1]); @@ -249,13 +249,15 @@ public void updateChannelState(ChannelUID channel, State state) { colorValue.update(HSBType.fromRGB(red, green, blue)); } } - - if (hasColorChannel) { - listener.updateChannelState(buildChannelUID(COLOR_CHANNEL_ID), colorValue.getChannelState()); - } else if (brightnessChannel != null) { - listener.updateChannelState(buildChannelUID(BRIGHTNESS_CHANNEL_ID), brightnessValue.getChannelState()); + ComponentChannel localBrightnessChannel = brightnessChannel; + ComponentChannel localColorChannel = colorChannel; + if (localColorChannel != null) { + listener.updateChannelState(localColorChannel.getChannel().getUID(), colorValue.getChannelState()); + } else if (localBrightnessChannel != null) { + listener.updateChannelState(localBrightnessChannel.getChannel().getUID(), + brightnessValue.getChannelState()); } else { - listener.updateChannelState(buildChannelUID(ON_OFF_CHANNEL_ID), onOffValue.getChannelState()); + listener.updateChannelState(onOffChannel.getChannel().getUID(), onOffValue.getChannelState()); } template = channelConfiguration.effectTemplate; diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Vacuum.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Vacuum.java index 0c5185267230a..5862255f5ceaa 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Vacuum.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Vacuum.java @@ -290,6 +290,7 @@ public Vacuum(ComponentFactory.ComponentConfiguration componentConfiguration, bo buildOptionalChannel(JSON_ATTRIBUTES_CH_ID, ComponentChannelType.STRING, new TextValue(), updateListener, null, null, channelConfiguration.jsonAttributesTemplate, channelConfiguration.jsonAttributesTopic); + finalizeChannels(); } @Nullable diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/handler/HomeAssistantThingHandler.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/handler/HomeAssistantThingHandler.java index 1bfb1801214f0..1143a8e669061 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/handler/HomeAssistantThingHandler.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/handler/HomeAssistantThingHandler.java @@ -33,7 +33,6 @@ import org.openhab.binding.mqtt.generic.tools.DelayedBatchProcessing; import org.openhab.binding.mqtt.generic.utils.FutureCollector; import org.openhab.binding.mqtt.homeassistant.generic.internal.MqttBindingConstants; -import org.openhab.binding.mqtt.homeassistant.internal.ComponentChannel; import org.openhab.binding.mqtt.homeassistant.internal.DiscoverComponents; import org.openhab.binding.mqtt.homeassistant.internal.DiscoverComponents.ComponentDiscovered; import org.openhab.binding.mqtt.homeassistant.internal.HaID; @@ -43,6 +42,7 @@ import org.openhab.binding.mqtt.homeassistant.internal.component.Update; import org.openhab.binding.mqtt.homeassistant.internal.config.ChannelConfigurationTypeAdapterFactory; import org.openhab.binding.mqtt.homeassistant.internal.exception.ConfigurationException; +import org.openhab.core.config.core.Configuration; import org.openhab.core.config.core.validation.ConfigValidationException; import org.openhab.core.io.transport.mqtt.MqttBrokerConnection; import org.openhab.core.thing.Channel; @@ -98,6 +98,8 @@ public class HomeAssistantThingHandler extends AbstractMQTTThingHandler private final Gson gson; protected final Map<@Nullable String, AbstractComponent> haComponents = new HashMap<>(); + protected final Map<@Nullable String, AbstractComponent> haComponentsByUniqueId = new HashMap<>(); + protected final Map channelStates = new HashMap<>(); protected HandlerConfiguration config = new HandlerConfiguration(); private Set discoveryHomeAssistantIDs = new HashSet<>(); @@ -147,13 +149,21 @@ public void initialize() { ThingTypeUID typeID = getThing().getThingTypeUID(); for (Channel channel : thing.getChannels()) { final String groupID = channel.getUID().getGroupId(); - // Already restored component? - @Nullable - AbstractComponent component = haComponents.get(groupID); - if (component != null) { + if (groupID != null) { + // Already restored component via another channel in the component? + AbstractComponent component = haComponents.get(groupID); + if (component != null) { + continue; + } + } + Configuration channelConfig = channel.getConfiguration(); + if (!channelConfig.containsKey("component") + || !channelConfig.containsKey("objectid") | !channelConfig.containsKey("config")) { + // Must be a secondary channel continue; } - HaID haID = HaID.fromConfig(config.basetopic, channel.getConfiguration()); + + HaID haID = HaID.fromConfig(config.basetopic, channelConfig); if (!config.topics.contains(haID.getTopic())) { // don't add a component for this channel that isn't configured on the thing @@ -164,21 +174,17 @@ public void initialize() { discoveryHomeAssistantIDs.add(haID); ThingUID thingUID = channel.getUID().getThingUID(); - String channelConfigurationJSON = (String) channel.getConfiguration().get("config"); - if (channelConfigurationJSON == null) { - logger.warn("Provided channel does not have a 'config' configuration key!"); - } else { - try { - component = ComponentFactory.createComponent(thingUID, haID, channelConfigurationJSON, this, this, - scheduler, gson, jinjava, newStyleChannels); - if (typeID.equals(MqttBindingConstants.HOMEASSISTANT_MQTT_THING)) { - typeID = calculateThingTypeUID(component); - } - - haComponents.put(component.getGroupId(), component); - } catch (ConfigurationException e) { - logger.error("Cannot restore component {}: {}", thing, e.getMessage()); + String channelConfigurationJSON = (String) channelConfig.get("config"); + try { + AbstractComponent component = ComponentFactory.createComponent(thingUID, haID, + channelConfigurationJSON, this, this, scheduler, gson, jinjava, newStyleChannels); + if (typeID.equals(MqttBindingConstants.HOMEASSISTANT_MQTT_THING)) { + typeID = calculateThingTypeUID(component); } + + addComponent(component); + } catch (ConfigurationException e) { + logger.warn("Cannot restore component {}: {}", thing, e.getMessage()); } } if (updateThingType(typeID)) { @@ -241,27 +247,9 @@ protected void stop() { @Override public @Nullable ChannelState getChannelState(ChannelUID channelUID) { - String componentId; - if (channelUID.isInGroup()) { - componentId = channelUID.getGroupId(); - } else { - componentId = channelUID.getId(); - } - AbstractComponent component; synchronized (haComponents) { // sync whenever discoverComponents is started - component = haComponents.get(componentId); - } - if (component == null) { - component = haComponents.get(""); - if (component == null) { - return null; - } - } - ComponentChannel componentChannel = component.getChannel(channelUID.getIdWithoutGroup()); - if (componentChannel == null) { - return null; + return channelStates.get(channelUID); } - return componentChannel.getState(); } /** @@ -289,14 +277,18 @@ public void accept(List> discoveredComponentsList) { if (typeID.equals(MqttBindingConstants.HOMEASSISTANT_MQTT_THING)) { typeID = calculateThingTypeUID(discovered); } - String id = discovered.getGroupId(); - AbstractComponent known = haComponents.get(id); + AbstractComponent known = haComponentsByUniqueId.get(discovered.getUniqueId()); // Is component already known? if (known != null) { if (discovered.getConfigHash() != known.getConfigHash()) { // Don't wait for the future to complete. We are also not interested in failures. // The component will be replaced in a moment. known.stop(); + haComponentsByUniqueId.remove(discovered.getUniqueId()); + haComponents.remove(known.getComponentId()); + if (!known.getComponentId().equals(discovered.getComponentId())) { + discovered.resolveConflict(); + } } else { known.setConfigSeen(); continue; @@ -304,7 +296,7 @@ public void accept(List> discoveredComponentsList) { } // Add component to the component map - haComponents.put(id, discovered); + addComponent(discovered); // Start component / Subscribe to channel topics discovered.start(connection, scheduler, 0).exceptionally(e -> { logger.warn("Failed to start component {}", discovered.getHaID(), e); @@ -392,6 +384,9 @@ private boolean updateThingType(ThingTypeUID typeID) { sortedComponents.stream().map(AbstractComponent::getChannels).flatMap(List::stream) .forEach(c -> thingBuilder.withChannel(c)); + channelStates.clear(); + sortedComponents.forEach(c -> c.getChannelStates(channelStates)); + updateThing(thingBuilder.build()); } } @@ -422,4 +417,18 @@ private void releaseStateUpdated(Update.ReleaseState state) { properties = state.appendToProperties(properties); updateProperties(properties); } + + // should only be called when it's safe to access haComponents + private void addComponent(AbstractComponent component) { + AbstractComponent existing = haComponents.get(component.getComponentId()); + if (existing != null) { + // rename the conflict + haComponents.remove(existing.getComponentId()); + existing.resolveConflict(); + component.resolveConflict(); + haComponents.put(existing.getComponentId(), existing); + } + haComponents.put(component.getComponentId(), component); + haComponentsByUniqueId.put(component.getUniqueId(), component); + } } diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/resources/OH-INF/config/homeassistant-channel-config.xml b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/resources/OH-INF/config/homeassistant-channel-config.xml index d84743bc7331d..d3092a82b11af 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/resources/OH-INF/config/homeassistant-channel-config.xml +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/resources/OH-INF/config/homeassistant-channel-config.xml @@ -5,7 +5,7 @@ xsi:schemaLocation="https://openhab.org/schemas/config-description/v1.0.0 https://openhab.org/schemas/config-description-1.0.0.xsd"> - + Home Assistant component type (e.g. binary_sensor, switch, light) @@ -15,12 +15,12 @@ Optional node name of the component - + Object ID of the component - + The JSON configuration string received by the component via MQTT. diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/BinarySensorTests.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/BinarySensorTests.java index 6c875e74b0cb7..d11f1f82f4775 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/BinarySensorTests.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/BinarySensorTests.java @@ -65,7 +65,7 @@ public void test() throws InterruptedException { assertThat(component.channels.size(), is(1)); assertThat(component.getName(), is("onoffsensor")); - assertThat(component.getGroupId(), is("sn1")); + assertThat(component.getComponentId(), is("sn1")); assertChannel(component, BinarySensor.SENSOR_CHANNEL_ID, "zigbee2mqtt/sensor/state", "", "onoffsensor", OnOffValue.class); diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/SensorTests.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/SensorTests.java index c2abf74b39c07..d712ee20317b3 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/SensorTests.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/SensorTests.java @@ -65,7 +65,7 @@ public void test() throws InterruptedException { assertThat(component.channels.size(), is(1)); assertThat(component.getName(), is("sensor1")); - assertThat(component.getGroupId(), is("sn1")); + assertThat(component.getComponentId(), is("sn1")); assertChannel(component, Sensor.SENSOR_CHANNEL_ID, "zigbee2mqtt/sensor/state", "", "sensor1", NumberValue.class); diff --git a/itests/org.openhab.binding.mqtt.homeassistant.tests/src/main/java/org/openhab/binding/mqtt/homeassistant/HomeAssistantMQTTImplementationTest.java b/itests/org.openhab.binding.mqtt.homeassistant.tests/src/main/java/org/openhab/binding/mqtt/homeassistant/HomeAssistantMQTTImplementationTest.java index 2f273661d3be1..64781741ea182 100644 --- a/itests/org.openhab.binding.mqtt.homeassistant.tests/src/main/java/org/openhab/binding/mqtt/homeassistant/HomeAssistantMQTTImplementationTest.java +++ b/itests/org.openhab.binding.mqtt.homeassistant.tests/src/main/java/org/openhab/binding/mqtt/homeassistant/HomeAssistantMQTTImplementationTest.java @@ -155,7 +155,7 @@ public void parseHATree() throws Exception { // and add the types to the channelTypeProvider, like in the real Thing handler. final CountDownLatch latch = new CountDownLatch(1); ComponentDiscovered cd = (haID, c) -> { - haComponents.put(c.getGroupId(), c); + haComponents.put(c.getComponentId(), c); latch.countDown(); }; @@ -174,11 +174,10 @@ public void parseHATree() throws Exception { assertNull(failure); assertThat(haComponents.size(), is(1)); - String channelGroupId = "switch_" + ThingChannelConstants.TEST_HOME_ASSISTANT_THING.getId(); + String componentId = ThingChannelConstants.TEST_HOME_ASSISTANT_THING.getId(); String channelId = Switch.SWITCH_CHANNEL_ID; - State value = haComponents.get(channelGroupId).getChannel(channelGroupId).getState().getCache() - .getChannelState(); + State value = haComponents.get(componentId).getChannel(channelId).getState().getCache().getChannelState(); assertThat(value, is(UnDefType.UNDEF)); haComponents.values().stream().map(e -> e.start(haConnection, scheduler, 100)) @@ -191,7 +190,7 @@ public void parseHATree() throws Exception { verify(channelStateUpdateListener, timeout(4000).times(1)).updateChannelState(any(), any()); // Value should be ON now. - value = haComponents.get(channelGroupId).getChannel(channelGroupId).getState().getCache().getChannelState(); + value = haComponents.get(componentId).getChannel(channelId).getState().getCache().getChannelState(); assertThat(value, is(OnOffType.ON)); } }