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