]> git.basschouten.com Git - openhab-addons.git/blob
2105bfc3bf0102ea45774089363dd01c185a858f
[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.AvailabilityTracker;
26 import org.openhab.binding.mqtt.generic.ChannelStateUpdateListener;
27 import org.openhab.binding.mqtt.generic.MqttChannelTypeProvider;
28 import org.openhab.binding.mqtt.generic.TransformationServiceProvider;
29 import org.openhab.binding.mqtt.generic.utils.FutureCollector;
30 import org.openhab.binding.mqtt.generic.values.Value;
31 import org.openhab.binding.mqtt.homeassistant.generic.internal.MqttBindingConstants;
32 import org.openhab.binding.mqtt.homeassistant.internal.ComponentChannel;
33 import org.openhab.binding.mqtt.homeassistant.internal.HaID;
34 import org.openhab.binding.mqtt.homeassistant.internal.component.ComponentFactory.ComponentConfiguration;
35 import org.openhab.binding.mqtt.homeassistant.internal.config.dto.AbstractChannelConfiguration;
36 import org.openhab.binding.mqtt.homeassistant.internal.config.dto.Availability;
37 import org.openhab.binding.mqtt.homeassistant.internal.config.dto.AvailabilityMode;
38 import org.openhab.binding.mqtt.homeassistant.internal.config.dto.Device;
39 import org.openhab.core.io.transport.mqtt.MqttBrokerConnection;
40 import org.openhab.core.thing.ChannelGroupUID;
41 import org.openhab.core.thing.ChannelUID;
42 import org.openhab.core.thing.type.ChannelDefinition;
43 import org.openhab.core.thing.type.ChannelGroupDefinition;
44 import org.openhab.core.thing.type.ChannelGroupType;
45 import org.openhab.core.thing.type.ChannelGroupTypeBuilder;
46 import org.openhab.core.thing.type.ChannelGroupTypeUID;
47
48 import com.google.gson.Gson;
49
50 /**
51  * A HomeAssistant component is comparable to a channel group.
52  * It has a name and consists of multiple channels.
53  *
54  * @author David Graeff - Initial contribution
55  * @param <C> Config class derived from {@link AbstractChannelConfiguration}
56  */
57 @NonNullByDefault
58 public abstract class AbstractComponent<C extends AbstractChannelConfiguration> {
59     private static final String JINJA_PREFIX = "JINJA:";
60
61     // Component location fields
62     private final ComponentConfiguration componentConfiguration;
63     protected final @Nullable ChannelGroupTypeUID channelGroupTypeUID;
64     protected final @Nullable ChannelGroupUID channelGroupUID;
65     protected final HaID haID;
66
67     // Channels and configuration
68     protected final Map<String, ComponentChannel> channels = new TreeMap<>();
69     // The hash code ({@link String#hashCode()}) of the configuration string
70     // Used to determine if a component has changed.
71     protected final int configHash;
72     protected final String channelConfigurationJson;
73     protected final C channelConfiguration;
74
75     protected boolean configSeen;
76
77     /**
78      * Creates component based on generic configuration and component configuration type.
79      *
80      * @param componentConfiguration generic componentConfiguration with not parsed JSON config
81      * @param clazz target configuration type
82      */
83     public AbstractComponent(ComponentFactory.ComponentConfiguration componentConfiguration, Class<C> clazz) {
84         this.componentConfiguration = componentConfiguration;
85
86         this.channelConfigurationJson = componentConfiguration.getConfigJSON();
87         this.channelConfiguration = componentConfiguration.getConfig(clazz);
88         this.configHash = channelConfigurationJson.hashCode();
89
90         this.haID = componentConfiguration.getHaID();
91
92         String name = channelConfiguration.getName();
93         if (name != null && !name.isEmpty()) {
94             String groupId = this.haID.getGroupId(channelConfiguration.getUniqueId());
95
96             this.channelGroupTypeUID = new ChannelGroupTypeUID(MqttBindingConstants.BINDING_ID, groupId);
97             this.channelGroupUID = new ChannelGroupUID(componentConfiguration.getThingUID(), groupId);
98         } else {
99             this.channelGroupTypeUID = null;
100             this.channelGroupUID = null;
101         }
102
103         this.configSeen = false;
104
105         final List<Availability> availabilities = channelConfiguration.getAvailability();
106         if (availabilities != null) {
107             AvailabilityMode mode = channelConfiguration.getAvailabilityMode();
108             AvailabilityTracker.AvailabilityMode availabilityTrackerMode = switch (mode) {
109                 case ALL -> AvailabilityTracker.AvailabilityMode.ALL;
110                 case ANY -> AvailabilityTracker.AvailabilityMode.ANY;
111                 case LATEST -> AvailabilityTracker.AvailabilityMode.LATEST;
112             };
113             componentConfiguration.getTracker().setAvailabilityMode(availabilityTrackerMode);
114             for (Availability availability : availabilities) {
115                 String availabilityTemplate = availability.getValueTemplate();
116                 if (availabilityTemplate != null) {
117                     availabilityTemplate = JINJA_PREFIX + availabilityTemplate;
118                 }
119                 componentConfiguration.getTracker().addAvailabilityTopic(availability.getTopic(),
120                         availability.getPayloadAvailable(), availability.getPayloadNotAvailable(), availabilityTemplate,
121                         componentConfiguration.getTransformationServiceProvider());
122             }
123         } else {
124             String availabilityTopic = this.channelConfiguration.getAvailabilityTopic();
125             if (availabilityTopic != null) {
126                 String availabilityTemplate = this.channelConfiguration.getAvailabilityTemplate();
127                 if (availabilityTemplate != null) {
128                     availabilityTemplate = JINJA_PREFIX + availabilityTemplate;
129                 }
130                 componentConfiguration.getTracker().addAvailabilityTopic(availabilityTopic,
131                         this.channelConfiguration.getPayloadAvailable(),
132                         this.channelConfiguration.getPayloadNotAvailable(), availabilityTemplate,
133                         componentConfiguration.getTransformationServiceProvider());
134             }
135         }
136     }
137
138     protected ComponentChannel.Builder buildChannel(String channelID, Value valueState, String label,
139             ChannelStateUpdateListener channelStateUpdateListener) {
140         return new ComponentChannel.Builder(this, channelID, valueState, label, channelStateUpdateListener);
141     }
142
143     public void setConfigSeen() {
144         this.configSeen = true;
145     }
146
147     /**
148      * Subscribes to all state channels of the component and adds all channels to the provided channel type provider.
149      *
150      * @param connection connection to the MQTT broker
151      * @param scheduler thing scheduler
152      * @param timeout channel subscription timeout
153      * @return A future that completes as soon as all subscriptions have been performed. Completes exceptionally on
154      *         errors.
155      */
156     public CompletableFuture<@Nullable Void> start(MqttBrokerConnection connection, ScheduledExecutorService scheduler,
157             int timeout) {
158         return channels.values().stream().map(cChannel -> cChannel.start(connection, scheduler, timeout))
159                 .collect(FutureCollector.allOf());
160     }
161
162     /**
163      * Unsubscribes from all state channels of the component.
164      *
165      * @return A future that completes as soon as all subscriptions removals have been performed. Completes
166      *         exceptionally on errors.
167      */
168     public CompletableFuture<@Nullable Void> stop() {
169         return channels.values().stream().map(ComponentChannel::stop).collect(FutureCollector.allOf());
170     }
171
172     /**
173      * Add all channel types to the channel type provider.
174      *
175      * @param channelTypeProvider The channel type provider
176      */
177     public void addChannelTypes(MqttChannelTypeProvider channelTypeProvider) {
178         ChannelGroupTypeUID groupTypeUID = channelGroupTypeUID;
179         if (groupTypeUID != null) {
180             channelTypeProvider.setChannelGroupType(groupTypeUID, Objects.requireNonNull(getType()));
181         }
182         channels.values().forEach(v -> v.addChannelTypes(channelTypeProvider));
183     }
184
185     /**
186      * Removes all channels from the channel type provider.
187      * Call this if the corresponding Thing handler gets disposed.
188      *
189      * @param channelTypeProvider The channel type provider
190      */
191     public void removeChannelTypes(MqttChannelTypeProvider channelTypeProvider) {
192         channels.values().forEach(v -> v.removeChannelTypes(channelTypeProvider));
193         ChannelGroupTypeUID groupTypeUID = channelGroupTypeUID;
194         if (groupTypeUID != null) {
195             channelTypeProvider.removeChannelGroupType(groupTypeUID);
196         }
197     }
198
199     public ChannelUID buildChannelUID(String channelID) {
200         final ChannelGroupUID groupUID = channelGroupUID;
201         if (groupUID != null) {
202             return new ChannelUID(groupUID, channelID);
203         }
204         return new ChannelUID(componentConfiguration.getThingUID(), channelID);
205     }
206
207     /**
208      * Each HomeAssistant component corresponds to a Channel Group Type.
209      */
210     public @Nullable ChannelGroupTypeUID getGroupTypeUID() {
211         return channelGroupTypeUID;
212     }
213
214     /**
215      * The unique id of this component.
216      */
217     public @Nullable ChannelGroupUID getGroupUID() {
218         return channelGroupUID;
219     }
220
221     /**
222      * Component (Channel Group) name.
223      */
224     public String getName() {
225         String result = channelConfiguration.getName();
226
227         Device device = channelConfiguration.getDevice();
228         if (result == null && device != null) {
229             result = device.getName();
230         }
231         if (result == null) {
232             result = haID.objectID;
233         }
234         return result;
235     }
236
237     /**
238      * Each component consists of multiple Channels.
239      */
240     public Map<String, ComponentChannel> getChannelMap() {
241         return channels;
242     }
243
244     /**
245      * Return a components channel. A HomeAssistant MQTT component consists of multiple functions
246      * and those are mapped to one or more channels. The channel IDs are constants within the
247      * derived Component, like the {@link Switch#SWITCH_CHANNEL_ID}.
248      *
249      * @param channelID The channel ID
250      * @return A components channel
251      */
252     public @Nullable ComponentChannel getChannel(String channelID) {
253         return channels.get(channelID);
254     }
255
256     /**
257      * @return Returns the configuration hash value for easy comparison.
258      */
259     public int getConfigHash() {
260         return configHash;
261     }
262
263     /**
264      * Return the channel group type.
265      */
266     public @Nullable ChannelGroupType getType() {
267         ChannelGroupTypeUID groupTypeUID = channelGroupTypeUID;
268         if (groupTypeUID == null) {
269             return null;
270         }
271         final List<ChannelDefinition> channelDefinitions = channels.values().stream().map(ComponentChannel::type)
272                 .collect(Collectors.toList());
273         return ChannelGroupTypeBuilder.instance(groupTypeUID, getName()).withChannelDefinitions(channelDefinitions)
274                 .build();
275     }
276
277     public List<ChannelDefinition> getChannels() {
278         return channels.values().stream().map(ComponentChannel::type).collect(Collectors.toList());
279     }
280
281     /**
282      * Resets all channel states to state UNDEF. Call this method after the connection
283      * to the MQTT broker got lost.
284      */
285     public void resetState() {
286         channels.values().forEach(ComponentChannel::resetState);
287     }
288
289     /**
290      * Return the channel group definition for this component.
291      */
292     public @Nullable ChannelGroupDefinition getGroupDefinition() {
293         ChannelGroupTypeUID groupTypeUID = channelGroupTypeUID;
294         if (groupTypeUID == null) {
295             return null;
296         }
297         return new ChannelGroupDefinition(channelGroupUID.getId(), groupTypeUID, getName(), null);
298     }
299
300     public HaID getHaID() {
301         return haID;
302     }
303
304     public String getChannelConfigurationJson() {
305         return channelConfigurationJson;
306     }
307
308     @Nullable
309     public TransformationServiceProvider getTransformationServiceProvider() {
310         return componentConfiguration.getTransformationServiceProvider();
311     }
312
313     public boolean isEnabledByDefault() {
314         return channelConfiguration.isEnabledByDefault();
315     }
316
317     public Gson getGson() {
318         return componentConfiguration.getGson();
319     }
320 }