[mqtt.homeassistant] handle null component name (#15427)

* [mqtt.homeassistant] handle null component name

channels from such components will not have a group. this is
now done by zigbee2mqtt for the "default" component of a device,
such as the light. HASS encourages this as of release 2023.8

Signed-off-by: Cody Cutrer <cody@cutrer.us>
This commit is contained in:
Cody Cutrer 2023-11-18 14:46:57 -07:00 committed by GitHub
parent a535caa13c
commit 7fc351c9c2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 111 additions and 70 deletions

View File

@ -224,7 +224,7 @@ public class ComponentChannel {
ChannelType type; ChannelType type;
ChannelTypeUID channelTypeUID; ChannelTypeUID channelTypeUID;
channelUID = new ChannelUID(component.getGroupUID(), channelID); channelUID = component.buildChannelUID(channelID);
channelTypeUID = new ChannelTypeUID(MqttBindingConstants.BINDING_ID, channelTypeUID = new ChannelTypeUID(MqttBindingConstants.BINDING_ID,
channelUID.getGroupId() + "_" + channelID); channelUID.getGroupId() + "_" + channelID);
channelState = new HomeAssistantChannelState( channelState = new HomeAssistantChannelState(

View File

@ -14,6 +14,7 @@ package org.openhab.binding.mqtt.homeassistant.internal.component;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import java.util.TreeMap; import java.util.TreeMap;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledExecutorService;
@ -31,8 +32,10 @@ import org.openhab.binding.mqtt.homeassistant.internal.ComponentChannel;
import org.openhab.binding.mqtt.homeassistant.internal.HaID; import org.openhab.binding.mqtt.homeassistant.internal.HaID;
import org.openhab.binding.mqtt.homeassistant.internal.component.ComponentFactory.ComponentConfiguration; import org.openhab.binding.mqtt.homeassistant.internal.component.ComponentFactory.ComponentConfiguration;
import org.openhab.binding.mqtt.homeassistant.internal.config.dto.AbstractChannelConfiguration; import org.openhab.binding.mqtt.homeassistant.internal.config.dto.AbstractChannelConfiguration;
import org.openhab.binding.mqtt.homeassistant.internal.config.dto.Device;
import org.openhab.core.io.transport.mqtt.MqttBrokerConnection; import org.openhab.core.io.transport.mqtt.MqttBrokerConnection;
import org.openhab.core.thing.ChannelGroupUID; import org.openhab.core.thing.ChannelGroupUID;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.type.ChannelDefinition; import org.openhab.core.thing.type.ChannelDefinition;
import org.openhab.core.thing.type.ChannelGroupDefinition; import org.openhab.core.thing.type.ChannelGroupDefinition;
import org.openhab.core.thing.type.ChannelGroupType; import org.openhab.core.thing.type.ChannelGroupType;
@ -54,8 +57,8 @@ public abstract class AbstractComponent<C extends AbstractChannelConfiguration>
// Component location fields // Component location fields
private final ComponentConfiguration componentConfiguration; private final ComponentConfiguration componentConfiguration;
protected final ChannelGroupTypeUID channelGroupTypeUID; protected final @Nullable ChannelGroupTypeUID channelGroupTypeUID;
protected final ChannelGroupUID channelGroupUID; protected final @Nullable ChannelGroupUID channelGroupUID;
protected final HaID haID; protected final HaID haID;
// Channels and configuration // Channels and configuration
@ -83,10 +86,15 @@ public abstract class AbstractComponent<C extends AbstractChannelConfiguration>
this.haID = componentConfiguration.getHaID(); this.haID = componentConfiguration.getHaID();
String groupId = this.haID.getGroupId(channelConfiguration.getUniqueId()); if (channelConfiguration.getName() != null) {
String groupId = this.haID.getGroupId(channelConfiguration.getUniqueId());
this.channelGroupTypeUID = new ChannelGroupTypeUID(MqttBindingConstants.BINDING_ID, groupId); this.channelGroupTypeUID = new ChannelGroupTypeUID(MqttBindingConstants.BINDING_ID, groupId);
this.channelGroupUID = new ChannelGroupUID(componentConfiguration.getThingUID(), groupId); this.channelGroupUID = new ChannelGroupUID(componentConfiguration.getThingUID(), groupId);
} else {
this.channelGroupTypeUID = null;
this.channelGroupUID = null;
}
this.configSeen = false; this.configSeen = false;
@ -142,7 +150,10 @@ public abstract class AbstractComponent<C extends AbstractChannelConfiguration>
* @param channelTypeProvider The channel type provider * @param channelTypeProvider The channel type provider
*/ */
public void addChannelTypes(MqttChannelTypeProvider channelTypeProvider) { public void addChannelTypes(MqttChannelTypeProvider channelTypeProvider) {
channelTypeProvider.setChannelGroupType(getGroupTypeUID(), getType()); ChannelGroupTypeUID groupTypeUID = channelGroupTypeUID;
if (groupTypeUID != null) {
channelTypeProvider.setChannelGroupType(groupTypeUID, Objects.requireNonNull(getType()));
}
channels.values().forEach(v -> v.addChannelTypes(channelTypeProvider)); channels.values().forEach(v -> v.addChannelTypes(channelTypeProvider));
} }
@ -154,20 +165,31 @@ public abstract class AbstractComponent<C extends AbstractChannelConfiguration>
*/ */
public void removeChannelTypes(MqttChannelTypeProvider channelTypeProvider) { public void removeChannelTypes(MqttChannelTypeProvider channelTypeProvider) {
channels.values().forEach(v -> v.removeChannelTypes(channelTypeProvider)); channels.values().forEach(v -> v.removeChannelTypes(channelTypeProvider));
channelTypeProvider.removeChannelGroupType(getGroupTypeUID()); ChannelGroupTypeUID groupTypeUID = channelGroupTypeUID;
if (groupTypeUID != null) {
channelTypeProvider.removeChannelGroupType(groupTypeUID);
}
}
public ChannelUID buildChannelUID(String channelID) {
final ChannelGroupUID groupUID = channelGroupUID;
if (groupUID != null) {
return new ChannelUID(groupUID, channelID);
}
return new ChannelUID(componentConfiguration.getThingUID(), channelID);
} }
/** /**
* Each HomeAssistant component corresponds to a Channel Group Type. * Each HomeAssistant component corresponds to a Channel Group Type.
*/ */
public ChannelGroupTypeUID getGroupTypeUID() { public @Nullable ChannelGroupTypeUID getGroupTypeUID() {
return channelGroupTypeUID; return channelGroupTypeUID;
} }
/** /**
* The unique id of this component. * The unique id of this component.
*/ */
public ChannelGroupUID getGroupUID() { public @Nullable ChannelGroupUID getGroupUID() {
return channelGroupUID; return channelGroupUID;
} }
@ -175,7 +197,16 @@ public abstract class AbstractComponent<C extends AbstractChannelConfiguration>
* Component (Channel Group) name. * Component (Channel Group) name.
*/ */
public String getName() { public String getName() {
return channelConfiguration.getName(); String result = channelConfiguration.getName();
Device device = channelConfiguration.getDevice();
if (result == null && device != null) {
result = device.getName();
}
if (result == null) {
result = haID.objectID;
}
return result;
} }
/** /**
@ -207,11 +238,19 @@ public abstract class AbstractComponent<C extends AbstractChannelConfiguration>
/** /**
* Return the channel group type. * Return the channel group type.
*/ */
public ChannelGroupType getType() { public @Nullable ChannelGroupType getType() {
ChannelGroupTypeUID groupTypeUID = channelGroupTypeUID;
if (groupTypeUID == null) {
return null;
}
final List<ChannelDefinition> channelDefinitions = channels.values().stream().map(ComponentChannel::type) final List<ChannelDefinition> channelDefinitions = channels.values().stream().map(ComponentChannel::type)
.collect(Collectors.toList()); .collect(Collectors.toList());
return ChannelGroupTypeBuilder.instance(channelGroupTypeUID, getName()) return ChannelGroupTypeBuilder.instance(groupTypeUID, getName()).withChannelDefinitions(channelDefinitions)
.withChannelDefinitions(channelDefinitions).build(); .build();
}
public List<ChannelDefinition> getChannels() {
return channels.values().stream().map(ComponentChannel::type).collect(Collectors.toList());
} }
/** /**
@ -225,8 +264,12 @@ public abstract class AbstractComponent<C extends AbstractChannelConfiguration>
/** /**
* Return the channel group definition for this component. * Return the channel group definition for this component.
*/ */
public ChannelGroupDefinition getGroupDefinition() { public @Nullable ChannelGroupDefinition getGroupDefinition() {
return new ChannelGroupDefinition(channelGroupUID.getId(), getGroupTypeUID(), getName(), null); ChannelGroupTypeUID groupTypeUID = channelGroupTypeUID;
if (groupTypeUID == null) {
return null;
}
return new ChannelGroupDefinition(channelGroupUID.getId(), groupTypeUID, getName(), null);
} }
public HaID getHaID() { public HaID getHaID() {

View File

@ -74,24 +74,23 @@ public class AlarmControlPanel extends AbstractComponent<AlarmControlPanel.Chann
final String[] stateEnum = { channelConfiguration.stateDisarmed, channelConfiguration.stateArmedHome, final String[] stateEnum = { channelConfiguration.stateDisarmed, channelConfiguration.stateArmedHome,
channelConfiguration.stateArmedAway, channelConfiguration.statePending, channelConfiguration.stateArmedAway, channelConfiguration.statePending,
channelConfiguration.stateTriggered }; channelConfiguration.stateTriggered };
buildChannel(STATE_CHANNEL_ID, new TextValue(stateEnum), channelConfiguration.getName(), buildChannel(STATE_CHANNEL_ID, new TextValue(stateEnum), getName(), componentConfiguration.getUpdateListener())
componentConfiguration.getUpdateListener())
.stateTopic(channelConfiguration.stateTopic, channelConfiguration.getValueTemplate())// .stateTopic(channelConfiguration.stateTopic, channelConfiguration.getValueTemplate())//
.build(); .build();
String commandTopic = channelConfiguration.commandTopic; String commandTopic = channelConfiguration.commandTopic;
if (commandTopic != null) { if (commandTopic != null) {
buildChannel(SWITCH_DISARM_CHANNEL_ID, new TextValue(new String[] { channelConfiguration.payloadDisarm }), buildChannel(SWITCH_DISARM_CHANNEL_ID, new TextValue(new String[] { channelConfiguration.payloadDisarm }),
channelConfiguration.getName(), componentConfiguration.getUpdateListener()) getName(), componentConfiguration.getUpdateListener())
.commandTopic(commandTopic, channelConfiguration.isRetain(), channelConfiguration.getQos()).build(); .commandTopic(commandTopic, channelConfiguration.isRetain(), channelConfiguration.getQos()).build();
buildChannel(SWITCH_ARM_HOME_CHANNEL_ID, buildChannel(SWITCH_ARM_HOME_CHANNEL_ID,
new TextValue(new String[] { channelConfiguration.payloadArmHome }), channelConfiguration.getName(), new TextValue(new String[] { channelConfiguration.payloadArmHome }), getName(),
componentConfiguration.getUpdateListener()) componentConfiguration.getUpdateListener())
.commandTopic(commandTopic, channelConfiguration.isRetain(), channelConfiguration.getQos()).build(); .commandTopic(commandTopic, channelConfiguration.isRetain(), channelConfiguration.getQos()).build();
buildChannel(SWITCH_ARM_AWAY_CHANNEL_ID, buildChannel(SWITCH_ARM_AWAY_CHANNEL_ID,
new TextValue(new String[] { channelConfiguration.payloadArmAway }), channelConfiguration.getName(), new TextValue(new String[] { channelConfiguration.payloadArmAway }), getName(),
componentConfiguration.getUpdateListener()) componentConfiguration.getUpdateListener())
.commandTopic(commandTopic, channelConfiguration.isRetain(), channelConfiguration.getQos()).build(); .commandTopic(commandTopic, channelConfiguration.isRetain(), channelConfiguration.getQos()).build();
} }

View File

@ -43,7 +43,7 @@ public class Camera extends AbstractComponent<Camera.ChannelConfiguration> {
ImageValue value = new ImageValue(); ImageValue value = new ImageValue();
buildChannel(CAMERA_CHANNEL_ID, value, channelConfiguration.getName(), buildChannel(CAMERA_CHANNEL_ID, value, getName(), componentConfiguration.getUpdateListener())
componentConfiguration.getUpdateListener()).stateTopic(channelConfiguration.topic).build(); .stateTopic(channelConfiguration.topic).build();
} }
} }

View File

@ -286,7 +286,7 @@ public class Climate extends AbstractComponent<Climate.ChannelConfiguration> {
@Nullable String commandTopic, @Nullable String stateTemplate, @Nullable String stateTopic, @Nullable String commandTopic, @Nullable String stateTemplate, @Nullable String stateTopic,
@Nullable Predicate<Command> commandFilter) { @Nullable Predicate<Command> commandFilter) {
if ((commandTopic != null && !commandTopic.isBlank()) || (stateTopic != null && !stateTopic.isBlank())) { if ((commandTopic != null && !commandTopic.isBlank()) || (stateTopic != null && !stateTopic.isBlank())) {
return buildChannel(channelId, valueState, channelConfiguration.getName(), channelStateUpdateListener) return buildChannel(channelId, valueState, getName(), channelStateUpdateListener)
.stateTopic(stateTopic, stateTemplate, channelConfiguration.getValueTemplate()) .stateTopic(stateTopic, stateTemplate, channelConfiguration.getValueTemplate())
.commandTopic(commandTopic, channelConfiguration.isRetain(), channelConfiguration.getQos(), .commandTopic(commandTopic, channelConfiguration.isRetain(), channelConfiguration.getQos(),
commandTemplate) commandTemplate)

View File

@ -56,8 +56,7 @@ public class Cover extends AbstractComponent<Cover.ChannelConfiguration> {
RollershutterValue value = new RollershutterValue(channelConfiguration.payloadOpen, RollershutterValue value = new RollershutterValue(channelConfiguration.payloadOpen,
channelConfiguration.payloadClose, channelConfiguration.payloadStop); channelConfiguration.payloadClose, channelConfiguration.payloadStop);
buildChannel(SWITCH_CHANNEL_ID, value, channelConfiguration.getName(), buildChannel(SWITCH_CHANNEL_ID, value, getName(), componentConfiguration.getUpdateListener())
componentConfiguration.getUpdateListener())
.stateTopic(channelConfiguration.stateTopic, channelConfiguration.getValueTemplate()) .stateTopic(channelConfiguration.stateTopic, channelConfiguration.getValueTemplate())
.commandTopic(channelConfiguration.commandTopic, channelConfiguration.isRetain(), .commandTopic(channelConfiguration.commandTopic, channelConfiguration.isRetain(),
channelConfiguration.getQos()) channelConfiguration.getQos())

View File

@ -281,7 +281,7 @@ public class DefaultSchemaLight extends Light {
colorValue.update(newOnState); colorValue.update(newOnState);
} }
listener.updateChannelState(new ChannelUID(getGroupUID(), COLOR_CHANNEL_ID), listener.updateChannelState(buildChannelUID(COLOR_CHANNEL_ID),
state.equals(OnOffType.ON) ? newOnState : HSBType.BLACK); state.equals(OnOffType.ON) ? newOnState : HSBType.BLACK);
} else if (brightnessChannel != null) { } else if (brightnessChannel != null) {
listener.updateChannelState(new ChannelUID(channel.getThingUID(), BRIGHTNESS_CHANNEL_ID), listener.updateChannelState(new ChannelUID(channel.getThingUID(), BRIGHTNESS_CHANNEL_ID),
@ -301,8 +301,7 @@ public class DefaultSchemaLight extends Light {
colorValue.update(new HSBType(DecimalType.ZERO, PercentType.ZERO, colorValue.update(new HSBType(DecimalType.ZERO, PercentType.ZERO,
(PercentType) brightnessValue.getChannelState())); (PercentType) brightnessValue.getChannelState()));
} }
listener.updateChannelState(new ChannelUID(getGroupUID(), COLOR_CHANNEL_ID), listener.updateChannelState(buildChannelUID(COLOR_CHANNEL_ID), colorValue.getChannelState());
colorValue.getChannelState());
} else { } else {
listener.updateChannelState(channel, state); listener.updateChannelState(channel, state);
} }
@ -330,13 +329,11 @@ public class DefaultSchemaLight extends Light {
HSBType xyColor = HSBType.fromXY(x, y); HSBType xyColor = HSBType.fromXY(x, y);
colorValue.update(new HSBType(xyColor.getHue(), xyColor.getSaturation(), brightness)); colorValue.update(new HSBType(xyColor.getHue(), xyColor.getSaturation(), brightness));
} }
listener.updateChannelState(new ChannelUID(getGroupUID(), COLOR_CHANNEL_ID), listener.updateChannelState(buildChannelUID(COLOR_CHANNEL_ID), colorValue.getChannelState());
colorValue.getChannelState());
return; return;
case RGB_CHANNEL_ID: case RGB_CHANNEL_ID:
colorValue.update((HSBType) state); colorValue.update((HSBType) state);
listener.updateChannelState(new ChannelUID(getGroupUID(), COLOR_CHANNEL_ID), listener.updateChannelState(buildChannelUID(COLOR_CHANNEL_ID), colorValue.getChannelState());
colorValue.getChannelState());
break; break;
case RGBW_CHANNEL_ID: case RGBW_CHANNEL_ID:
case RGBWW_CHANNEL_ID: case RGBWW_CHANNEL_ID:

View File

@ -65,8 +65,7 @@ public class DeviceTrigger extends AbstractComponent<DeviceTrigger.ChannelConfig
value = new TextValue(); value = new TextValue();
} }
buildChannel(channelConfiguration.type, value, channelConfiguration.getName(), buildChannel(channelConfiguration.type, value, getName(), componentConfiguration.getUpdateListener())
componentConfiguration.getUpdateListener())
.stateTopic(channelConfiguration.topic, channelConfiguration.getValueTemplate()).trigger(true).build(); .stateTopic(channelConfiguration.topic, channelConfiguration.getValueTemplate()).trigger(true).build();
} }
} }

