]> git.basschouten.com Git - openhab-addons.git/blob
ac27112bff1ba71e37a6904518734ccc8ad0fb5b
[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.ChannelStateUpdateListener;
25 import org.openhab.binding.mqtt.generic.values.Value;
26 import org.openhab.binding.mqtt.homeassistant.internal.component.AbstractComponent;
27 import org.openhab.core.config.core.Configuration;
28 import org.openhab.core.io.transport.mqtt.MqttBrokerConnection;
29 import org.openhab.core.thing.Channel;
30 import org.openhab.core.thing.ChannelUID;
31 import org.openhab.core.thing.binding.builder.ChannelBuilder;
32 import org.openhab.core.thing.binding.generic.ChannelTransformation;
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 final ChannelState channelState;
61     private Channel channel;
62     private final @Nullable StateDescription stateDescription;
63     private final @Nullable CommandDescription commandDescription;
64     private final ChannelStateUpdateListener channelStateUpdateListener;
65
66     private ComponentChannel(ChannelState channelState, Channel channel, @Nullable StateDescription stateDescription,
67             @Nullable CommandDescription commandDescription, ChannelStateUpdateListener channelStateUpdateListener) {
68         super();
69         this.channelState = channelState;
70         this.channel = channel;
71         this.stateDescription = stateDescription;
72         this.commandDescription = commandDescription;
73         this.channelStateUpdateListener = channelStateUpdateListener;
74     }
75
76     public Channel getChannel() {
77         return channel;
78     }
79
80     public void resetUID(ChannelUID channelUID) {
81         channel = ChannelBuilder.create(channelUID, channel.getAcceptedItemType()).withType(channel.getChannelTypeUID())
82                 .withKind(channel.getKind()).withLabel(Objects.requireNonNull(channel.getLabel()))
83                 .withConfiguration(channel.getConfiguration()).withAutoUpdatePolicy(channel.getAutoUpdatePolicy())
84                 .build();
85         channelState.setChannelUID(channelUID);
86     }
87
88     public void clearConfiguration() {
89         channel = ChannelBuilder.create(channel).withConfiguration(new Configuration()).build();
90     }
91
92     public ChannelState getState() {
93         return channelState;
94     }
95
96     public @Nullable StateDescription getStateDescription() {
97         return stateDescription;
98     }
99
100     public @Nullable CommandDescription getCommandDescription() {
101         return commandDescription;
102     }
103
104     public CompletableFuture<@Nullable Void> stop() {
105         return channelState.stop();
106     }
107
108     public CompletableFuture<@Nullable Void> start(MqttBrokerConnection connection, ScheduledExecutorService scheduler,
109             int timeout) {
110         // Make sure we set the callback again which might have been nulled during a stop
111         channelState.setChannelStateUpdateListener(this.channelStateUpdateListener);
112
113         return channelState.start(connection, scheduler, timeout);
114     }
115
116     public ChannelDefinition channelDefinition() {
117         return new ChannelDefinitionBuilder(channel.getUID().getId(),
118                 Objects.requireNonNull(channel.getChannelTypeUID())).withLabel(channel.getLabel()).build();
119     }
120
121     public void resetState() {
122         channelState.getCache().resetState();
123     }
124
125     public static class Builder {
126         private final AbstractComponent<?> component;
127         private final String channelID;
128         private ChannelTypeUID channelTypeUID;
129         private final Value valueState;
130         private final String label;
131         private final ChannelStateUpdateListener channelStateUpdateListener;
132
133         private @Nullable String stateTopic;
134         private @Nullable String commandTopic;
135         private boolean retain;
136         private boolean trigger;
137         private boolean isAdvanced;
138         private @Nullable AutoUpdatePolicy autoUpdatePolicy;
139         private @Nullable Integer qos;
140         private @Nullable Predicate<Command> commandFilter;
141
142         private @Nullable String templateIn;
143         private @Nullable String templateOut;
144
145         private String format = "%s";
146
147         public Builder(AbstractComponent<?> component, String channelID, ChannelTypeUID channelTypeUID,
148                 Value valueState, String label, ChannelStateUpdateListener channelStateUpdateListener) {
149             this.component = component;
150             this.channelID = channelID;
151             this.channelTypeUID = channelTypeUID;
152             this.valueState = valueState;
153             this.label = label;
154             this.isAdvanced = false;
155             this.channelStateUpdateListener = channelStateUpdateListener;
156         }
157
158         public Builder stateTopic(@Nullable String stateTopic) {
159             this.stateTopic = stateTopic;
160             return this;
161         }
162
163         public Builder stateTopic(@Nullable String stateTopic, @Nullable String... templates) {
164             this.stateTopic = stateTopic;
165             if (stateTopic != null && !stateTopic.isBlank()) {
166                 for (String template : templates) {
167                     if (template != null && !template.isBlank()) {
168                         this.templateIn = template;
169                         break;
170                     }
171                 }
172             }
173             return this;
174         }
175
176         /**
177          * @deprecated use commandTopic(String, boolean, int)
178          * @param commandTopic topic
179          * @param retain retain
180          * @return this
181          */
182         @Deprecated
183         public Builder commandTopic(@Nullable String commandTopic, boolean retain) {
184             this.commandTopic = commandTopic;
185             this.retain = retain;
186             return this;
187         }
188
189         public Builder commandTopic(@Nullable String commandTopic, boolean retain, int qos) {
190             return commandTopic(commandTopic, retain, qos, null);
191         }
192
193         public Builder commandTopic(@Nullable String commandTopic, boolean retain, int qos, @Nullable String template) {
194             this.commandTopic = commandTopic;
195             this.retain = retain;
196             this.qos = qos;
197             if (commandTopic != null && !commandTopic.isBlank()) {
198                 this.templateOut = template;
199             }
200             return this;
201         }
202
203         public Builder trigger(boolean trigger) {
204             this.trigger = trigger;
205             return this;
206         }
207
208         public Builder isAdvanced(boolean advanced) {
209             this.isAdvanced = advanced;
210             return this;
211         }
212
213         public Builder withAutoUpdatePolicy(@Nullable AutoUpdatePolicy autoUpdatePolicy) {
214             this.autoUpdatePolicy = autoUpdatePolicy;
215             return this;
216         }
217
218         public Builder commandFilter(@Nullable Predicate<Command> commandFilter) {
219             this.commandFilter = commandFilter;
220             return this;
221         }
222
223         public Builder withFormat(String format) {
224             this.format = format;
225             return this;
226         }
227
228         public ComponentChannel build() {
229             return build(true);
230         }
231
232         public ComponentChannel build(boolean addToComponent) {
233             ChannelUID channelUID;
234             ChannelState channelState;
235             Channel channel;
236             ChannelTransformation incomingTransformation = null, outgoingTransformation = null;
237
238             channelUID = component.buildChannelUID(channelID);
239             ChannelConfigBuilder channelConfigBuilder = ChannelConfigBuilder.create().withRetain(retain).withQos(qos)
240                     .withStateTopic(stateTopic).withCommandTopic(commandTopic).makeTrigger(trigger)
241                     .withFormatter(format);
242
243             String localTemplateIn = templateIn;
244             if (localTemplateIn != null) {
245                 incomingTransformation = new HomeAssistantChannelTransformation(component.getJinjava(), component,
246                         localTemplateIn);
247             }
248             String localTemplateOut = templateOut;
249             if (localTemplateOut != null) {
250                 outgoingTransformation = new HomeAssistantChannelTransformation(component.getJinjava(), component,
251                         localTemplateOut);
252             }
253
254             channelState = new HomeAssistantChannelState(channelConfigBuilder.build(), channelUID, valueState,
255                     channelStateUpdateListener, commandFilter, incomingTransformation, outgoingTransformation);
256
257             // disabled by default components should always show up as advanced
258             if (!component.isEnabledByDefault()) {
259                 isAdvanced = true;
260             }
261             if (isAdvanced) {
262                 channelTypeUID = new ChannelTypeUID(channelTypeUID.getBindingId(),
263                         channelTypeUID.getId() + "-advanced");
264             }
265
266             ChannelKind kind;
267             StateDescription stateDescription = null;
268             CommandDescription commandDescription = null;
269             if (this.trigger) {
270                 kind = ChannelKind.TRIGGER;
271             } else {
272                 kind = ChannelKind.STATE;
273                 stateDescription = valueState.createStateDescription(commandTopic == null).build().toStateDescription();
274                 commandDescription = valueState.createCommandDescription().build();
275             }
276
277             Configuration configuration = new Configuration();
278             configuration.put("config", component.getChannelConfigurationJson());
279             component.getHaID().toConfig(configuration);
280
281             channel = ChannelBuilder.create(channelUID, channelState.getItemType()).withType(channelTypeUID)
282                     .withKind(kind).withLabel(label).withConfiguration(configuration)
283                     .withAutoUpdatePolicy(autoUpdatePolicy).build();
284
285             ComponentChannel result = new ComponentChannel(channelState, channel, stateDescription, commandDescription,
286                     channelStateUpdateListener);
287
288             if (addToComponent) {
289                 component.getChannelMap().put(channelID, result);
290             }
291             return result;
292         }
293     }
294 }