]> git.basschouten.com Git - openhab-addons.git/blob
ebe696ee01aa5e5087dd02ac38ccc81e3116b6df
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
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
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.mqtt.homeassistant.internal.component;
14
15 import java.util.List;
16 import java.util.Map;
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;
22
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;
44
45 import com.google.gson.Gson;
46
47 /**
48  * A HomeAssistant component is comparable to a channel group.
49  * It has a name and consists of multiple channels.
50  *
51  * @author David Graeff - Initial contribution
52  * @param <C> Config class derived from {@link AbstractChannelConfiguration}
53  */
54 @NonNullByDefault
55 public abstract class AbstractComponent<C extends AbstractChannelConfiguration> {
56     private static final String JINJA_PREFIX = "JINJA:";
57
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;
63
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;
71
72     protected boolean configSeen;
73
74     /**
75      * Creates component based on generic configuration and component configuration type.
76      *
77      * @param componentConfiguration generic componentConfiguration with not parsed JSON config
78      * @param clazz target configuration type
79      */
80     public AbstractComponent(ComponentFactory.ComponentConfiguration componentConfiguration, Class<C> clazz) {
81         this.componentConfiguration = componentConfiguration;
82
83         this.channelConfigurationJson = componentConfiguration.getConfigJSON();
84         this.channelConfiguration = componentConfiguration.getConfig(clazz);
85         this.configHash = channelConfigurationJson.hashCode();
86
87         this.haID = componentConfiguration.getHaID();
88
89         String name = channelConfiguration.getName();
90         if (name != null && !name.isEmpty()) {
91             String groupId = this.haID.getGroupId(channelConfiguration.getUniqueId());
92
93             this.channelGroupTypeUID = new ChannelGroupTypeUID(MqttBindingConstants.BINDING_ID, groupId);
94             this.channelGroupUID = new ChannelGroupUID(componentConfiguration.getThingUID(), groupId);
95         } else {
96             this.channelGroupTypeUID = null;
97             this.channelGroupUID = null;
98         }
99
100         this.configSeen = false;
101
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;
107             }
108             componentConfiguration.getTracker().addAvailabilityTopic(availabilityTopic,
109                     this.channelConfiguration.getPayloadAvailable(), this.channelConfiguration.getPayloadNotAvailable(),
110                     availabilityTemplate, componentConfiguration.getTransformationServiceProvider());
111         }
112     }
113
114     protected ComponentChannel.Builder buildChannel(String channelID, Value valueState, String label,
115             ChannelStateUpdateListener channelStateUpdateListener) {
116         return new ComponentChannel.Builder(this, channelID, valueState, label, channelStateUpdateListener);
117     }
118
119     public void setConfigSeen() {
120         this.configSeen = true;
121     }
122
123     /**
124      * Subscribes to all state channels of the component and adds all channels to the provided channel type provider.
125      *
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
130      *         errors.
131      */
132     public CompletableFuture<@Nullable Void> start(MqttBrokerConnection connection, ScheduledExecutorService scheduler,
133             int timeout) {
134         return channels.values().stream().map(cChannel -> cChannel.start(connection, scheduler, timeout))
135                 .collect(FutureCollector.allOf());
136     }
137
138     /**
139      * Unsubscribes from all state channels of the component.
140      *
141      * @return A future that completes as soon as all subscriptions removals have been performed. Completes
142      *         exceptionally on errors.
143      */
144     public CompletableFuture<@Nullable Void> stop() {
145         return channels.values().stream().map(ComponentChannel::stop).collect(FutureCollector.allOf());
146     }
147
148     /**
149      * Add all channel types to the channel type provider.
150      *
151      * @param channelTypeProvider The channel type provider
152      */
153     public void addChannelTypes(MqttChannelTypeProvider channelTypeProvider) {
154         ChannelGroupTypeUID groupTypeUID = channelGroupTypeUID;
155         if (groupTypeUID != null) {
156             channelTypeProvider.setChannelGroupType(groupTypeUID, Objects.requireNonNull(getType()));
157         }
158         channels.values().forEach(v -> v.addChannelTypes(channelTypeProvider));
159     }
160
161     /**
162      * Removes all channels from the channel type provider.
163      * Call this if the corresponding Thing handler gets disposed.
164      *
165      * @param channelTypeProvider The channel type provider
166      */
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);
172         }
173     }
174
175     public ChannelUID buildChannelUID(String channelID) {
176         final ChannelGroupUID groupUID = channelGroupUID;
177         if (groupUID != null) {
178             return new ChannelUID(groupUID, channelID);
179         }
180         return new ChannelUID(componentConfiguration.getThingUID(), channelID);
181     }
182
183     /**
184      * Each HomeAssistant component corresponds to a Channel Group Type.
185      */
186     public @Nullable ChannelGroupTypeUID getGroupTypeUID() {
187         return channelGroupTypeUID;
188     }
189
190     /**
191      * The unique id of this component.
192      */
193     public @Nullable ChannelGroupUID getGroupUID() {
194         return channelGroupUID;
195     }
196
197     /**
198      * Component (Channel Group) name.
199      */
200     public String getName() {
201         String result = channelConfiguration.getName();
202
203         Device device = channelConfiguration.getDevice();
204         if (result == null && device != null) {
205             result = device.getName();
206         }
207         if (result == null) {
208             result = haID.objectID;
209         }
210         return result;
211     }
212
213     /**
214      * Each component consists of multiple Channels.
215      */
216     public Map<String, ComponentChannel> getChannelMap() {
217         return channels;
218     }
219
220     /**
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}.
224      *
225      * @param channelID The channel ID
226      * @return A components channel
227      */
228     public @Nullable ComponentChannel getChannel(String channelID) {
229         return channels.get(channelID);
230     }
231
232     /**
233      * @return Returns the configuration hash value for easy comparison.
234      */
235     public int getConfigHash() {
236         return configHash;
237     }
238
239     /**
240      * Return the channel group type.
241      */
242     public @Nullable ChannelGroupType getType() {
243         ChannelGroupTypeUID groupTypeUID = channelGroupTypeUID;
244         if (groupTypeUID == null) {
245             return null;
246         }
247         final List<ChannelDefinition> channelDefinitions = channels.values().stream().map(ComponentChannel::type)
248                 .collect(Collectors.toList());
249         return ChannelGroupTypeBuilder.instance(groupTypeUID, getName()).withChannelDefinitions(channelDefinitions)
250                 .build();
251     }
252
253     public List<ChannelDefinition> getChannels() {
254         return channels.values().stream().map(ComponentChannel::type).collect(Collectors.toList());
255     }
256
257     /**
258      * Resets all channel states to state UNDEF. Call this method after the connection
259      * to the MQTT broker got lost.
260      */
261     public void resetState() {
262         channels.values().forEach(ComponentChannel::resetState);
263     }
264
265     /**
266      * Return the channel group definition for this component.
267      */
268     public @Nullable ChannelGroupDefinition getGroupDefinition() {
269         ChannelGroupTypeUID groupTypeUID = channelGroupTypeUID;
270         if (groupTypeUID == null) {
271             return null;
272         }
273         return new ChannelGroupDefinition(channelGroupUID.getId(), groupTypeUID, getName(), null);
274     }
275
276     public HaID getHaID() {
277         return haID;
278     }
279
280     public String getChannelConfigurationJson() {
281         return channelConfigurationJson;
282     }
283
284     @Nullable
285     public TransformationServiceProvider getTransformationServiceProvider() {
286         return componentConfiguration.getTransformationServiceProvider();
287     }
288
289     public boolean isEnabledByDefault() {
290         return channelConfiguration.isEnabledByDefault();
291     }
292
293     public Gson getGson() {
294         return componentConfiguration.getGson();
295     }
296 }