mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-01-10 07:02:02 +01:00
[mqtt.homeassistant] fix newStyleChannels (#17491)
* [mqtt.homeassistant] fix newStyleChannels * further simplify channel IDs Signed-off-by: Cody Cutrer <cody@cutrer.us>
This commit is contained in:
parent
90442a3864
commit
31f6cda174
@ -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 class ChannelState implements MqttMessageSubscriber {
|
||||
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
|
||||
*
|
||||
|
@ -58,7 +58,7 @@ import org.openhab.core.types.StateDescription;
|
||||
@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 class ComponentChannel {
|
||||
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;
|
||||
}
|
||||
|
@ -24,6 +24,7 @@ import java.util.stream.Stream;
|
||||
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.AvailabilityMo
|
||||
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<C extends AbstractChannelConfiguration>
|
||||
|
||||
// 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<C extends AbstractChannelConfiguration>
|
||||
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<C> 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 abstract class AbstractComponent<C extends AbstractChannelConfiguration>
|
||||
* (only if newStyleChannels is true)
|
||||
*/
|
||||
public AbstractComponent(ComponentFactory.ComponentConfiguration componentConfiguration, Class<C> 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 abstract class AbstractComponent<C extends AbstractChannelConfiguration>
|
||||
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 abstract class AbstractComponent<C extends AbstractChannelConfiguration>
|
||||
}
|
||||
}
|
||||
|
||||
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 abstract class AbstractComponent<C extends AbstractChannelConfiguration>
|
||||
}
|
||||
|
||||
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 abstract class AbstractComponent<C extends AbstractChannelConfiguration>
|
||||
* 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 abstract class AbstractComponent<C extends AbstractChannelConfiguration>
|
||||
}
|
||||
|
||||
public List<ChannelDefinition> getChannelDefinitions() {
|
||||
if (channelGroupUID != null) {
|
||||
if (groupId != null) {
|
||||
return List.of();
|
||||
}
|
||||
return getAllChannelDefinitions();
|
||||
@ -295,6 +316,10 @@ public abstract class AbstractComponent<C extends AbstractChannelConfiguration>
|
||||
return channels.values().stream().map(ComponentChannel::getChannel).toList();
|
||||
}
|
||||
|
||||
public void getChannelStates(Map<ChannelUID, ChannelState> 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 abstract class AbstractComponent<C extends AbstractChannelConfiguration>
|
||||
* 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() {
|
||||
|
@ -97,5 +97,6 @@ public class AlarmControlPanel extends AbstractComponent<AlarmControlPanel.Chann
|
||||
componentConfiguration.getUpdateListener())
|
||||
.commandTopic(commandTopic, channelConfiguration.isRetain(), channelConfiguration.getQos()).build();
|
||||
}
|
||||
finalizeChannels();
|
||||
}
|
||||
}
|
||||
|
@ -69,7 +69,7 @@ public class BinarySensor extends AbstractComponent<BinarySensor.ChannelConfigur
|
||||
}
|
||||
|
||||
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 class BinarySensor extends AbstractComponent<BinarySensor.ChannelConfigur
|
||||
getListener(componentConfiguration, value))
|
||||
.stateTopic(channelConfiguration.stateTopic, channelConfiguration.getValueTemplate())
|
||||
.withAutoUpdatePolicy(AutoUpdatePolicy.VETO).build();
|
||||
finalizeChannels();
|
||||
}
|
||||
|
||||
private ChannelStateUpdateListener getListener(ComponentFactory.ComponentConfiguration componentConfiguration,
|
||||
|
@ -48,7 +48,7 @@ public class Button extends AbstractComponent<Button.ChannelConfiguration> {
|
||||
}
|
||||
|
||||
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 class Button extends AbstractComponent<Button.ChannelConfiguration> {
|
||||
.commandTopic(channelConfiguration.commandTopic, channelConfiguration.isRetain(),
|
||||
channelConfiguration.getQos())
|
||||
.withAutoUpdatePolicy(AutoUpdatePolicy.VETO).build();
|
||||
finalizeChannels();
|
||||
}
|
||||
}
|
||||
|
@ -46,5 +46,6 @@ public class Camera extends AbstractComponent<Camera.ChannelConfiguration> {
|
||||
|
||||
buildChannel(CAMERA_CHANNEL_ID, ComponentChannelType.IMAGE, value, getName(),
|
||||
componentConfiguration.getUpdateListener()).stateTopic(channelConfiguration.topic).build();
|
||||
finalizeChannels();
|
||||
}
|
||||
}
|
||||
|
@ -284,6 +284,7 @@ public class Climate extends AbstractComponent<Climate.ChannelConfiguration> {
|
||||
|
||||
buildOptionalChannel(POWER_CH_ID, ComponentChannelType.SWITCH, new OnOffValue(), updateListener, null,
|
||||
channelConfiguration.powerCommandTopic, null, null, null);
|
||||
finalizeChannels();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
|
@ -150,5 +150,6 @@ public class Cover extends AbstractComponent<Cover.ChannelConfiguration> {
|
||||
}
|
||||
return true;
|
||||
}).build();
|
||||
finalizeChannels();
|
||||
}
|
||||
}
|
||||
|
@ -112,6 +112,7 @@ public class DefaultSchemaLight extends Light {
|
||||
.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 @@ public class DefaultSchemaLight extends Light {
|
||||
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 @@ public class DefaultSchemaLight extends Light {
|
||||
@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");
|
||||
}
|
||||
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(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;
|
||||
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);
|
||||
}
|
||||
|
||||
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 {
|
||||
colorValue.update(new HSBType(DecimalType.ZERO, PercentType.ZERO,
|
||||
(PercentType) brightnessValue.getChannelState()));
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
|
@ -46,7 +46,7 @@ public class DeviceTrigger extends AbstractComponent<DeviceTrigger.ChannelConfig
|
||||
}
|
||||
|
||||
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 class DeviceTrigger extends AbstractComponent<DeviceTrigger.ChannelConfig
|
||||
buildChannel(channelConfiguration.type, ComponentChannelType.TRIGGER, value, getName(),
|
||||
componentConfiguration.getUpdateListener())
|
||||
.stateTopic(channelConfiguration.topic, channelConfiguration.getValueTemplate()).trigger(true).build();
|
||||
finalizeChannels();
|
||||
}
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ package org.openhab.binding.mqtt.homeassistant.internal.component;
|
||||
|
||||
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 @@ public class Fan extends AbstractComponent<Fan.ChannelConfiguration> implements
|
||||
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 class Fan extends AbstractComponent<Fan.ChannelConfiguration> implements
|
||||
|
||||
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<String> presetModes = channelConfiguration.presetModes;
|
||||
@ -184,6 +191,7 @@ public class Fan extends AbstractComponent<Fan.ChannelConfiguration> implements
|
||||
channelConfiguration.getQos(), channelConfiguration.directionCommandTemplate)
|
||||
.build();
|
||||
}
|
||||
finalizeChannels();
|
||||
}
|
||||
|
||||
private boolean handlePercentageCommand(Command command) {
|
||||
@ -197,7 +205,7 @@ public class Fan extends AbstractComponent<Fan.ChannelConfiguration> implements
|
||||
|
||||
@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 class Fan extends AbstractComponent<Fan.ChannelConfiguration> implements
|
||||
} 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 class Fan extends AbstractComponent<Fan.ChannelConfiguration> implements
|
||||
}
|
||||
}
|
||||
speedValue.update(state);
|
||||
channelStateUpdateListener.updateChannelState(buildChannelUID(SPEED_CHANNEL_ID), state);
|
||||
channelStateUpdateListener.updateChannelState(primaryChannel.getChannel().getUID(), state);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -21,6 +21,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
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 class JSONSchemaLight extends AbstractRawSchemaLight {
|
||||
|
||||
@Override
|
||||
protected void buildChannels() {
|
||||
boolean hasColorChannel = false;
|
||||
List<LightColorMode> supportedColorModes = channelConfiguration.supportedColorModes;
|
||||
if (supportedColorModes != null) {
|
||||
if (LightColorMode.hasColorChannel(supportedColorModes)) {
|
||||
@ -99,7 +101,7 @@ public class JSONSchemaLight extends AbstractRawSchemaLight {
|
||||
}
|
||||
|
||||
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 @@ public class JSONSchemaLight extends AbstractRawSchemaLight {
|
||||
.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 class JSONSchemaLight extends AbstractRawSchemaLight {
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -228,10 +228,10 @@ public abstract class Light extends AbstractComponent<Light.ChannelConfiguration
|
||||
}
|
||||
|
||||
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 @@ public abstract class Light extends AbstractComponent<Light.ChannelConfiguration
|
||||
colorTempValue = new NumberValue(min, max, BigDecimal.ONE, Units.MIRED);
|
||||
|
||||
buildChannels();
|
||||
finalizeChannels();
|
||||
}
|
||||
|
||||
protected abstract void buildChannels();
|
||||
|
@ -121,6 +121,7 @@ public class Lock extends AbstractComponent<Lock.ChannelConfiguration> {
|
||||
}
|
||||
return true;
|
||||
}).build();
|
||||
finalizeChannels();
|
||||
}
|
||||
|
||||
private void autoUpdate(boolean locking) {
|
||||
|
@ -71,7 +71,7 @@ public class Number extends AbstractComponent<Number.ChannelConfiguration> {
|
||||
}
|
||||
|
||||
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 class Number extends AbstractComponent<Number.ChannelConfiguration> {
|
||||
.commandTopic(channelConfiguration.commandTopic, channelConfiguration.isRetain(),
|
||||
channelConfiguration.getQos(), channelConfiguration.commandTemplate)
|
||||
.build();
|
||||
finalizeChannels();
|
||||
}
|
||||
}
|
||||
|
@ -46,7 +46,7 @@ public class Scene extends AbstractComponent<Scene.ChannelConfiguration> {
|
||||
}
|
||||
|
||||
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 class Scene extends AbstractComponent<Scene.ChannelConfiguration> {
|
||||
.commandTopic(channelConfiguration.commandTopic, channelConfiguration.isRetain(),
|
||||
channelConfiguration.getQos())
|
||||
.withAutoUpdatePolicy(AutoUpdatePolicy.VETO).build();
|
||||
finalizeChannels();
|
||||
}
|
||||
}
|
||||
|
@ -56,7 +56,7 @@ public class Select extends AbstractComponent<Select.ChannelConfiguration> {
|
||||
}
|
||||
|
||||
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 class Select extends AbstractComponent<Select.ChannelConfiguration> {
|
||||
.commandTopic(channelConfiguration.commandTopic, channelConfiguration.isRetain(),
|
||||
channelConfiguration.getQos(), channelConfiguration.commandTemplate)
|
||||
.build();
|
||||
finalizeChannels();
|
||||
}
|
||||
}
|
||||
|
@ -69,7 +69,7 @@ public class Sensor extends AbstractComponent<Sensor.ChannelConfiguration> {
|
||||
}
|
||||
|
||||
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 class Sensor extends AbstractComponent<Sensor.ChannelConfiguration> {
|
||||
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,
|
||||
|
@ -61,7 +61,7 @@ public class Switch extends AbstractComponent<Switch.ChannelConfiguration> {
|
||||
}
|
||||
|
||||
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 class Switch extends AbstractComponent<Switch.ChannelConfiguration> {
|
||||
.commandTopic(channelConfiguration.commandTopic, channelConfiguration.isRetain(),
|
||||
channelConfiguration.getQos())
|
||||
.build();
|
||||
finalizeChannels();
|
||||
}
|
||||
}
|
||||
|
@ -23,6 +23,7 @@ import org.openhab.binding.mqtt.generic.ChannelStateUpdateListener;
|
||||
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 @@ public class TemplateSchemaLight extends AbstractRawSchemaLight {
|
||||
|
||||
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 @@ public class TemplateSchemaLight extends AbstractRawSchemaLight {
|
||||
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 class TemplateSchemaLight extends AbstractRawSchemaLight {
|
||||
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;
|
||||
|
@ -290,6 +290,7 @@ public class Vacuum extends AbstractComponent<Vacuum.ChannelConfiguration> {
|
||||
|
||||
buildOptionalChannel(JSON_ATTRIBUTES_CH_ID, ComponentChannelType.STRING, new TextValue(), updateListener, null,
|
||||
null, channelConfiguration.jsonAttributesTemplate, channelConfiguration.jsonAttributesTopic);
|
||||
finalizeChannels();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
|
@ -33,7 +33,6 @@ import org.openhab.binding.mqtt.generic.MqttChannelTypeProvider;
|
||||
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.ComponentFactor
|
||||
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<ChannelUID, ChannelState> channelStates = new HashMap<>();
|
||||
|
||||
protected HandlerConfiguration config = new HandlerConfiguration();
|
||||
private Set<HaID> discoveryHomeAssistantIDs = new HashSet<>();
|
||||
@ -147,13 +149,21 @@ public class HomeAssistantThingHandler extends AbstractMQTTThingHandler
|
||||
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 class HomeAssistantThingHandler extends AbstractMQTTThingHandler
|
||||
|
||||
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 @@ public class HomeAssistantThingHandler extends AbstractMQTTThingHandler
|
||||
|
||||
@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);
|
||||
return channelStates.get(channelUID);
|
||||
}
|
||||
if (component == null) {
|
||||
component = haComponents.get("");
|
||||
if (component == null) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
ComponentChannel componentChannel = component.getChannel(channelUID.getIdWithoutGroup());
|
||||
if (componentChannel == null) {
|
||||
return null;
|
||||
}
|
||||
return componentChannel.getState();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -289,14 +277,18 @@ public class HomeAssistantThingHandler extends AbstractMQTTThingHandler
|
||||
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 class HomeAssistantThingHandler extends AbstractMQTTThingHandler
|
||||
}
|
||||
|
||||
// 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 @@ public class HomeAssistantThingHandler extends AbstractMQTTThingHandler
|
||||
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 @@ public class HomeAssistantThingHandler extends AbstractMQTTThingHandler
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -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">
|
||||
|
||||
<config-description uri="channel-type:mqtt:ha-channel">
|
||||
<parameter name="component" type="text" readOnly="true" required="true">
|
||||
<parameter name="component" type="text" readOnly="true">
|
||||
<label>Component</label>
|
||||
<description>Home Assistant component type (e.g. binary_sensor, switch, light)</description>
|
||||
<default></default>
|
||||
@ -15,12 +15,12 @@
|
||||
<description>Optional node name of the component</description>
|
||||
<default></default>
|
||||
</parameter>
|
||||
<parameter name="objectid" type="text" readOnly="true" required="true">
|
||||
<parameter name="objectid" type="text" readOnly="true">
|
||||
<label>Object ID</label>
|
||||
<description>Object ID of the component</description>
|
||||
<default></default>
|
||||
</parameter>
|
||||
<parameter name="config" type="text" readOnly="true" required="true">
|
||||
<parameter name="config" type="text" readOnly="true">
|
||||
<label>JSON Configuration</label>
|
||||
<description>The JSON configuration string received by the component via MQTT.</description>
|
||||
<default></default>
|
||||
|
@ -65,7 +65,7 @@ public class BinarySensorTests extends AbstractComponentTests {
|
||||
|
||||
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);
|
||||
|
@ -65,7 +65,7 @@ public class SensorTests extends AbstractComponentTests {
|
||||
|
||||
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);
|
||||
|
@ -155,7 +155,7 @@ public class HomeAssistantMQTTImplementationTest extends MqttOSGiTest {
|
||||
// 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 class HomeAssistantMQTTImplementationTest extends MqttOSGiTest {
|
||||
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 class HomeAssistantMQTTImplementationTest extends MqttOSGiTest {
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user