]> git.basschouten.com Git - openhab-addons.git/blob
87625305d300b84a1977ba1f758305761587f55b
[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         if (channelConfiguration.getName() != null) {
90             String groupId = this.haID.getGroupId(channelConfiguration.getUniqueId());
91
92             this.channelGroupTypeUID = new ChannelGroupTypeUID(MqttBindingConstants.BINDING_ID, groupId);
93             this.channelGroupUID = new ChannelGroupUID(componentConfiguration.getThingUID(), groupId);
94         } else {
95             this.channelGroupTypeUID = null;
96             this.channelGroupUID = null;
97         }
98
99         this.configSeen = false;
100
101         String availabilityTopic = this.channelConfiguration.getAvailabilityTopic();
102         if (availabilityTopic != null) {
103             String availabilityTemplate = this.channelConfiguration.getAvailabilityTemplate();
104             if (availabilityTemplate != null) {
105                 availabilityTemplate = JINJA_PREFIX + availabilityTemplate;
106             }
107             componentConfiguration.getTracker().addAvailabilityTopic(availabilityTopic,
108                     this.channelConfiguration.getPayloadAvailable(), this.channelConfiguration.getPayloadNotAvailable(),
109                     availabilityTemplate, componentConfiguration.getTransformationServiceProvider());
110         }
111     }
112
113     protected ComponentChannel.Builder buildChannel(String channelID, Value valueState, String label,
114             ChannelStateUpdateListener channelStateUpdateListener) {
115         return new ComponentChannel.Builder(this, channelID, valueState, label, channelStateUpdateListener);
116     }
117
118     public void setConfigSeen() {
119         this.configSeen = true;
120     }
121
122     /**
123      * Subscribes to all state channels of the component and adds all channels to the provided channel type provider.
124      *
125      * @param connection connection to the MQTT broker
126      * @param scheduler thing scheduler
127      * @param timeout channel subscription timeout
128      * @return A future that completes as soon as all subscriptions have been performed. Completes exceptionally on
129      *         errors.
130      */
131     public CompletableFuture<@Nullable Void> start(MqttBrokerConnection connection, ScheduledExecutorService scheduler,
132             int timeout) {
133         return channels.values().stream().map(cChannel -> cChannel.start(connection, scheduler, timeout))
134                 .collect(FutureCollector.allOf());
135     }
136
137     /**
138      * Unsubscribes from all state channels of the component.
139      *
140      * @return A future that completes as soon as all subscriptions removals have been performed. Completes
141      *         exceptionally on errors.
142      */
143     public CompletableFuture<@Nullable Void> stop() {
144         return channels.values().stream().map(ComponentChannel::stop).collect(FutureCollector.allOf());
145     }
146
147     /**
148      * Add all channel types to the channel type provider.
149      *
150      * @param channelTypeProvider The channel type provider
151      */
152     public void addChannelTypes(MqttChannelTypeProvider channelTypeProvider) {
153         ChannelGroupTypeUID groupTypeUID = channelGroupTypeUID;
154         if (groupTypeUID != null) {
155             channelTypeProvider.setChannelGroupType(groupTypeUID, Objects.requireNonNull(getType()));
156         }
157         channels.values().forEach(v -> v.addChannelTypes(channelTypeProvider));
158     }
159
160     /**
161      * Removes all channels from the channel type provider.
162      * Call this if the corresponding Thing handler gets disposed.
163      *
164      * @param channelTypeProvider The channel type provider
165      */
166     public void removeChannelTypes(MqttChannelTypeProvider channelTypeProvider) {
167         channels.values().forEach(v -> v.removeChannelTypes(channelTypeProvider));
168         ChannelGroupTypeUID groupTypeUID = channelGroupTypeUID;
169         if (groupTypeUID != null) {
170             channelTypeProvider.removeChannelGroupType(groupTypeUID);
171         }
172     }
173
174     public ChannelUID buildChannelUID(String channelID) {
175         final ChannelGroupUID groupUID = channelGroupUID;
176         if (groupUID != null) {
177             return new ChannelUID(groupUID, channelID);
178         }
179         return new ChannelUID(componentConfiguration.getThingUID(), channelID);
180     }
181
182     /**
183      * Each HomeAssistant component corresponds to a Channel Group Type.
184      */
185     public @Nullable ChannelGroupTypeUID getGroupTypeUID() {
186         return channelGroupTypeUID;
187     }
188
189     /**
190      * The unique id of this component.
191      */
192     public @Nullable ChannelGroupUID getGroupUID() {
193         return channelGroupUID;
194     }
195
196     /**
197      * Component (Channel Group) name.
198      */
199     public String getName() {
200         String result = channelConfiguration.getName();
201
202         Device device = channelConfiguration.getDevice();
203         if (result == null && device != null) {
204             result = device.getName();
205         }
206         if (result == null) {
207             result = haID.objectID;
208         }
209         return result;
210     }
211
212     /**
213      * Each component consists of multiple Channels.
214      */
215     public Map<String, ComponentChannel> getChannelMap() {
216         return channels;
217     }
218
219     /**
220      * Return a components channel. A HomeAssistant MQTT component consists of multiple functions
221      * and those are mapped to one or more channels. The channel IDs are constants within the
222      * derived Component, like the {@link Switch#SWITCH_CHANNEL_ID}.
223      *
224      * @param channelID The channel ID
225      * @return A components channel
226      */
227     public @Nullable ComponentChannel getChannel(String channelID) {
228         return channels.get(channelID);
229     }
230
231     /**
232      * @return Returns the configuration hash value for easy comparison.
233      */
234     public int getConfigHash() {
235         return configHash;
236     }
237
238     /**
239      * Return the channel group type.
240      */
241     public @Nullable ChannelGroupType getType() {
242         ChannelGroupTypeUID groupTypeUID = channelGroupTypeUID;
243         if (groupTypeUID == null) {
244             return null;
245         }
246         final List<ChannelDefinition> channelDefinitions = channels.values().stream().map(ComponentChannel::type)
247                 .collect(Collectors.toList());
248         return ChannelGroupTypeBuilder.instance(groupTypeUID, getName()).withChannelDefinitions(channelDefinitions)
249                 .build();
250     }
251
252     public List<ChannelDefinition> getChannels() {
253         return channels.values().stream().map(ComponentChannel::type).collect(Collectors.toList());
254     }
255
256     /**
257      * Resets all channel states to state UNDEF. Call this method after the connection
258      * to the MQTT broker got lost.
259      */
260     public void resetState() {
261         channels.values().forEach(ComponentChannel::resetState);
262     }
263
264     /**
265      * Return the channel group definition for this component.
266      */
267     public @Nullable ChannelGroupDefinition getGroupDefinition() {
268         ChannelGroupTypeUID groupTypeUID = channelGroupTypeUID;
269         if (groupTypeUID == null) {
270             return null;
271         }
272         return new ChannelGroupDefinition(channelGroupUID.getId(), groupTypeUID, getName(), null);
273     }
274
275     public HaID getHaID() {
276         return haID;
277     }
278
279     public String getChannelConfigurationJson() {
280         return channelConfigurationJson;
281     }
282
283     @Nullable
284     public TransformationServiceProvider getTransformationServiceProvider() {
285         return componentConfiguration.getTransformationServiceProvider();
286     }
287
288     public boolean isEnabledByDefault() {
289         return channelConfiguration.isEnabledByDefault();
290     }
291
292     public Gson getGson() {
293         return componentConfiguration.getGson();
294     }
295 }