// 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;
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
*
@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;
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;
}
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;
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;
// Component location fields
protected final ComponentConfiguration componentConfiguration;
- protected final @Nullable ChannelGroupUID channelGroupUID;
protected final HaID haID;
// Channels and configuration
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.
* (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);
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);
}
}
+ 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);
}
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;
}
/**
* 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())
}
public List<ChannelDefinition> getChannelDefinitions() {
- if (channelGroupUID != null) {
+ if (groupId != null) {
return List.of();
}
return getAllChannelDefinitions();
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.
* 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() {
componentConfiguration.getUpdateListener())
.commandTopic(commandTopic, channelConfiguration.isRetain(), channelConfiguration.getQos()).build();
}
+ finalizeChannels();
}
}
}
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);
getListener(componentConfiguration, value))
.stateTopic(channelConfiguration.stateTopic, channelConfiguration.getValueTemplate())
.withAutoUpdatePolicy(AutoUpdatePolicy.VETO).build();
+ finalizeChannels();
}
private ChannelStateUpdateListener getListener(ComponentFactory.ComponentConfiguration componentConfiguration,
}
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 });
.commandTopic(channelConfiguration.commandTopic, channelConfiguration.isRetain(),
channelConfiguration.getQos())
.withAutoUpdatePolicy(AutoUpdatePolicy.VETO).build();
+ finalizeChannels();
}
}
buildChannel(CAMERA_CHANNEL_ID, ComponentChannelType.IMAGE, value, getName(),
componentConfiguration.getUpdateListener()).stateTopic(channelConfiguration.topic).build();
+ finalizeChannels();
}
}
buildOptionalChannel(POWER_CH_ID, ComponentChannelType.SWITCH, new OnOffValue(), updateListener, null,
channelConfiguration.powerCommandTopic, null, null, null);
+ finalizeChannels();
}
@Nullable
}
return true;
}).build();
+ finalizeChannels();
}
}
.build();
}
+ boolean hasColorChannel = false;
if (channelConfiguration.rgbStateTopic != null || channelConfiguration.rgbCommandTopic != null) {
hasColorChannel = true;
hiddenChannels.add(rgbChannel = buildChannel(RGB_CHANNEL_ID, ComponentChannelType.COLOR,
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) {
@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
}
}
}
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'");
buildChannel(channelConfiguration.type, ComponentChannelType.TRIGGER, value, getName(),
componentConfiguration.getUpdateListener())
.stateTopic(channelConfiguration.topic, channelConfiguration.getValueTemplate()).trigger(true).build();
+ finalizeChannels();
}
}
import java.math.BigDecimal;
import java.util.List;
+import java.util.Objects;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
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) {
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;
channelConfiguration.getQos(), channelConfiguration.directionCommandTemplate)
.build();
}
+ finalizeChannels();
}
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;
} 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
}
}
speedValue.update(state);
- channelStateUpdateListener.updateChannelState(buildChannelUID(SPEED_CHANNEL_ID), state);
+ channelStateUpdateListener.updateChannelState(primaryChannel.getChannel().getUID(), state);
}
@Override
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;
@Override
protected void buildChannels() {
+ boolean hasColorChannel = false;
List<LightColorMode> supportedColorModes = channelConfiguration.supportedColorModes;
if (supportedColorModes != null) {
if (LightColorMode.hasColorChannel(supportedColorModes)) {
}
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,
.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();
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());
}
}
}
}
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
colorTempValue = new NumberValue(min, max, BigDecimal.ONE, Units.MIRED);
buildChannels();
+ finalizeChannels();
}
protected abstract void buildChannels();
}
return true;
}).build();
+ finalizeChannels();
}
private void autoUpdate(boolean locking) {
}
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();
.commandTopic(channelConfiguration.commandTopic, channelConfiguration.isRetain(),
channelConfiguration.getQos(), channelConfiguration.commandTemplate)
.build();
+ finalizeChannels();
}
}
}
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 });
.commandTopic(channelConfiguration.commandTopic, channelConfiguration.isRetain(),
channelConfiguration.getQos())
.withAutoUpdatePolicy(AutoUpdatePolicy.VETO).build();
+ finalizeChannels();
}
}
}
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();
.commandTopic(channelConfiguration.commandTopic, channelConfiguration.isRetain(),
channelConfiguration.getQos(), channelConfiguration.commandTemplate)
.build();
+ finalizeChannels();
}
}
}
public Sensor(ComponentFactory.ComponentConfiguration componentConfiguration, boolean newStyleChannels) {
- super(componentConfiguration, ChannelConfiguration.class, newStyleChannels, true);
+ super(componentConfiguration, ChannelConfiguration.class, newStyleChannels);
Value value;
String uom = channelConfiguration.unitOfMeasurement;
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,
}
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();
.commandTopic(channelConfiguration.commandTopic, channelConfiguration.isRetain(),
channelConfiguration.getQos())
.build();
+ finalizeChannels();
}
}
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;
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,
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]);
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;
buildOptionalChannel(JSON_ATTRIBUTES_CH_ID, ComponentChannelType.STRING, new TextValue(), updateListener, null,
null, channelConfiguration.jsonAttributesTemplate, channelConfiguration.jsonAttributesTopic);
+ finalizeChannels();
}
@Nullable
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;
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;
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<>();
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
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)) {
@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();
}
/**
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;
}
// 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);
sortedComponents.stream().map(AbstractComponent::getChannels).flatMap(List::stream)
.forEach(c -> thingBuilder.withChannel(c));
+ channelStates.clear();
+ sortedComponents.forEach(c -> c.getChannelStates(channelStates));
+
updateThing(thingBuilder.build());
}
}
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);
+ }
}
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>
<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>
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);
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);
// 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();
};
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))
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));
}
}