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.TreeMap;
18 import java.util.concurrent.CompletableFuture;
19 import java.util.concurrent.ScheduledExecutorService;
20 import java.util.stream.Collectors;
22 import org.eclipse.jdt.annotation.NonNullByDefault;
23 import org.eclipse.jdt.annotation.Nullable;
24 import org.openhab.binding.mqtt.generic.ChannelStateUpdateListener;
25 import org.openhab.binding.mqtt.generic.MqttChannelTypeProvider;
26 import org.openhab.binding.mqtt.generic.TransformationServiceProvider;
27 import org.openhab.binding.mqtt.generic.utils.FutureCollector;
28 import org.openhab.binding.mqtt.generic.values.Value;
29 import org.openhab.binding.mqtt.homeassistant.generic.internal.MqttBindingConstants;
30 import org.openhab.binding.mqtt.homeassistant.internal.ComponentChannel;
31 import org.openhab.binding.mqtt.homeassistant.internal.HaID;
32 import org.openhab.binding.mqtt.homeassistant.internal.component.ComponentFactory.ComponentConfiguration;
33 import org.openhab.binding.mqtt.homeassistant.internal.config.dto.AbstractChannelConfiguration;
34 import org.openhab.core.io.transport.mqtt.MqttBrokerConnection;
35 import org.openhab.core.thing.ChannelGroupUID;
36 import org.openhab.core.thing.type.ChannelDefinition;
37 import org.openhab.core.thing.type.ChannelGroupDefinition;
38 import org.openhab.core.thing.type.ChannelGroupType;
39 import org.openhab.core.thing.type.ChannelGroupTypeBuilder;
40 import org.openhab.core.thing.type.ChannelGroupTypeUID;
42 import com.google.gson.Gson;
45 * A HomeAssistant component is comparable to a channel group.
46 * It has a name and consists of multiple channels.
48 * @author David Graeff - Initial contribution
49 * @param <C> Config class derived from {@link AbstractChannelConfiguration}
52 public abstract class AbstractComponent<C extends AbstractChannelConfiguration> {
53 private static final String JINJA_PREFIX = "JINJA:";
55 // Component location fields
56 private final ComponentConfiguration componentConfiguration;
57 protected final ChannelGroupTypeUID channelGroupTypeUID;
58 protected final ChannelGroupUID channelGroupUID;
59 protected final HaID haID;
61 // Channels and configuration
62 protected final Map<String, ComponentChannel> channels = new TreeMap<>();
63 // The hash code ({@link String#hashCode()}) of the configuration string
64 // Used to determine if a component has changed.
65 protected final int configHash;
66 protected final String channelConfigurationJson;
67 protected final C channelConfiguration;
69 protected boolean configSeen;
72 * Creates component based on generic configuration and component configuration type.
74 * @param componentConfiguration generic componentConfiguration with not parsed JSON config
75 * @param clazz target configuration type
77 public AbstractComponent(ComponentFactory.ComponentConfiguration componentConfiguration, Class<C> clazz) {
78 this.componentConfiguration = componentConfiguration;
80 this.channelConfigurationJson = componentConfiguration.getConfigJSON();
81 this.channelConfiguration = componentConfiguration.getConfig(clazz);
82 this.configHash = channelConfigurationJson.hashCode();
84 this.haID = componentConfiguration.getHaID();
86 String groupId = this.haID.getGroupId(channelConfiguration.getUniqueId());
88 this.channelGroupTypeUID = new ChannelGroupTypeUID(MqttBindingConstants.BINDING_ID, groupId);
89 this.channelGroupUID = new ChannelGroupUID(componentConfiguration.getThingUID(), groupId);
91 this.configSeen = false;
93 String availabilityTopic = this.channelConfiguration.getAvailabilityTopic();
94 if (availabilityTopic != null) {
95 String availabilityTemplate = this.channelConfiguration.getAvailabilityTemplate();
96 if (availabilityTemplate != null) {
97 availabilityTemplate = JINJA_PREFIX + availabilityTemplate;
99 componentConfiguration.getTracker().addAvailabilityTopic(availabilityTopic,
100 this.channelConfiguration.getPayloadAvailable(), this.channelConfiguration.getPayloadNotAvailable(),
101 availabilityTemplate, componentConfiguration.getTransformationServiceProvider());
105 protected ComponentChannel.Builder buildChannel(String channelID, Value valueState, String label,
106 ChannelStateUpdateListener channelStateUpdateListener) {
107 return new ComponentChannel.Builder(this, channelID, valueState, label, channelStateUpdateListener);
110 public void setConfigSeen() {
111 this.configSeen = true;
115 * Subscribes to all state channels of the component and adds all channels to the provided channel type provider.
117 * @param connection connection to the MQTT broker
118 * @param scheduler thing scheduler
119 * @param timeout channel subscription timeout
120 * @return A future that completes as soon as all subscriptions have been performed. Completes exceptionally on
123 public CompletableFuture<@Nullable Void> start(MqttBrokerConnection connection, ScheduledExecutorService scheduler,
125 return channels.values().stream().map(cChannel -> cChannel.start(connection, scheduler, timeout))
126 .collect(FutureCollector.allOf());
130 * Unsubscribes from all state channels of the component.
132 * @return A future that completes as soon as all subscriptions removals have been performed. Completes
133 * exceptionally on errors.
135 public CompletableFuture<@Nullable Void> stop() {
136 return channels.values().stream().map(ComponentChannel::stop).collect(FutureCollector.allOf());
140 * Add all channel types to the channel type provider.
142 * @param channelTypeProvider The channel type provider
144 public void addChannelTypes(MqttChannelTypeProvider channelTypeProvider) {
145 channelTypeProvider.setChannelGroupType(getGroupTypeUID(), getType());
146 channels.values().forEach(v -> v.addChannelTypes(channelTypeProvider));
150 * Removes all channels from the channel type provider.
151 * Call this if the corresponding Thing handler gets disposed.
153 * @param channelTypeProvider The channel type provider
155 public void removeChannelTypes(MqttChannelTypeProvider channelTypeProvider) {
156 channels.values().forEach(v -> v.removeChannelTypes(channelTypeProvider));
157 channelTypeProvider.removeChannelGroupType(getGroupTypeUID());
161 * Each HomeAssistant component corresponds to a Channel Group Type.
163 public ChannelGroupTypeUID getGroupTypeUID() {
164 return channelGroupTypeUID;
168 * The unique id of this component.
170 public ChannelGroupUID getGroupUID() {
171 return channelGroupUID;
175 * Component (Channel Group) name.
177 public String getName() {
178 return channelConfiguration.getName();
182 * Each component consists of multiple Channels.
184 public Map<String, ComponentChannel> getChannelMap() {
189 * Return a components channel. A HomeAssistant MQTT component consists of multiple functions
190 * and those are mapped to one or more channels. The channel IDs are constants within the
191 * derived Component, like the {@link Switch#SWITCH_CHANNEL_ID}.
193 * @param channelID The channel ID
194 * @return A components channel
196 public @Nullable ComponentChannel getChannel(String channelID) {
197 return channels.get(channelID);
201 * @return Returns the configuration hash value for easy comparison.
203 public int getConfigHash() {
208 * Return the channel group type.
210 public ChannelGroupType getType() {
211 final List<ChannelDefinition> channelDefinitions = channels.values().stream().map(ComponentChannel::type)
212 .collect(Collectors.toList());
213 return ChannelGroupTypeBuilder.instance(channelGroupTypeUID, getName())
214 .withChannelDefinitions(channelDefinitions).build();
218 * Resets all channel states to state UNDEF. Call this method after the connection
219 * to the MQTT broker got lost.
221 public void resetState() {
222 channels.values().forEach(ComponentChannel::resetState);
226 * Return the channel group definition for this component.
228 public ChannelGroupDefinition getGroupDefinition() {
229 return new ChannelGroupDefinition(channelGroupUID.getId(), getGroupTypeUID(), getName(), null);
232 public HaID getHaID() {
236 public String getChannelConfigurationJson() {
237 return channelConfigurationJson;
241 public TransformationServiceProvider getTransformationServiceProvider() {
242 return componentConfiguration.getTransformationServiceProvider();
245 public boolean isEnabledByDefault() {
246 return channelConfiguration.isEnabledByDefault();
249 public Gson getGson() {
250 return componentConfiguration.getGson();