View File

@ -54,8 +54,7 @@ public class Fan extends AbstractComponent<Fan.ChannelConfiguration> {
super(componentConfiguration, ChannelConfiguration.class); super(componentConfiguration, ChannelConfiguration.class);
OnOffValue value = new OnOffValue(channelConfiguration.payloadOn, channelConfiguration.payloadOff); OnOffValue value = new OnOffValue(channelConfiguration.payloadOn, channelConfiguration.payloadOff);
buildChannel(SWITCH_CHANNEL_ID, value, channelConfiguration.getName(), buildChannel(SWITCH_CHANNEL_ID, value, getName(), componentConfiguration.getUpdateListener())
componentConfiguration.getUpdateListener())
.stateTopic(channelConfiguration.stateTopic, channelConfiguration.getValueTemplate()) .stateTopic(channelConfiguration.stateTopic, channelConfiguration.getValueTemplate())
.commandTopic(channelConfiguration.commandTopic, channelConfiguration.isRetain(), .commandTopic(channelConfiguration.commandTopic, channelConfiguration.isRetain(),
channelConfiguration.getQos(), channelConfiguration.commandTemplate) channelConfiguration.getQos(), channelConfiguration.commandTemplate)

View File

@ -286,12 +286,11 @@ public class JSONSchemaLight extends AbstractRawSchemaLight {
colorModeValue.getChannelState()); colorModeValue.getChannelState());
if (hasColorChannel) { if (hasColorChannel) {
listener.updateChannelState(new ChannelUID(getGroupUID(), COLOR_CHANNEL_ID), colorValue.getChannelState()); listener.updateChannelState(buildChannelUID(COLOR_CHANNEL_ID), colorValue.getChannelState());
} else if (brightnessChannel != null) { } else if (brightnessChannel != null) {
listener.updateChannelState(new ChannelUID(getGroupUID(), BRIGHTNESS_CHANNEL_ID), listener.updateChannelState(buildChannelUID(BRIGHTNESS_CHANNEL_ID), brightnessValue.getChannelState());
brightnessValue.getChannelState());
} else { } else {
listener.updateChannelState(new ChannelUID(getGroupUID(), ON_OFF_CHANNEL_ID), onOffValue.getChannelState()); listener.updateChannelState(buildChannelUID(ON_OFF_CHANNEL_ID), onOffValue.getChannelState());
} }
} }
} }

