diff --git a/addons/binding/org.openhab.binding.mqtt.generic.test/src/test/java/org/openhab/binding/mqtt/generic/HomeAssistantMQTTImplementationTests.java b/addons/binding/org.openhab.binding.mqtt.generic.test/src/test/java/org/openhab/binding/mqtt/generic/HomeAssistantMQTTImplementationTests.java
index b279a23aeb81d..9589e92137e1a 100644
--- a/addons/binding/org.openhab.binding.mqtt.generic.test/src/test/java/org/openhab/binding/mqtt/generic/HomeAssistantMQTTImplementationTests.java
+++ b/addons/binding/org.openhab.binding.mqtt.generic.test/src/test/java/org/openhab/binding/mqtt/generic/HomeAssistantMQTTImplementationTests.java
@@ -45,10 +45,10 @@
import org.junit.Test;
import org.mockito.Mock;
import org.openhab.binding.mqtt.generic.internal.convention.homeassistant.AbstractComponent;
+import org.openhab.binding.mqtt.generic.internal.convention.homeassistant.ChannelConfigurationTypeAdapterFactory;
import org.openhab.binding.mqtt.generic.internal.convention.homeassistant.ComponentSwitch;
import org.openhab.binding.mqtt.generic.internal.convention.homeassistant.DiscoverComponents;
import org.openhab.binding.mqtt.generic.internal.convention.homeassistant.DiscoverComponents.ComponentDiscovered;
-import org.openhab.binding.mqtt.generic.internal.convention.homeassistant.ChannelConfigurationTypeAdapterFactory;
import org.openhab.binding.mqtt.generic.internal.convention.homeassistant.HaID;
import org.openhab.binding.mqtt.generic.internal.generic.ChannelStateUpdateListener;
import org.openhab.binding.mqtt.generic.internal.generic.MqttChannelTypeProvider;
@@ -175,7 +175,7 @@ public void parseHATree() throws InterruptedException, ExecutionException, Timeo
// 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(haID.getChannelGroupID(), c);
+ haComponents.put(c.uid().getId(), c);
c.addChannelTypes(channelTypeProvider);
channelTypeProvider.setChannelGroupType(c.groupTypeUID(), c.type());
latch.countDown();
@@ -192,7 +192,7 @@ public void parseHATree() throws InterruptedException, ExecutionException, Timeo
assertTrue(latch.await(300, TimeUnit.MILLISECONDS));
future.get(100, TimeUnit.MILLISECONDS);
- // No failure expected and one discoverd result
+ // No failure expected and one discovered result
assertNull(failure);
assertThat(haComponents.size(), is(1));
@@ -200,9 +200,11 @@ public void parseHATree() throws InterruptedException, ExecutionException, Timeo
verify(channelTypeProvider, times(1)).setChannelGroupType(any(), any());
verify(channelTypeProvider, times(1)).setChannelType(any(), any());
+ String channelGroupId = ThingChannelConstants.testHomeAssistantThing.getId() + "_switch";
+
// We expect a switch component with an OnOff channel with the initial value UNDEF:
- State value = haComponents.get(haID.getChannelGroupID()).channelTypes().get(ComponentSwitch.switchChannelID)
- .getState().getCache().getChannelState();
+ State value = haComponents.get(channelGroupId).channelTypes().get(ComponentSwitch.switchChannelID).getState()
+ .getCache().getChannelState();
assertThat(value, is(UnDefType.UNDEF));
haComponents.values().stream().map(e -> e.start(connection, scheduler, 100))
@@ -215,8 +217,8 @@ public void parseHATree() throws InterruptedException, ExecutionException, Timeo
verify(channelStateUpdateListener, times(1)).updateChannelState(any(), any());
// Value should be ON now.
- value = haComponents.get(haID.getChannelGroupID()).channelTypes().get(ComponentSwitch.switchChannelID)
- .getState().getCache().getChannelState();
+ value = haComponents.get(channelGroupId).channelTypes().get(ComponentSwitch.switchChannelID).getState()
+ .getCache().getChannelState();
assertThat(value, is(OnOffType.ON));
}
diff --git a/addons/binding/org.openhab.binding.mqtt.generic.test/src/test/java/org/openhab/binding/mqtt/generic/internal/convention/homeassistant/DiscoverComponentsTests.java b/addons/binding/org.openhab.binding.mqtt.generic.test/src/test/java/org/openhab/binding/mqtt/generic/internal/convention/homeassistant/DiscoverComponentsTests.java
index 3e1421616421d..0b833cfcd9795 100644
--- a/addons/binding/org.openhab.binding.mqtt.generic.test/src/test/java/org/openhab/binding/mqtt/generic/internal/convention/homeassistant/DiscoverComponentsTests.java
+++ b/addons/binding/org.openhab.binding.mqtt.generic.test/src/test/java/org/openhab/binding/mqtt/generic/internal/convention/homeassistant/DiscoverComponentsTests.java
@@ -74,8 +74,9 @@ public void discoveryTimeTest() throws InterruptedException, ExecutionException,
DiscoverComponents discover = spy(new DiscoverComponents(ThingChannelConstants.testHomeAssistantThing,
scheduler, null, gson, transformationServiceProvider));
- discover.startDiscovery(connection, 50, new HaID("homeassistant", "object", "node", "component"), discovered)
- .get(100, TimeUnit.MILLISECONDS);
+ HandlerConfiguration config = new HandlerConfiguration("homeassistant", "object");
+
+ discover.startDiscovery(connection, 50, HaID.fromConfig(config), discovered).get(100, TimeUnit.MILLISECONDS);
}
}
diff --git a/addons/binding/org.openhab.binding.mqtt.generic.test/src/test/java/org/openhab/binding/mqtt/generic/internal/convention/homeassistant/HaIDTests.java b/addons/binding/org.openhab.binding.mqtt.generic.test/src/test/java/org/openhab/binding/mqtt/generic/internal/convention/homeassistant/HaIDTests.java
index d4f6dceb624a4..e1353ff1eb2b7 100644
--- a/addons/binding/org.openhab.binding.mqtt.generic.test/src/test/java/org/openhab/binding/mqtt/generic/internal/convention/homeassistant/HaIDTests.java
+++ b/addons/binding/org.openhab.binding.mqtt.generic.test/src/test/java/org/openhab/binding/mqtt/generic/internal/convention/homeassistant/HaIDTests.java
@@ -15,9 +15,8 @@
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
-import org.eclipse.smarthome.core.thing.type.ChannelTypeUID;
+import org.eclipse.smarthome.config.core.Configuration;
import org.junit.Test;
-import org.openhab.binding.mqtt.generic.internal.convention.homeassistant.HaID;
public class HaIDTests {
@@ -25,20 +24,44 @@ public class HaIDTests {
public void testWithoutNode() {
HaID subject = new HaID("homeassistant/switch/name/config");
- assertThat(subject.getThingID(), is("name"));
- assertThat(subject.getChannelGroupTypeID(), is("name_switch"));
- assertThat(subject.getChannelTypeID("channel"), is(new ChannelTypeUID("mqtt:name_switch_channel")));
- assertThat(subject.getChannelGroupID(), is("switch_"));
+ assertThat(subject.objectID, is("name"));
+
+ assertThat(subject.component, is("switch"));
+ assertThat(subject.getTopic("suffix"), is("homeassistant/switch/name/suffix"));
+
+ Configuration config = new Configuration();
+ subject.toConfig(config);
+
+ HaID restore = HaID.fromConfig("homeassistant", config);
+
+ assertThat(restore, is(subject));
+
+ HandlerConfiguration haConfig = subject.toHandlerConfiguration();
+
+ restore = HaID.fromConfig(haConfig);
+ assertThat(restore, is(new HaID("homeassistant/+/name/config")));
}
@Test
public void testWithNode() {
HaID subject = new HaID("homeassistant/switch/node/name/config");
- assertThat(subject.getThingID(), is("name"));
- assertThat(subject.getChannelGroupTypeID(), is("name_switchnode"));
- assertThat(subject.getChannelTypeID("channel"), is(new ChannelTypeUID("mqtt:name_switchnode_channel")));
- assertThat(subject.getChannelGroupID(), is("switch_node"));
+ assertThat(subject.objectID, is("name"));
+
+ assertThat(subject.component, is("switch"));
+ assertThat(subject.getTopic("suffix"), is("homeassistant/switch/node/name/suffix"));
+
+ Configuration config = new Configuration();
+ subject.toConfig(config);
+
+ HaID restore = HaID.fromConfig("homeassistant", config);
+
+ assertThat(restore, is(subject));
+
+ HandlerConfiguration haConfig = subject.toHandlerConfiguration();
+
+ restore = HaID.fromConfig(haConfig);
+ assertThat(restore, is(new HaID("homeassistant/+/node/name/config")));
}
}
diff --git a/addons/binding/org.openhab.binding.mqtt.generic/ESH-INF/config/homeassistant-channel-config.xml b/addons/binding/org.openhab.binding.mqtt.generic/ESH-INF/config/homeassistant-channel-config.xml
index 4d162e12bcceb..f510eb45bdfb6 100644
--- a/addons/binding/org.openhab.binding.mqtt.generic/ESH-INF/config/homeassistant-channel-config.xml
+++ b/addons/binding/org.openhab.binding.mqtt.generic/ESH-INF/config/homeassistant-channel-config.xml
@@ -5,7 +5,22 @@
xsi:schemaLocation="http://eclipse.org/smarthome/schemas/config-description/v1.0.0 http://eclipse.org/smarthome/schemas/config-description-1.0.0.xsd">
-
+
+
+ Type of the channel group.
+
+
+
+
+ Optional node name of the component
+
+
+
+
+ Object id of the component
+
+
+ The json configuration string received by the component via MQTT.
diff --git a/addons/binding/org.openhab.binding.mqtt.generic/README.md b/addons/binding/org.openhab.binding.mqtt.generic/README.md
index dfc887ba5c1b4..2cfbf8db30444 100644
--- a/addons/binding/org.openhab.binding.mqtt.generic/README.md
+++ b/addons/binding/org.openhab.binding.mqtt.generic/README.md
@@ -23,6 +23,7 @@ Find the next table to understand the topology mapping from Homie to the Framewo
| Property | Channel | homie/super-car/engine/temperature |
System trigger channels are supported using non-retained properties, with *enum* data type and with the following formats:
+
* Format: "PRESSED,RELEASED" -> system.rawbutton
* Format: "SHORT\_PRESSED,DOUBLE\_PRESSED,LONG\_PRESSED" -> system.button
* Format: "DIR1\_PRESSED,DIR1\_RELEASED,DIR2\_PRESSED,DIR2\_RELEASED" -> system.rawrocker
@@ -124,6 +125,7 @@ You can connect this channel to a Contact or Switch item.
* __on__: An optional string (like "BRIGHT") that is recognized as on state. (ON will always be recognized.)
* __off__: An optional string (like "DARK") that is recognized as off state. (OFF will always be recognized.)
* __onBrightness__: If you connect this channel to a Switch item and turn it on,
+
color and saturation are preserved from the last state, but
the brightness will be set to this configured initial brightness (default: 10%).
@@ -234,9 +236,9 @@ Here are a few examples:
## Troubleshooting
* If you get the error "No MQTT client": Please update your installation.
-* If you use the Mosquitto broker: Please be aware that there is a relatively low setting
- for retained messages. At some point messages will just not being delivered
- anymore: Change the setting
+* If you use the Mosquitto broker: Please be aware that there is a relatively low setting
+for retained messages. At some point messages will just not being delivered anymore:
+Change the setting
## Examples
diff --git a/addons/binding/org.openhab.binding.mqtt.generic/src/main/java/org/openhab/binding/mqtt/generic/internal/convention/homeassistant/AbstractComponent.java b/addons/binding/org.openhab.binding.mqtt.generic/src/main/java/org/openhab/binding/mqtt/generic/internal/convention/homeassistant/AbstractComponent.java
index c4484b658f411..96f7c41c51c4d 100644
--- a/addons/binding/org.openhab.binding.mqtt.generic/src/main/java/org/openhab/binding/mqtt/generic/internal/convention/homeassistant/AbstractComponent.java
+++ b/addons/binding/org.openhab.binding.mqtt.generic/src/main/java/org/openhab/binding/mqtt/generic/internal/convention/homeassistant/AbstractComponent.java
@@ -19,6 +19,7 @@
import java.util.concurrent.ScheduledExecutorService;
import java.util.stream.Collectors;
+import org.apache.commons.lang.StringUtils;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.smarthome.core.thing.ChannelGroupUID;
@@ -65,14 +66,20 @@ public abstract class AbstractComponent {
*/
public AbstractComponent(CFactory.ComponentConfiguration componentConfiguration, Class clazz) {
this.componentConfiguration = componentConfiguration;
- this.haID = componentConfiguration.getHaID();
- this.channelGroupTypeUID = new ChannelGroupTypeUID(MqttBindingConstants.BINDING_ID,
- haID.getChannelGroupTypeID());
- this.channelGroupUID = new ChannelGroupUID(componentConfiguration.getThingUID(), haID.getChannelGroupID());
this.channelConfigurationJson = componentConfiguration.getConfigJSON();
this.channelConfiguration = componentConfiguration.getConfig(clazz);
this.configHash = channelConfigurationJson.hashCode();
+
+ this.haID = componentConfiguration.getHaID();
+
+ String groupId = channelConfiguration.unique_id;
+ if (groupId == null || StringUtils.isBlank(groupId)) {
+ groupId = this.haID.getFallbackGroupId();
+ }
+
+ this.channelGroupTypeUID = new ChannelGroupTypeUID(MqttBindingConstants.BINDING_ID, groupId);
+ this.channelGroupUID = new ChannelGroupUID(componentConfiguration.getThingUID(), groupId);
}
protected CChannel.Builder buildChannel(String channelID, Value valueState, String label) {
diff --git a/addons/binding/org.openhab.binding.mqtt.generic/src/main/java/org/openhab/binding/mqtt/generic/internal/convention/homeassistant/CChannel.java b/addons/binding/org.openhab.binding.mqtt.generic/src/main/java/org/openhab/binding/mqtt/generic/internal/convention/homeassistant/CChannel.java
index be06e5ec628e3..d1b0fcfdcfb98 100644
--- a/addons/binding/org.openhab.binding.mqtt.generic/src/main/java/org/openhab/binding/mqtt/generic/internal/convention/homeassistant/CChannel.java
+++ b/addons/binding/org.openhab.binding.mqtt.generic/src/main/java/org/openhab/binding/mqtt/generic/internal/convention/homeassistant/CChannel.java
@@ -180,7 +180,8 @@ public CChannel build(boolean addToComponent) {
ChannelTypeUID channelTypeUID;
channelUID = new ChannelUID(component.channelGroupUID, channelID);
- channelTypeUID = component.haID.getChannelTypeID(channelID);
+ channelTypeUID = new ChannelTypeUID(MqttBindingConstants.BINDING_ID,
+ channelUID.getGroupId() + "_" + channelID);
channelState = new ChannelState(
ChannelConfigBuilder.create().withRetain(retain).withStateTopic(state_topic)
.withCommandTopic(command_topic).build(),
@@ -197,6 +198,8 @@ public CChannel build(boolean addToComponent) {
Configuration configuration = new Configuration();
configuration.put("config", component.channelConfigurationJson);
+ component.haID.toConfig(configuration);
+
channel = ChannelBuilder.create(channelUID, channelState.getItemType()).withType(channelTypeUID)
.withKind(type.getKind()).withLabel(label).withConfiguration(configuration).build();
diff --git a/addons/binding/org.openhab.binding.mqtt.generic/src/main/java/org/openhab/binding/mqtt/generic/internal/convention/homeassistant/CFactory.java b/addons/binding/org.openhab.binding.mqtt.generic/src/main/java/org/openhab/binding/mqtt/generic/internal/convention/homeassistant/CFactory.java
index 721cace588359..79a024956580e 100644
--- a/addons/binding/org.openhab.binding.mqtt.generic/src/main/java/org/openhab/binding/mqtt/generic/internal/convention/homeassistant/CFactory.java
+++ b/addons/binding/org.openhab.binding.mqtt.generic/src/main/java/org/openhab/binding/mqtt/generic/internal/convention/homeassistant/CFactory.java
@@ -14,7 +14,6 @@
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
-import org.eclipse.smarthome.core.thing.Channel;
import org.eclipse.smarthome.core.thing.ThingUID;
import org.openhab.binding.mqtt.generic.internal.generic.ChannelStateUpdateListener;
import org.openhab.binding.mqtt.generic.internal.generic.TransformationServiceProvider;
@@ -44,11 +43,12 @@ public class CFactory {
* @param updateListener A channel state update listener
* @return A HA MQTT Component
*/
- public static @Nullable AbstractComponent> createComponent(ThingUID thingUID, HaID haID, String configJSON,
- @Nullable ChannelStateUpdateListener updateListener, Gson gson,
+ public static @Nullable AbstractComponent> createComponent(ThingUID thingUID, HaID haID,
+ String channelConfigurationJSON, @Nullable ChannelStateUpdateListener updateListener, Gson gson,
TransformationServiceProvider transformationServiceProvider) {
- ComponentConfiguration componentConfiguration = new ComponentConfiguration(thingUID, haID, configJSON, gson)
- .listener(updateListener).transformationProvider(transformationServiceProvider);
+ ComponentConfiguration componentConfiguration = new ComponentConfiguration(thingUID, haID,
+ channelConfigurationJSON, gson).listener(updateListener)
+ .transformationProvider(transformationServiceProvider);
try {
switch (haID.component) {
case "alarm_control_panel":
@@ -78,27 +78,6 @@ public class CFactory {
return null;
}
- /**
- * Create a HA MQTT component by a given channel configuration.
- *
- * @param basetopic The MQTT base topic, usually "homeassistant"
- * @param channel A channel with the JSON configuration embedded as configuration (key: 'config')
- * @param updateListener A channel state update listener
- * @return A HA MQTT Component
- */
- public static @Nullable AbstractComponent> createComponent(String basetopic, Channel channel,
- @Nullable ChannelStateUpdateListener updateListener, Gson gson,
- TransformationServiceProvider transformationServiceProvider) {
- HaID haID = new HaID(basetopic, channel.getUID());
- ThingUID thingUID = channel.getUID().getThingUID();
- String configJSON = (String) channel.getConfiguration().get("config");
- if (configJSON == null) {
- logger.warn("Provided channel does not have a 'config' configuration key!");
- return null;
- }
- return createComponent(thingUID, haID, configJSON, updateListener, gson, transformationServiceProvider);
- }
-
protected static class ComponentConfiguration {
private ThingUID thingUID;
private HaID haID;
diff --git a/addons/binding/org.openhab.binding.mqtt.generic/src/main/java/org/openhab/binding/mqtt/generic/internal/convention/homeassistant/DiscoverComponents.java b/addons/binding/org.openhab.binding.mqtt.generic/src/main/java/org/openhab/binding/mqtt/generic/internal/convention/homeassistant/DiscoverComponents.java
index 451dc11cf90bb..24f8b394e5ba2 100644
--- a/addons/binding/org.openhab.binding.mqtt.generic/src/main/java/org/openhab/binding/mqtt/generic/internal/convention/homeassistant/DiscoverComponents.java
+++ b/addons/binding/org.openhab.binding.mqtt.generic/src/main/java/org/openhab/binding/mqtt/generic/internal/convention/homeassistant/DiscoverComponents.java
@@ -51,7 +51,6 @@ public class DiscoverComponents implements MqttMessageSubscriber {
private WeakReference<@Nullable MqttBrokerConnection> connectionRef = new WeakReference<>(null);
protected @NonNullByDefault({}) ComponentDiscovered discoveredListener;
private int discoverTime;
- private String topicWithNode = "";
private String topic = "";
/**
@@ -106,8 +105,10 @@ public void processMessage(String topic, byte[] payload) {
*
*
* @param connection A MQTT broker connection
- * @param discoverTime The time in milliseconds for the discovery to run. Can be 0 to disable the timeout.
- * You need to call {@link #stopDiscovery(MqttBrokerConnection)} at some point in that case.
+ * @param discoverTime The time in milliseconds for the discovery to run. Can be 0 to disable the
+ * timeout.
+ * You need to call {@link #stopDiscovery(MqttBrokerConnection)} at some
+ * point in that case.
* @param topicDescription Contains the object-id (=device id) and potentially a node-id as well.
* @param componentsDiscoveredListener Listener for results
* @return A future that completes normally after the given time in milliseconds or exceptionally on any error.
@@ -116,15 +117,13 @@ public void processMessage(String topic, byte[] payload) {
public CompletableFuture<@Nullable Void> startDiscovery(MqttBrokerConnection connection, int discoverTime,
HaID topicDescription, ComponentDiscovered componentsDiscoveredListener) {
- this.topicWithNode = topicDescription.baseTopic + "/+/+/" + topicDescription.objectID + "/config";
- this.topic = topicDescription.baseTopic + "/+/" + topicDescription.objectID + "/config";
+ this.topic = topicDescription.getTopic("config");
this.discoverTime = discoverTime;
this.discoveredListener = componentsDiscoveredListener;
this.connectionRef = new WeakReference<>(connection);
- // Subscribe to the wildcard topics and start receive MQTT retained topics
- CompletableFuture.allOf(connection.subscribe(topic, this), connection.subscribe(topicWithNode, this))
- .thenRun(this::subscribeSuccess).exceptionally(this::subscribeFail);
+ // Subscribe to the wildcard topic and start receive MQTT retained topics
+ connection.subscribe(topic, this).thenRun(this::subscribeSuccess).exceptionally(this::subscribeFail);
return discoverFinishedFuture;
}
@@ -135,7 +134,6 @@ private void subscribeSuccess() {
if (connection != null && discoverTime > 0) {
this.stopDiscoveryFuture = scheduler.schedule(() -> {
this.stopDiscoveryFuture = null;
- connection.unsubscribe(topicWithNode, this);
connection.unsubscribe(topic, this);
this.discoveredListener = null;
discoverFinishedFuture.complete(null);
@@ -155,7 +153,6 @@ private void subscribeSuccess() {
this.discoveredListener = null;
final MqttBrokerConnection connection = connectionRef.get();
if (connection != null) {
- connection.unsubscribe(topicWithNode, this);
connection.unsubscribe(topic, this);
connectionRef.clear();
}
diff --git a/addons/binding/org.openhab.binding.mqtt.generic/src/main/java/org/openhab/binding/mqtt/generic/internal/convention/homeassistant/HaID.java b/addons/binding/org.openhab.binding.mqtt.generic/src/main/java/org/openhab/binding/mqtt/generic/internal/convention/homeassistant/HaID.java
index 08f80b7289b34..1ecaca59c4006 100644
--- a/addons/binding/org.openhab.binding.mqtt.generic/src/main/java/org/openhab/binding/mqtt/generic/internal/convention/homeassistant/HaID.java
+++ b/addons/binding/org.openhab.binding.mqtt.generic/src/main/java/org/openhab/binding/mqtt/generic/internal/convention/homeassistant/HaID.java
@@ -12,10 +12,10 @@
*/
package org.openhab.binding.mqtt.generic.internal.convention.homeassistant;
+import org.apache.commons.lang.StringUtils;
import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.eclipse.smarthome.core.thing.ChannelUID;
-import org.eclipse.smarthome.core.thing.type.ChannelTypeUID;
-import org.openhab.binding.mqtt.generic.internal.MqttBindingConstants;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.smarthome.config.core.Configuration;
/**
* HomeAssistant MQTT components use a specific MQTT topic layout,
@@ -23,37 +23,51 @@
* followed by the component id, an optional node id and the object id.
*
* This helper class can split up an MQTT topic into such parts.
+ *
+ * Implementation note: This is an immutable class.
*
* @author David Graeff - Initial contribution
*/
@NonNullByDefault
public class HaID {
- final public String baseTopic;
- final public String component;
- final public String nodeID;
- final public String objectID;
+ public final String baseTopic;
+ public final String component;
+ public final String nodeID;
+ public final String objectID;
+
+ private final String topic;
/**
* Creates a {@link HaID} object for a given HomeAssistant MQTT topic.
*
* @param mqttTopic A topic like "homeassistant/binary_sensor/garden/config" or
- * "homeassistant/binary_sensor/0/garden/config"
+ * "homeassistant/binary_sensor/0/garden/config"
*/
public HaID(String mqttTopic) {
String[] strings = mqttTopic.split("/");
- if (strings.length < 3) {
- throw new IllegalArgumentException("MQTT topic not a HomeAssistant topic!");
+ if (strings.length < 4 || strings.length > 5) {
+ throw new IllegalArgumentException("MQTT topic not a HomeAssistant topic (wrong length)!");
+ }
+ if (!"config".equals(strings[strings.length - 1])) {
+ throw new IllegalArgumentException("MQTT topic not a HomeAssistant topic ('config' missing)!");
}
- if (strings.length >= 5) {
- component = strings[1];
+
+ baseTopic = strings[0];
+ component = strings[1];
+
+ if (strings.length == 5) {
nodeID = strings[2];
objectID = strings[3];
} else {
- component = strings[1];
nodeID = "";
objectID = strings[2];
}
- baseTopic = strings[0];
+
+ this.topic = createTopic(this);
+ }
+
+ public HaID() {
+ this("", "", "", "");
}
/**
@@ -64,66 +78,173 @@ public HaID(String mqttTopic) {
* @param nodeID The node ID (can be the empty string)
* @param component The component ID
*/
- public HaID(String baseTopic, String objectID, String nodeID, String component) {
+ private HaID(String baseTopic, String objectID, String nodeID, String component) {
this.baseTopic = baseTopic;
this.objectID = objectID;
this.nodeID = nodeID;
this.component = component;
+ this.topic = createTopic(this);
+ }
+
+ private static final String createTopic(HaID id) {
+ StringBuilder str = new StringBuilder();
+ str.append(id.baseTopic).append('/').append(id.component).append('/');
+ if (StringUtils.isNotBlank(id.nodeID)) {
+ str.append(id.nodeID).append('/');
+ }
+ str.append(id.objectID).append('/');
+ return str.toString();
}
/**
- * Creates a {@link HaID} by providing a channel UID.
+ * Extract the HaID information from a channel configuration.
+ *
+ * objectid, nodeid, and component values are fetched from the configuration.
*
- * @param baseTopic The base topic. Usually "homeassistant".
- * @param channel The channel UID
+ * @param baseTopic
+ * @param config
+ * @return newly created HaID
*/
- public HaID(String baseTopic, ChannelUID channel) {
- String groupId = channel.getGroupId();
- if (groupId == null) {
- throw new IllegalArgumentException("Channel needs a group ID!");
- }
- String[] groupParts = groupId.split("_");
- if (groupParts.length != 2) {
- throw new IllegalArgumentException("Channel needs a group ID with the pattern component_node!");
- }
- this.objectID = channel.getThingUID().getId();
- this.nodeID = groupParts[1];
- this.component = groupParts[0];
- this.baseTopic = baseTopic;
+ public static HaID fromConfig(String baseTopic, Configuration config) {
+ String objectID = (String) config.get("objectid");
+ String nodeID = (String) config.getProperties().getOrDefault("nodeid", "");
+ String component = (String) config.get("component");
+ return new HaID(baseTopic, objectID, nodeID, component);
}
/**
- * We map the HomeAssistant MQTT topic tree object to an ESH Thing.
+ * Add the HaID information to a channel configuration.
+ *
+ * objectid, nodeid, and component values are added to the configuration.
+ *
+ * @param config
+ * @return the modified configuration
*/
- public String getThingID() {
- return objectID;
+ public Configuration toConfig(Configuration config) {
+ config.put("objectid", objectID);
+ config.put("nodeid", nodeID);
+ config.put("component", component);
+ return config;
}
/**
- * The channel group type UID consists of all components of this object (object-id + node-id + component-id).
+ * Extract the HaID information from a thing configuration.
+ *
+ * basetpoic and objectid are taken from the configuration.
+ * The objectid string may be in the form nodeid/objectid.
+ *
+ * The component component in the resulting HaID will be set to +.
+ * This enables the HaID to be used as an mqtt subscription topic.
+ *
+ * @param config
+ * @return newly created HaID
*/
- public String getChannelGroupTypeID() {
- return objectID + "_" + component + nodeID;
+ public static HaID fromConfig(HandlerConfiguration config) {
+ String objectID = config.objectid;
+ String nodeID = "";
+
+ if (StringUtils.contains(objectID, '/')) {
+ String[] parts = objectID.split("/");
+
+ if (parts.length != 2) {
+ throw new IllegalArgumentException(
+ "Bad configuration. objectid must be or /!");
+ }
+ nodeID = parts[0];
+ objectID = parts[1];
+ }
+ return new HaID(config.basetopic, objectID, nodeID, "+");
}
/**
- * A channel type UID consists of all components of this object (object-id + node-id + component-id) and a
- * channel-id on top.
+ * Create a new thing configuration which contains the information from this HaID.
+ *
+ * objectid in the thing configuration will be
+ * nodeID/objectID from the HaID, if nodeID is not empty.
+ *
+ * component value will not be preserved.
+ *
+ * @return the new thing configuration
*/
- public ChannelTypeUID getChannelTypeID(String channelID) {
- return new ChannelTypeUID(MqttBindingConstants.BINDING_ID,
- objectID + "_" + component + nodeID + "_" + channelID);
+ public HandlerConfiguration toHandlerConfiguration() {
+ String objectID = this.objectID;
+ if (StringUtils.isNotBlank(nodeID)) {
+ objectID = nodeID + "/" + objectID;
+ }
+
+ return new HandlerConfiguration(baseTopic, objectID);
}
/**
- * The channel group ID consists of the node-id and the component-id
+ * The default group id is the unique_id of the component, given in the config-json.
+ * If the unique id is not set, then a fallback is constructed from the HaID information.
+ *
+ * @return fallback group id
*/
- public String getChannelGroupID() {
- return component + "_" + nodeID;
+ public String getFallbackGroupId() {
+ StringBuilder str = new StringBuilder();
+
+ if (StringUtils.isNotBlank(nodeID)) {
+ str.append(nodeID).append('_');
+ }
+ str.append(objectID).append('_').append(component);
+ return str.toString();
+ }
+
+ /**
+ * Return a topic, which can be used for a mqtt subscription.
+ * Defined values for suffix are:
+ *
+ *
config
+ *
state
+ *
+ *
+ * @return fallback group id
+ */
+ public String getTopic(String suffix) {
+ return topic + suffix;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + baseTopic.hashCode();
+ result = prime * result + component.hashCode();
+ result = prime * result + nodeID.hashCode();
+ result = prime * result + objectID.hashCode();
+ return result;
+ }
+
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ HaID other = (HaID) obj;
+ if (!baseTopic.equals(other.baseTopic)) {
+ return false;
+ }
+ if (!component.equals(other.component)) {
+ return false;
+ }
+ if (!nodeID.equals(other.nodeID)) {
+ return false;
+ }
+ if (!objectID.equals(other.objectID)) {
+ return false;
+ }
+ return true;
}
@Override
public String toString() {
- return baseTopic + "/" + component + "/" + nodeID + "/" + objectID;
+ return topic;
}
}
diff --git a/addons/binding/org.openhab.binding.mqtt.generic/src/main/java/org/openhab/binding/mqtt/generic/internal/convention/homeassistant/HandlerConfiguration.java b/addons/binding/org.openhab.binding.mqtt.generic/src/main/java/org/openhab/binding/mqtt/generic/internal/convention/homeassistant/HandlerConfiguration.java
index 5d79ad2c26653..3b11d0aa5ecaa 100644
--- a/addons/binding/org.openhab.binding.mqtt.generic/src/main/java/org/openhab/binding/mqtt/generic/internal/convention/homeassistant/HandlerConfiguration.java
+++ b/addons/binding/org.openhab.binding.mqtt.generic/src/main/java/org/openhab/binding/mqtt/generic/internal/convention/homeassistant/HandlerConfiguration.java
@@ -12,6 +12,8 @@
*/
package org.openhab.binding.mqtt.generic.internal.convention.homeassistant;
+import java.util.Map;
+
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.mqtt.generic.internal.handler.HomeAssistantThingHandler;
@@ -25,11 +27,35 @@
@NonNullByDefault
public class HandlerConfiguration {
/**
+ * hint: cannot be final, or getConfigAs will not work.
* The MQTT prefix topic
*/
- public String basetopic = "homeassistant";
+ public String basetopic;
/**
+ * hint: cannot be final, or getConfigAs will not work.
* The object id. This is comparable to a Homie Device.
*/
- public String objectid = "";
+ public String objectid;
+
+ public HandlerConfiguration() {
+ this("homeassistant", "");
+ }
+
+ public HandlerConfiguration(String basetopic, String objectid) {
+ super();
+ this.basetopic = basetopic;
+ this.objectid = objectid;
+ }
+
+ /**
+ * Add the basetopic and objectid to the properties.
+ *
+ * @param properties
+ * @return the modified properties
+ */
+ public > T appendToProperties(T properties) {
+ properties.put("basetopic", basetopic);
+ properties.put("objectid", objectid);
+ return properties;
+ }
}
diff --git a/addons/binding/org.openhab.binding.mqtt.generic/src/main/java/org/openhab/binding/mqtt/generic/internal/discovery/HomeAssistantDiscovery.java b/addons/binding/org.openhab.binding.mqtt.generic/src/main/java/org/openhab/binding/mqtt/generic/internal/discovery/HomeAssistantDiscovery.java
index a2f3badc2070d..16b1a524523d0 100644
--- a/addons/binding/org.openhab.binding.mqtt.generic/src/main/java/org/openhab/binding/mqtt/generic/internal/discovery/HomeAssistantDiscovery.java
+++ b/addons/binding/org.openhab.binding.mqtt.generic/src/main/java/org/openhab/binding/mqtt/generic/internal/discovery/HomeAssistantDiscovery.java
@@ -31,9 +31,10 @@
import org.eclipse.smarthome.io.transport.mqtt.MqttBrokerConnection;
import org.openhab.binding.mqtt.discovery.MQTTTopicDiscoveryService;
import org.openhab.binding.mqtt.generic.internal.MqttBindingConstants;
-import org.openhab.binding.mqtt.generic.internal.convention.homeassistant.ChannelConfigurationTypeAdapterFactory;
import org.openhab.binding.mqtt.generic.internal.convention.homeassistant.BaseChannelConfiguration;
+import org.openhab.binding.mqtt.generic.internal.convention.homeassistant.ChannelConfigurationTypeAdapterFactory;
import org.openhab.binding.mqtt.generic.internal.convention.homeassistant.HaID;
+import org.openhab.binding.mqtt.generic.internal.convention.homeassistant.HandlerConfiguration;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.slf4j.Logger;
@@ -129,9 +130,9 @@ public void receivedMessage(ThingUID connectionBridge, MqttBrokerConnection conn
// We will of course find multiple of the same unique Thing IDs, for each different component another one.
// Therefore the components are assembled into a list and given to the DiscoveryResult label for the user to
- // easily recognise object capabilities.
+ // easily recognize object capabilities.
HaID topicParts = determineTopicParts(topic);
- final String thingID = topicParts.getThingID();
+ final String thingID = topicParts.objectID;
final ThingUID thingUID = new ThingUID(MqttBindingConstants.HOMEASSISTANT_MQTT_THING, connectionBridge,
thingID);
@@ -155,12 +156,12 @@ public void receivedMessage(ThingUID connectionBridge, MqttBrokerConnection conn
final String componentNames = components.stream().map(c -> HA_COMP_TO_NAME.getOrDefault(c, c))
.collect(Collectors.joining(","));
- BaseChannelConfiguration config = BaseChannelConfiguration.fromString(new String(payload, StandardCharsets.UTF_8), gson);
+ BaseChannelConfiguration config = BaseChannelConfiguration
+ .fromString(new String(payload, StandardCharsets.UTF_8), gson);
Map properties = new HashMap<>();
- properties.put("objectid", topicParts.objectID);
- properties.put("nodeid", topicParts.nodeID);
- properties.put("basetopic", BASE_TOPIC);
+ HandlerConfiguration handlerConfig = topicParts.toHandlerConfiguration();
+ properties = handlerConfig.appendToProperties(properties);
config.addDeviceProperties(properties);
// First remove an already discovered thing with the same ID
thingRemoved(thingUID);
@@ -175,7 +176,7 @@ public void topicVanished(ThingUID connectionBridge, MqttBrokerConnection connec
if (!topic.endsWith("/config")) {
return;
}
- final String thingID = determineTopicParts(topic).getThingID();
+ final String thingID = determineTopicParts(topic).objectID;
componentsPerThingID.remove(thingID);
thingRemoved(new ThingUID(MqttBindingConstants.HOMEASSISTANT_MQTT_THING, connectionBridge, thingID));
}
diff --git a/addons/binding/org.openhab.binding.mqtt.generic/src/main/java/org/openhab/binding/mqtt/generic/internal/handler/HomeAssistantThingHandler.java b/addons/binding/org.openhab.binding.mqtt.generic/src/main/java/org/openhab/binding/mqtt/generic/internal/handler/HomeAssistantThingHandler.java
index 217d03f998b4a..a0e907f6e8549 100644
--- a/addons/binding/org.openhab.binding.mqtt.generic/src/main/java/org/openhab/binding/mqtt/generic/internal/handler/HomeAssistantThingHandler.java
+++ b/addons/binding/org.openhab.binding.mqtt.generic/src/main/java/org/openhab/binding/mqtt/generic/internal/handler/HomeAssistantThingHandler.java
@@ -22,6 +22,7 @@
import java.util.concurrent.TimeoutException;
import java.util.function.Consumer;
+import org.apache.commons.lang.StringUtils;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.smarthome.core.thing.Channel;
@@ -29,12 +30,13 @@
import org.eclipse.smarthome.core.thing.Thing;
import org.eclipse.smarthome.core.thing.ThingStatus;
import org.eclipse.smarthome.core.thing.ThingStatusDetail;
+import org.eclipse.smarthome.core.thing.ThingUID;
import org.eclipse.smarthome.io.transport.mqtt.MqttBrokerConnection;
import org.openhab.binding.mqtt.generic.internal.convention.homeassistant.AbstractComponent;
import org.openhab.binding.mqtt.generic.internal.convention.homeassistant.CChannel;
import org.openhab.binding.mqtt.generic.internal.convention.homeassistant.CFactory;
-import org.openhab.binding.mqtt.generic.internal.convention.homeassistant.DiscoverComponents;
import org.openhab.binding.mqtt.generic.internal.convention.homeassistant.ChannelConfigurationTypeAdapterFactory;
+import org.openhab.binding.mqtt.generic.internal.convention.homeassistant.DiscoverComponents;
import org.openhab.binding.mqtt.generic.internal.convention.homeassistant.DiscoverComponents.ComponentDiscovered;
import org.openhab.binding.mqtt.generic.internal.convention.homeassistant.HaID;
import org.openhab.binding.mqtt.generic.internal.convention.homeassistant.HandlerConfiguration;
@@ -78,7 +80,7 @@ public class HomeAssistantThingHandler extends AbstractMQTTThingHandler
protected final Map> haComponents = new HashMap<>();
protected HandlerConfiguration config = new HandlerConfiguration();
- private HaID discoveryHomeAssistantID = new HaID("", "", "", "");
+ private HaID discoveryHomeAssistantID = new HaID();
protected final TransformationServiceProvider transformationServiceProvider;
@@ -108,11 +110,11 @@ public HomeAssistantThingHandler(Thing thing, MqttChannelTypeProvider channelTyp
@Override
public void initialize() {
config = getConfigAs(HandlerConfiguration.class);
- if (config.objectid.isEmpty()) {
+ if (StringUtils.isEmpty(config.objectid)) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Device ID unknown");
return;
}
- discoveryHomeAssistantID = new HaID(config.basetopic, config.objectid, "", "");
+ discoveryHomeAssistantID = HaID.fromConfig(config);
for (Channel channel : thing.getChannels()) {
final String groupID = channel.getUID().getGroupId();
@@ -127,7 +129,15 @@ public void initialize() {
continue;
}
- component = CFactory.createComponent(config.basetopic, channel, this, gson, transformationServiceProvider);
+ HaID haID = HaID.fromConfig(config.basetopic, channel.getConfiguration());
+ 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 {
+ component = CFactory.createComponent(thingUID, haID, channelConfigurationJSON, this, gson,
+ transformationServiceProvider);
+ }
if (component != null) {
haComponents.put(component.uid().getId(), component);