2 * Copyright (c) 2010-2023 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.component;
15 import java.util.List;
17 import java.util.Objects;
18 import java.util.TreeMap;
19 import java.util.concurrent.CompletableFuture;
20 import java.util.concurrent.ScheduledExecutorService;
21 import java.util.stream.Collectors;
23 import org.eclipse.jdt.annotation.NonNullByDefault;
24 import org.eclipse.jdt.annotation.Nullable;
25 import org.openhab.binding.mqtt.generic.ChannelStateUpdateListener;
26 import org.openhab.binding.mqtt.generic.MqttChannelTypeProvider;
27 import org.openhab.binding.mqtt.generic.TransformationServiceProvider;
28 import org.openhab.binding.mqtt.generic.utils.FutureCollector;
29 import org.openhab.binding.mqtt.generic.values.Value;
30 import org.openhab.binding.mqtt.homeassistant.generic.internal.MqttBindingConstants;
31 import org.openhab.binding.mqtt.homeassistant.internal.ComponentChannel;
32 import org.openhab.binding.mqtt.homeassistant.internal.HaID;
33 import org.openhab.binding.mqtt.homeassistant.internal.component.ComponentFactory.ComponentConfiguration;
34 import org.openhab.binding.mqtt.homeassistant.internal.config.dto.AbstractChannelConfiguration;
35 import org.openhab.binding.mqtt.homeassistant.internal.config.dto.Device;
36 import org.openhab.core.io.transport.mqtt.MqttBrokerConnection;
37 import org.openhab.core.thing.ChannelGroupUID;
38 import org.openhab.core.thing.ChannelUID;
39 import org.openhab.core.thing.type.ChannelDefinition;
40 import org.openhab.core.thing.type.ChannelGroupDefinition;
41 import org.openhab.core.thing.type.ChannelGroupType;
42 import org.openhab.core.thing.type.ChannelGroupTypeBuilder;
43 import org.openhab.core.thing.type.ChannelGroupTypeUID;
45 import com.google.gson.Gson;
48 * A HomeAssistant component is comparable to a channel group.
49 * It has a name and consists of multiple channels.
51 * @author David Graeff - Initial contribution
52 * @param <C> Config class derived from {@link AbstractChannelConfiguration}
55 public abstract class AbstractComponent<C extends AbstractChannelConfiguration> {
56 private static final String JINJA_PREFIX = "JINJA:";
58 // Component location fields
59 private final ComponentConfiguration componentConfiguration;
60 protected final @Nullable ChannelGroupTypeUID channelGroupTypeUID;
61 protected final @Nullable ChannelGroupUID channelGroupUID;
62 protected final HaID haID;
64 // Channels and configuration
65 protected final Map<String, ComponentChannel> channels = new TreeMap<>();
66 // The hash code ({@link String#hashCode()}) of the configuration string
67 // Used to determine if a component has changed.
68 protected final int configHash;
69 protected final String channelConfigurationJson;
70 protected final C channelConfiguration;
72 protected boolean configSeen;
75 * Creates component based on generic configuration and component configuration type.
77 * @param componentConfiguration generic componentConfiguration with not parsed JSON config
78 * @param clazz target configuration type
80 public AbstractComponent(ComponentFactory.ComponentConfiguration componentConfiguration, Class<C> clazz) {
81 this.componentConfiguration = componentConfiguration;
83 this.channelConfigurationJson = componentConfiguration.getConfigJSON();
84 this.channelConfiguration = componentConfiguration.getConfig(clazz);
85 this.configHash = channelConfigurationJson.hashCode();
87 this.haID = componentConfiguration.getHaID();
89 String name = channelConfiguration.getName();
90 if (name != null && !name.isEmpty()) {
91 String groupId = this.haID.getGroupId(channelConfiguration.getUniqueId());
93 this.channelGroupTypeUID = new ChannelGroupTypeUID(MqttBindingConstants.BINDING_ID, groupId);
94 this.channelGroupUID = new ChannelGroupUID(componentConfiguration.getThingUID(), groupId);
96 this.channelGroupTypeUID = null;
97 this.channelGroupUID = null;
100 this.configSeen = false;
102 String availabilityTopic = this.channelConfiguration.getAvailabilityTopic();
103 if (availabilityTopic != null) {
104 String availabilityTemplate = this.channelConfiguration.getAvailabilityTemplate();
105 if (availabilityTemplate != null) {
106 availabilityTemplate = JINJA_PREFIX + availabilityTemplate;
108 componentConfiguration.getTracker().addAvailabilityTopic(availabilityTopic,
109 this.channelConfiguration.getPayloadAvailable(), this.channelConfiguration.getPayloadNotAvailable(),
110 availabilityTemplate, componentConfiguration.getTransformationServiceProvider());
114 protected ComponentChannel.Builder buildChannel(String channelID, Value valueState, String label,
115 ChannelStateUpdateListener channelStateUpdateListener) {
116 return new ComponentChannel.Builder(this, channelID, valueState, label, channelStateUpdateListener);
119 public void setConfigSeen() {
120 this.configSeen = true;
124 * Subscribes to all state channels of the component and adds all channels to the provided channel type provider.
126 * @param connection connection to the MQTT broker
127 * @param scheduler thing scheduler
128 * @param timeout channel subscription timeout
129 * @return A future that completes as soon as all subscriptions have been performed. Completes exceptionally on
132 public CompletableFuture<@Nullable Void> start(MqttBrokerConnection connection, ScheduledExecutorService scheduler,
134 return channels.values().stream().map(cChannel -> cChannel.start(connection, scheduler, timeout))
135 .collect(FutureCollector.allOf());
139 * Unsubscribes from all state channels of the component.
141 * @return A future that completes as soon as all subscriptions removals have been performed. Completes
142 * exceptionally on errors.
144 public CompletableFuture<@Nullable Void> stop() {
145 return channels.values().stream().map(ComponentChannel::stop).collect(FutureCollector.allOf());
149 * Add all channel types to the channel type provider.
151 * @param channelTypeProvider The channel type provider
153 public void addChannelTypes(MqttChannelTypeProvider channelTypeProvider) {
154 ChannelGroupTypeUID groupTypeUID = channelGroupTypeUID;
155 if (groupTypeUID != null) {
156 channelTypeProvider.setChannelGroupType(groupTypeUID, Objects.requireNonNull(getType()));
158 channels.values().forEach(v -> v.addChannelTypes(channelTypeProvider));
162 * Removes all channels from the channel type provider.
163 * Call this if the corresponding Thing handler gets disposed.
165 * @param channelTypeProvider The channel type provider
167 public void removeChannelTypes(MqttChannelTypeProvider channelTypeProvider) {
168 channels.values().forEach(v -> v.removeChannelTypes(channelTypeProvider));
169 ChannelGroupTypeUID groupTypeUID = channelGroupTypeUID;
170 if (groupTypeUID != null) {
171 channelTypeProvider.removeChannelGroupType(groupTypeUID);
175 public ChannelUID buildChannelUID(String channelID) {
176 final ChannelGroupUID groupUID = channelGroupUID;
177 if (groupUID != null) {
178 return new ChannelUID(groupUID, channelID);
180 return new ChannelUID(componentConfiguration.getThingUID(), channelID);
184 * Each HomeAssistant component corresponds to a Channel Group Type.
186 public @Nullable ChannelGroupTypeUID getGroupTypeUID() {
187 return channelGroupTypeUID;
191 * The unique id of this component.
193 public @Nullable ChannelGroupUID getGroupUID() {
194 return channelGroupUID;
198 * Component (Channel Group) name.
200 public String getName() {
201 String result = channelConfiguration.getName();
203 Device device = channelConfiguration.getDevice();
204 if (result == null && device != null) {
205 result = device.getName();
207 if (result == null) {
208 result = haID.objectID;
214 * Each component consists of multiple Channels.
216 public Map<String, ComponentChannel> getChannelMap() {
221 * Return a components channel. A HomeAssistant MQTT component consists of multiple functions
222 * and those are mapped to one or more channels. The channel IDs are constants within the
223 * derived Component, like the {@link Switch#SWITCH_CHANNEL_ID}.
225 * @param channelID The channel ID
226 * @return A components channel
228 public @Nullable ComponentChannel getChannel(String channelID) {
229 return channels.get(channelID);
233 * @return Returns the configuration hash value for easy comparison.
235 public int getConfigHash() {
240 * Return the channel group type.
242 public @Nullable ChannelGroupType getType() {
243 ChannelGroupTypeUID groupTypeUID = channelGroupTypeUID;
244 if (groupTypeUID == null) {
247 final List<ChannelDefinition> channelDefinitions = channels.values().stream().map(ComponentChannel::type)
248 .collect(Collectors.toList());
249 return ChannelGroupTypeBuilder.instance(groupTypeUID, getName()).withChannelDefinitions(channelDefinitions)
253 public List<ChannelDefinition> getChannels() {
254 return channels.values().stream().map(ComponentChannel::type).collect(Collectors.toList());
258 * Resets all channel states to state UNDEF. Call this method after the connection
259 * to the MQTT broker got lost.
261 public void resetState() {
262 channels.values().forEach(ComponentChannel::resetState);
266 * Return the channel group definition for this component.
268 public @Nullable ChannelGroupDefinition getGroupDefinition() {
269 ChannelGroupTypeUID groupTypeUID = channelGroupTypeUID;
270 if (groupTypeUID == null) {
273 return new ChannelGroupDefinition(channelGroupUID.getId(), groupTypeUID, getName(), null);
276 public HaID getHaID() {
280 public String getChannelConfigurationJson() {
281 return channelConfigurationJson;
285 public TransformationServiceProvider getTransformationServiceProvider() {
286 return componentConfiguration.getTransformationServiceProvider();
289 public boolean isEnabledByDefault() {
290 return channelConfiguration.isEnabledByDefault();
293 public Gson getGson() {
294 return componentConfiguration.getGson();