View File

@ -58,8 +58,8 @@ public class Lock extends AbstractComponent<Lock.ChannelConfiguration> {
} }
buildChannel(SWITCH_CHANNEL_ID, buildChannel(SWITCH_CHANNEL_ID,
new OnOffValue(channelConfiguration.payloadLock, channelConfiguration.payloadUnlock), new OnOffValue(channelConfiguration.payloadLock, channelConfiguration.payloadUnlock), getName(),
channelConfiguration.getName(), componentConfiguration.getUpdateListener()) componentConfiguration.getUpdateListener())
.stateTopic(channelConfiguration.stateTopic, channelConfiguration.getValueTemplate()) .stateTopic(channelConfiguration.stateTopic, channelConfiguration.getValueTemplate())
.commandTopic(channelConfiguration.commandTopic, channelConfiguration.isRetain(), .commandTopic(channelConfiguration.commandTopic, channelConfiguration.isRetain(),
channelConfiguration.getQos()) channelConfiguration.getQos())

View File

@ -82,8 +82,7 @@ public class Number extends AbstractComponent<Number.ChannelConfiguration> {
NumberValue value = new NumberValue(channelConfiguration.min, channelConfiguration.max, NumberValue value = new NumberValue(channelConfiguration.min, channelConfiguration.max,
channelConfiguration.step, UnitUtils.parseUnit(channelConfiguration.unitOfMeasurement)); channelConfiguration.step, UnitUtils.parseUnit(channelConfiguration.unitOfMeasurement));
buildChannel(NUMBER_CHANNEL_ID, value, channelConfiguration.getName(), buildChannel(NUMBER_CHANNEL_ID, value, getName(), componentConfiguration.getUpdateListener())
componentConfiguration.getUpdateListener())
.stateTopic(channelConfiguration.stateTopic, channelConfiguration.getValueTemplate()) .stateTopic(channelConfiguration.stateTopic, channelConfiguration.getValueTemplate())
.commandTopic(channelConfiguration.commandTopic, channelConfiguration.isRetain(), .commandTopic(channelConfiguration.commandTopic, channelConfiguration.isRetain(),
channelConfiguration.getQos(), channelConfiguration.commandTemplate) channelConfiguration.getQos(), channelConfiguration.commandTemplate)

View File

@ -66,8 +66,7 @@ public class Select extends AbstractComponent<Select.ChannelConfiguration> {
TextValue value = new TextValue(channelConfiguration.options); TextValue value = new TextValue(channelConfiguration.options);
buildChannel(SELECT_CHANNEL_ID, value, channelConfiguration.getName(), buildChannel(SELECT_CHANNEL_ID, value, getName(), componentConfiguration.getUpdateListener())
componentConfiguration.getUpdateListener())
.stateTopic(channelConfiguration.stateTopic, channelConfiguration.getValueTemplate()) .stateTopic(channelConfiguration.stateTopic, channelConfiguration.getValueTemplate())
.commandTopic(channelConfiguration.commandTopic, channelConfiguration.isRetain(), .commandTopic(channelConfiguration.commandTopic, channelConfiguration.isRetain(),
channelConfiguration.getQos(), channelConfiguration.commandTemplate) channelConfiguration.getQos(), channelConfiguration.commandTemplate)

View File

@ -88,8 +88,7 @@ public class Sensor extends AbstractComponent<Sensor.ChannelConfiguration> {
boolean trigger = TRIGGER_ICONS.matcher(icon).matches(); boolean trigger = TRIGGER_ICONS.matcher(icon).matches();
buildChannel(SENSOR_CHANNEL_ID, value, channelConfiguration.getName(), buildChannel(SENSOR_CHANNEL_ID, value, getName(), getListener(componentConfiguration, value))
getListener(componentConfiguration, value))
.stateTopic(channelConfiguration.stateTopic, channelConfiguration.getValueTemplate())// .stateTopic(channelConfiguration.stateTopic, channelConfiguration.getValueTemplate())//
.trigger(trigger).build(); .trigger(trigger).build();
} }

View File

@ -294,7 +294,7 @@ public class Vacuum extends AbstractComponent<Vacuum.ChannelConfiguration> {
ChannelStateUpdateListener channelStateUpdateListener, @Nullable String commandTemplate, ChannelStateUpdateListener channelStateUpdateListener, @Nullable String commandTemplate,
@Nullable String commandTopic, @Nullable String stateTemplate, @Nullable String stateTopic) { @Nullable String commandTopic, @Nullable String stateTemplate, @Nullable String stateTopic) {
if ((commandTopic != null && !commandTopic.isBlank()) || (stateTopic != null && !stateTopic.isBlank())) { if ((commandTopic != null && !commandTopic.isBlank()) || (stateTopic != null && !stateTopic.isBlank())) {
return buildChannel(channelId, valueState, channelConfiguration.getName(), channelStateUpdateListener) return buildChannel(channelId, valueState, getName(), channelStateUpdateListener)
.stateTopic(stateTopic, stateTemplate, channelConfiguration.getValueTemplate()) .stateTopic(stateTopic, stateTemplate, channelConfiguration.getValueTemplate())
.commandTopic(commandTopic, channelConfiguration.isRetain(), channelConfiguration.getQos(), .commandTopic(commandTopic, channelConfiguration.isRetain(), channelConfiguration.getQos(),
commandTemplate) commandTemplate)

View File

@ -33,8 +33,9 @@ import com.google.gson.annotations.SerializedName;
@NonNullByDefault @NonNullByDefault
public abstract class AbstractChannelConfiguration { public abstract class AbstractChannelConfiguration {
public static final char PARENT_TOPIC_PLACEHOLDER = '~'; public static final char PARENT_TOPIC_PLACEHOLDER = '~';
private static final String DEFAULT_THING_NAME = "Home Assistant Device";
protected String name; protected @Nullable String name;
protected String icon = ""; protected String icon = "";
protected int qos; // defaults to 0 according to HA specification protected int qos; // defaults to 0 according to HA specification
@ -93,6 +94,9 @@ public abstract class AbstractChannelConfiguration {
if (result == null) { if (result == null) {
result = name; result = name;
} }
if (result == null) {
result = DEFAULT_THING_NAME;
}
return result; return result;
} }
@ -127,7 +131,7 @@ public abstract class AbstractChannelConfiguration {
return properties; return properties;
} }
public String getName() { public @Nullable String getName() {
return name; return name;
} }

View File

@ -18,6 +18,7 @@ import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
@ -44,6 +45,7 @@ import org.openhab.binding.mqtt.homeassistant.internal.config.ChannelConfigurati
import org.openhab.binding.mqtt.homeassistant.internal.exception.ConfigurationException; import org.openhab.binding.mqtt.homeassistant.internal.exception.ConfigurationException;
import org.openhab.core.io.transport.mqtt.MqttBrokerConnection; import org.openhab.core.io.transport.mqtt.MqttBrokerConnection;
import org.openhab.core.thing.Channel; import org.openhab.core.thing.Channel;
import org.openhab.core.thing.ChannelGroupUID;
import org.openhab.core.thing.ChannelUID; import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing; import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus; import org.openhab.core.thing.ThingStatus;
@ -53,7 +55,6 @@ import org.openhab.core.thing.ThingUID;
import org.openhab.core.thing.binding.builder.ThingBuilder; import org.openhab.core.thing.binding.builder.ThingBuilder;
import org.openhab.core.thing.type.ChannelDefinition; import org.openhab.core.thing.type.ChannelDefinition;
import org.openhab.core.thing.type.ChannelGroupDefinition; import org.openhab.core.thing.type.ChannelGroupDefinition;
import org.openhab.core.thing.type.ChannelGroupType;
import org.openhab.core.thing.type.ThingType; import org.openhab.core.thing.type.ThingType;
import org.openhab.core.thing.util.ThingHelper; import org.openhab.core.thing.util.ThingHelper;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -93,7 +94,7 @@ public class HomeAssistantThingHandler extends AbstractMQTTThingHandler
protected final DiscoverComponents discoverComponents; protected final DiscoverComponents discoverComponents;
private final Gson gson; private final Gson gson;
protected final Map<String, AbstractComponent<?>> haComponents = new HashMap<>(); protected final Map<@Nullable String, AbstractComponent<?>> haComponents = new HashMap<>();
protected HandlerConfiguration config = new HandlerConfiguration(); protected HandlerConfiguration config = new HandlerConfiguration();
private Set<HaID> discoveryHomeAssistantIDs = new HashSet<>(); private Set<HaID> discoveryHomeAssistantIDs = new HashSet<>();
@ -137,10 +138,6 @@ public class HomeAssistantThingHandler extends AbstractMQTTThingHandler
for (Channel channel : thing.getChannels()) { for (Channel channel : thing.getChannels()) {
final String groupID = channel.getUID().getGroupId(); final String groupID = channel.getUID().getGroupId();
if (groupID == null) {
logger.warn("Channel {} has no groupd ID", channel.getLabel());
continue;
}
// Already restored component? // Already restored component?
@Nullable @Nullable
AbstractComponent<?> component = haComponents.get(groupID); AbstractComponent<?> component = haComponents.get(groupID);
@ -160,7 +157,12 @@ public class HomeAssistantThingHandler extends AbstractMQTTThingHandler
try { try {
component = ComponentFactory.createComponent(thingUID, haID, channelConfigurationJSON, this, this, component = ComponentFactory.createComponent(thingUID, haID, channelConfigurationJSON, this, this,
scheduler, gson, transformationServiceProvider); scheduler, gson, transformationServiceProvider);
haComponents.put(component.getGroupUID().getId(), component); final ChannelGroupUID groupUID = component.getGroupUID();
String id = null;
if (groupUID != null) {
id = groupUID.getId();
}
haComponents.put(id, component);
component.addChannelTypes(channelTypeProvider); component.addChannelTypes(channelTypeProvider);
} catch (ConfigurationException e) { } catch (ConfigurationException e) {
logger.error("Cannot not restore component {}: {}", thing, e.getMessage()); logger.error("Cannot not restore component {}: {}", thing, e.getMessage());
@ -228,9 +230,6 @@ public class HomeAssistantThingHandler extends AbstractMQTTThingHandler
@Override @Override
public @Nullable ChannelState getChannelState(ChannelUID channelUID) { public @Nullable ChannelState getChannelState(ChannelUID channelUID) {
String groupID = channelUID.getGroupId(); String groupID = channelUID.getGroupId();
if (groupID == null) {
return null;
}
AbstractComponent<?> component; AbstractComponent<?> component;
synchronized (haComponents) { // sync whenever discoverComponents is started synchronized (haComponents) { // sync whenever discoverComponents is started
component = haComponents.get(groupID); component = haComponents.get(groupID);
@ -266,7 +265,12 @@ public class HomeAssistantThingHandler extends AbstractMQTTThingHandler
synchronized (haComponents) { // sync whenever discoverComponents is started synchronized (haComponents) { // sync whenever discoverComponents is started
for (AbstractComponent<?> discovered : discoveredComponentsList) { for (AbstractComponent<?> discovered : discoveredComponentsList) {
AbstractComponent<?> known = haComponents.get(discovered.getGroupUID().getId()); final ChannelGroupUID groupUID = discovered.getGroupUID();
String id = null;
if (groupUID != null) {
id = groupUID.getId();
}
AbstractComponent<?> known = haComponents.get(id);
// Is component already known? // Is component already known?
if (known != null) { if (known != null) {
if (discovered.getConfigHash() != known.getConfigHash()) { if (discovered.getConfigHash() != known.getConfigHash()) {
@ -282,10 +286,10 @@ public class HomeAssistantThingHandler extends AbstractMQTTThingHandler
// Add channel and group types to the types registry // Add channel and group types to the types registry
discovered.addChannelTypes(channelTypeProvider); discovered.addChannelTypes(channelTypeProvider);
// Add component to the component map // Add component to the component map
haComponents.put(discovered.getGroupUID().getId(), discovered); haComponents.put(id, discovered);
// Start component / Subscribe to channel topics // Start component / Subscribe to channel topics
discovered.start(connection, scheduler, 0).exceptionally(e -> { discovered.start(connection, scheduler, 0).exceptionally(e -> {
logger.warn("Failed to start component {}", discovered.getGroupUID(), e); logger.warn("Failed to start component {}", discovered.getHaID(), e);
return null; return null;
}); });
@ -296,7 +300,7 @@ public class HomeAssistantThingHandler extends AbstractMQTTThingHandler
// We remove all conflicting old channels, they will be re-added below based on the new discovery // We remove all conflicting old channels, they will be re-added below based on the new discovery
logger.debug( logger.debug(
"Received component {} with slightly different config. Making sure we re-create conflicting channels...", "Received component {} with slightly different config. Making sure we re-create conflicting channels...",
discovered.getGroupUID()); discovered.getHaID());
removeJustRediscoveredChannels(discoveredChannels); removeJustRediscoveredChannels(discoveredChannels);
} }
@ -346,9 +350,8 @@ public class HomeAssistantThingHandler extends AbstractMQTTThingHandler
List<ChannelDefinition> channelDefs; List<ChannelDefinition> channelDefs;
synchronized (haComponents) { // sync whenever discoverComponents is started synchronized (haComponents) { // sync whenever discoverComponents is started
groupDefs = haComponents.values().stream().map(AbstractComponent::getGroupDefinition) groupDefs = haComponents.values().stream().map(AbstractComponent::getGroupDefinition)
.collect(Collectors.toList()); .filter(Objects::nonNull).map(Objects::requireNonNull).collect(Collectors.toList());
channelDefs = haComponents.values().stream().map(AbstractComponent::getType) channelDefs = haComponents.values().stream().map(AbstractComponent::getChannels).flatMap(List::stream)
.map(ChannelGroupType::getChannelDefinitions).flatMap(List::stream)
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
ThingType thingType = channelTypeProvider.derive(typeID, MqttBindingConstants.HOMEASSISTANT_MQTT_THING) ThingType thingType = channelTypeProvider.derive(typeID, MqttBindingConstants.HOMEASSISTANT_MQTT_THING)

View File

@ -22,6 +22,7 @@ import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import java.util.Set; import java.util.Set;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
@ -160,7 +161,8 @@ public class HomeAssistantMQTTImplementationTest extends MqttOSGiTest {
ComponentDiscovered cd = (haID, c) -> { ComponentDiscovered cd = (haID, c) -> {
haComponents.put(c.getGroupUID().getId(), c); haComponents.put(c.getGroupUID().getId(), c);
c.addChannelTypes(channelTypeProvider); c.addChannelTypes(channelTypeProvider);
channelTypeProvider.setChannelGroupType(c.getGroupTypeUID(), c.getType()); channelTypeProvider.setChannelGroupType(Objects.requireNonNull(c.getGroupTypeUID()),
Objects.requireNonNull(c.getType()));
latch.countDown(); latch.countDown();
}; };