2 * Copyright (c) 2010-2020 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.mqtt.homeassistant.internal;
15 import java.util.ArrayList;
16 import java.util.Collection;
18 import org.apache.commons.lang.StringUtils;
19 import org.eclipse.jdt.annotation.NonNullByDefault;
20 import org.eclipse.jdt.annotation.Nullable;
21 import org.openhab.core.config.core.Configuration;
22 import org.openhab.core.util.UIDUtils;
25 * HomeAssistant MQTT components use a specific MQTT topic layout,
26 * starting with a base prefix (usually "homeassistant"),
27 * followed by the component id, an optional node id and the object id.
29 * This helper class can split up an MQTT topic into such parts.
31 * Implementation note: This is an immutable class.
33 * @author David Graeff - Initial contribution
37 public final String baseTopic;
38 public final String component;
39 public final String nodeID;
40 public final String objectID;
42 private final String topic;
45 * Creates a {@link HaID} object for a given HomeAssistant MQTT topic.
47 * @param mqttTopic A topic like "homeassistant/binary_sensor/garden/config" or
48 * "homeassistant/binary_sensor/0/garden/config"
50 public HaID(String mqttTopic) {
51 String[] strings = mqttTopic.split("/");
52 if (strings.length < 4 || strings.length > 5) {
53 throw new IllegalArgumentException("MQTT topic not a HomeAssistant topic (wrong length)!");
55 if (!"config".equals(strings[strings.length - 1])) {
56 throw new IllegalArgumentException("MQTT topic not a HomeAssistant topic ('config' missing)!");
59 baseTopic = strings[0];
60 component = strings[1];
62 if (strings.length == 5) {
64 objectID = strings[3];
67 objectID = strings[2];
70 this.topic = createTopic(this);
78 * Creates a {@link HaID} by providing all components separately.
80 * @param baseTopic The base topic. Usually "homeassistant".
81 * @param objectID The object ID
82 * @param nodeID The node ID (can be the empty string)
83 * @param component The component ID
85 private HaID(String baseTopic, String objectID, String nodeID, String component) {
86 this.baseTopic = baseTopic;
87 this.objectID = objectID;
89 this.component = component;
90 this.topic = createTopic(this);
93 private static final String createTopic(HaID id) {
94 StringBuilder str = new StringBuilder();
95 str.append(id.baseTopic).append('/').append(id.component).append('/');
96 if (StringUtils.isNotBlank(id.nodeID)) {
97 str.append(id.nodeID).append('/');
99 str.append(id.objectID).append('/');
100 return str.toString();
104 * Extract the HaID information from a channel configuration.
106 * <code>objectid</code>, <code>nodeid</code>, and <code>component</code> values are fetched from the configuration.
110 * @return newly created HaID
112 public static HaID fromConfig(String baseTopic, Configuration config) {
113 String component = (String) config.get("component");
114 String nodeID = (String) config.getProperties().getOrDefault("nodeid", "");
115 String objectID = (String) config.get("objectid");
116 return new HaID(baseTopic, objectID, nodeID, component);
120 * Add the HaID information to a channel configuration.
122 * <code>objectid</code>, <code>nodeid</code>, and <code>component</code> values are added to the configuration.
125 * @return the modified configuration
127 public Configuration toConfig(Configuration config) {
128 config.put("objectid", objectID);
129 config.put("nodeid", nodeID);
130 config.put("component", component);
135 * Extract the HaID information from a thing configuration.
137 * <code>basetpoic</code> and <code>objectid</code> are taken from the configuration.
138 * The <code>objectid</code> string may be in the form <code>nodeid/objectid</code>.
140 * The <code>component</code> component in the resulting HaID will be set to <code>+</code>.
141 * This enables the HaID to be used as an mqtt subscription topic.
144 * @return newly created HaID
146 public static Collection<HaID> fromConfig(HandlerConfiguration config) {
147 Collection<HaID> result = new ArrayList<>();
149 for (String topic : config.topics) {
150 String[] parts = topic.split("/");
152 switch (parts.length) {
154 result.add(new HaID(config.basetopic, parts[1], "", parts[0]));
157 result.add(new HaID(config.basetopic, parts[2], parts[1], parts[0]));
160 throw new IllegalArgumentException(
161 "Bad configuration. topic must be <component>/<objectId> or <component>/<nodeId>/<objectId>!");
168 * Return the topic to put into the HandlerConfiguration for this component.
170 * <code>objectid</code> in the thing configuration will be
171 * <code>nodeID/objectID<code> from the HaID, if <code>nodeID</code> is not empty.
174 * @return the short topic.
176 public String toShortTopic() {
177 String objectID = this.objectID;
178 if (StringUtils.isNotBlank(nodeID)) {
179 objectID = nodeID + "/" + objectID;
182 return component + "/" + objectID;
186 * The default group id is the unique_id of the component, given in the config-json.
187 * If the unique id is not set, then a fallback is constructed from the HaID information.
191 public String getGroupId(@Nullable final String uniqueId) {
192 String result = uniqueId;
194 // the null test is only here so the compile knows, result is not null afterwards
195 if (result == null || StringUtils.isBlank(result)) {
196 StringBuilder str = new StringBuilder();
198 if (StringUtils.isNotBlank(nodeID)) {
199 str.append(nodeID).append('_');
201 str.append(objectID).append('_').append(component);
202 result = str.toString();
205 return UIDUtils.encode(result);
209 * Return a topic, which can be used for a mqtt subscription.
210 * Defined values for suffix are:
216 * @return fallback group id
218 public String getTopic(String suffix) {
219 return topic + suffix;
223 public int hashCode() {
224 final int prime = 31;
226 result = prime * result + baseTopic.hashCode();
227 result = prime * result + component.hashCode();
228 result = prime * result + nodeID.hashCode();
229 result = prime * result + objectID.hashCode();
234 public boolean equals(@Nullable Object obj) {
241 if (getClass() != obj.getClass()) {
244 HaID other = (HaID) obj;
245 if (!baseTopic.equals(other.baseTopic)) {
248 if (!component.equals(other.component)) {
251 if (!nodeID.equals(other.nodeID)) {
254 if (!objectID.equals(other.objectID)) {
261 public String toString() {