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