]> git.basschouten.com Git - openhab-addons.git/blob
41f66df011ea07c25e3c8d53782f68562e94e518
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2024 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;
14
15 import java.net.URI;
16 import java.util.concurrent.CompletableFuture;
17 import java.util.concurrent.ScheduledExecutorService;
18 import java.util.function.Predicate;
19
20 import org.eclipse.jdt.annotation.NonNullByDefault;
21 import org.eclipse.jdt.annotation.Nullable;
22 import org.openhab.binding.mqtt.generic.ChannelConfigBuilder;
23 import org.openhab.binding.mqtt.generic.ChannelState;
24 import org.openhab.binding.mqtt.generic.ChannelStateTransformation;
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.values.Value;
29 import org.openhab.binding.mqtt.homeassistant.generic.internal.MqttBindingConstants;
30 import org.openhab.binding.mqtt.homeassistant.internal.component.AbstractComponent;
31 import org.openhab.core.config.core.Configuration;
32 import org.openhab.core.io.transport.mqtt.MqttBrokerConnection;
33 import org.openhab.core.thing.Channel;
34 import org.openhab.core.thing.ChannelUID;
35 import org.openhab.core.thing.binding.builder.ChannelBuilder;
36 import org.openhab.core.thing.type.AutoUpdatePolicy;
37 import org.openhab.core.thing.type.ChannelDefinition;
38 import org.openhab.core.thing.type.ChannelDefinitionBuilder;
39 import org.openhab.core.thing.type.ChannelType;
40 import org.openhab.core.thing.type.ChannelTypeBuilder;
41 import org.openhab.core.thing.type.ChannelTypeUID;
42 import org.openhab.core.types.Command;
43 import org.openhab.core.types.CommandDescription;
44 import org.openhab.core.types.StateDescriptionFragment;
45
46 /**
47  * An {@link AbstractComponent}s derived class consists of one or multiple channels.
48  * Each component channel consists of the determined channel type, channel type UID and the
49  * channel description itself as well as the the channels state.
50  *
51  * After the discovery process has completed and the tree of components and component channels
52  * have been built up, the channel types are registered to a custom channel type provider
53  * before adding the channel descriptions to the Thing themselves.
54  * <br>
55  * <br>
56  * An object of this class creates the required {@link ChannelType} and {@link ChannelTypeUID} as well
57  * as keeps the {@link ChannelState} and {@link Channel} in one place.
58  *
59  * @author David Graeff - Initial contribution
60  */
61 @NonNullByDefault
62 public class ComponentChannel {
63     private static final String JINJA = "JINJA";
64
65     private final ChannelUID channelUID;
66     private final ChannelState channelState;
67     private final Channel channel;
68     private final ChannelType type;
69     private final ChannelTypeUID channelTypeUID;
70     private final ChannelStateUpdateListener channelStateUpdateListener;
71
72     private ComponentChannel(ChannelUID channelUID, ChannelState channelState, Channel channel, ChannelType type,
73             ChannelTypeUID channelTypeUID, ChannelStateUpdateListener channelStateUpdateListener) {
74         super();
75         this.channelUID = channelUID;
76         this.channelState = channelState;
77         this.channel = channel;
78         this.type = type;
79         this.channelTypeUID = channelTypeUID;
80         this.channelStateUpdateListener = channelStateUpdateListener;
81     }
82
83     public ChannelUID getChannelUID() {
84         return channelUID;
85     }
86
87     public Channel getChannel() {
88         return channel;
89     }
90
91     public ChannelState getState() {
92         return channelState;
93     }
94
95     public CompletableFuture<@Nullable Void> stop() {
96         return channelState.stop();
97     }
98
99     public CompletableFuture<@Nullable Void> start(MqttBrokerConnection connection, ScheduledExecutorService scheduler,
100             int timeout) {
101         // Make sure we set the callback again which might have been nulled during a stop
102         channelState.setChannelStateUpdateListener(this.channelStateUpdateListener);
103
104         return channelState.start(connection, scheduler, timeout);
105     }
106
107     public void addChannelTypes(MqttChannelTypeProvider channelTypeProvider) {
108         channelTypeProvider.setChannelType(channelTypeUID, type);
109     }
110
111     public void removeChannelTypes(MqttChannelTypeProvider channelTypeProvider) {
112         channelTypeProvider.removeChannelType(channelTypeUID);
113     }
114
115     public ChannelDefinition type() {
116         return new ChannelDefinitionBuilder(channelUID.getId(), channelTypeUID).build();
117     }
118
119     public void resetState() {
120         channelState.getCache().resetState();
121     }
122
123     public static class Builder {
124         private final AbstractComponent<?> component;
125         private final String channelID;
126         private final Value valueState;
127         private final String label;
128         private final ChannelStateUpdateListener channelStateUpdateListener;
129
130         private @Nullable String stateTopic;
131         private @Nullable String commandTopic;
132         private boolean retain;
133         private boolean trigger;
134         private boolean isAdvanced;
135         private @Nullable AutoUpdatePolicy autoUpdatePolicy;
136         private @Nullable Integer qos;
137         private @Nullable Predicate<Command> commandFilter;
138
139         private @Nullable String templateIn;
140         private @Nullable String templateOut;
141
142         private String format = "%s";
143
144         public Builder(AbstractComponent<?> component, String channelID, Value valueState, String label,
145                 ChannelStateUpdateListener channelStateUpdateListener) {
146             this.component = component;
147             this.channelID = channelID;
148             this.valueState = valueState;
149             this.label = label;
150             this.isAdvanced = false;
151             this.channelStateUpdateListener = channelStateUpdateListener;
152         }
153
154         public Builder stateTopic(@Nullable String stateTopic) {
155             this.stateTopic = stateTopic;
156             return this;
157         }
158
159         public Builder stateTopic(@Nullable String stateTopic, @Nullable String... templates) {
160             this.stateTopic = stateTopic;
161             if (stateTopic != null && !stateTopic.isBlank()) {
162                 for (String template : templates) {
163                     if (template != null && !template.isBlank()) {
164                         this.templateIn = template;
165                         break;
166                     }
167                 }
168             }
169             return this;
170         }
171
172         /**
173          * @deprecated use commandTopic(String, boolean, int)
174          * @param commandTopic topic
175          * @param retain retain
176          * @return this
177          */
178         @Deprecated
179         public Builder commandTopic(@Nullable String commandTopic, boolean retain) {
180             this.commandTopic = commandTopic;
181             this.retain = retain;
182             return this;
183         }
184
185         public Builder commandTopic(@Nullable String commandTopic, boolean retain, int qos) {
186             return commandTopic(commandTopic, retain, qos, null);
187         }
188
189         public Builder commandTopic(@Nullable String commandTopic, boolean retain, int qos, @Nullable String template) {
190             this.commandTopic = commandTopic;
191             this.retain = retain;
192             this.qos = qos;
193             if (commandTopic != null && !commandTopic.isBlank()) {
194                 this.templateOut = template;
195             }
196             return this;
197         }
198
199         public Builder trigger(boolean trigger) {
200             this.trigger = trigger;
201             return this;
202         }
203
204         public Builder isAdvanced(boolean advanced) {
205             this.isAdvanced = advanced;
206             return this;
207         }
208
209         public Builder withAutoUpdatePolicy(@Nullable AutoUpdatePolicy autoUpdatePolicy) {
210             this.autoUpdatePolicy = autoUpdatePolicy;
211             return this;
212         }
213
214         public Builder commandFilter(@Nullable Predicate<Command> commandFilter) {
215             this.commandFilter = commandFilter;
216             return this;
217         }
218
219         public Builder withFormat(String format) {
220             this.format = format;
221             return this;
222         }
223
224         public ComponentChannel build() {
225             return build(true);
226         }
227
228         public ComponentChannel build(boolean addToComponent) {
229             ChannelUID channelUID;
230             ChannelState channelState;
231             Channel channel;
232             ChannelType type;
233             ChannelTypeUID channelTypeUID;
234
235             channelUID = component.buildChannelUID(channelID);
236             channelTypeUID = new ChannelTypeUID(MqttBindingConstants.BINDING_ID,
237                     channelUID.getGroupId() + "_" + channelID);
238             channelState = new HomeAssistantChannelState(
239                     ChannelConfigBuilder.create().withRetain(retain).withQos(qos).withStateTopic(stateTopic)
240                             .withCommandTopic(commandTopic).makeTrigger(trigger).withFormatter(format).build(),
241                     channelUID, valueState, channelStateUpdateListener, commandFilter);
242
243             // disabled by default components should always show up as advanced
244             if (!component.isEnabledByDefault()) {
245                 isAdvanced = true;
246             }
247
248             ChannelTypeBuilder typeBuilder;
249             if (this.trigger) {
250                 typeBuilder = ChannelTypeBuilder.trigger(channelTypeUID, label);
251             } else {
252                 StateDescriptionFragment stateDescription = valueState.createStateDescription(commandTopic == null)
253                         .build();
254                 CommandDescription commandDescription = valueState.createCommandDescription().build();
255                 typeBuilder = ChannelTypeBuilder.state(channelTypeUID, label, channelState.getItemType())
256                         .withStateDescriptionFragment(stateDescription).withCommandDescription(commandDescription);
257             }
258             type = typeBuilder.withConfigDescriptionURI(URI.create(MqttBindingConstants.CONFIG_HA_CHANNEL))
259                     .isAdvanced(isAdvanced).build();
260
261             Configuration configuration = new Configuration();
262             configuration.put("config", component.getChannelConfigurationJson());
263             component.getHaID().toConfig(configuration);
264
265             channel = ChannelBuilder.create(channelUID, channelState.getItemType()).withType(channelTypeUID)
266                     .withKind(type.getKind()).withLabel(label).withConfiguration(configuration)
267                     .withAutoUpdatePolicy(autoUpdatePolicy).build();
268
269             ComponentChannel result = new ComponentChannel(channelUID, channelState, channel, type, channelTypeUID,
270                     channelStateUpdateListener);
271
272             TransformationServiceProvider transformationProvider = component.getTransformationServiceProvider();
273
274             final String templateIn = this.templateIn;
275             if (templateIn != null && transformationProvider != null) {
276                 channelState
277                         .addTransformation(new ChannelStateTransformation(JINJA, templateIn, transformationProvider));
278             }
279             final String templateOut = this.templateOut;
280             if (templateOut != null && transformationProvider != null) {
281                 channelState.addTransformationOut(
282                         new ChannelStateTransformation(JINJA, templateOut, transformationProvider));
283             }
284             if (addToComponent) {
285                 component.getChannelMap().put(channelID, result);
286             }
287             return result;
288         }
289     }
290 }