]> git.basschouten.com Git - openhab-addons.git/blob
b016a6960e0bdbb470c2ac7f29fdb0661260c47e
[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;
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.ChannelDefinition;
37 import org.openhab.core.thing.type.ChannelDefinitionBuilder;
38 import org.openhab.core.thing.type.ChannelType;
39 import org.openhab.core.thing.type.ChannelTypeBuilder;
40 import org.openhab.core.thing.type.ChannelTypeUID;
41 import org.openhab.core.types.Command;
42 import org.openhab.core.types.StateDescriptionFragment;
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 ChannelUID channelUID;
64     private final ChannelState channelState;
65     private final Channel channel;
66     private final ChannelType type;
67     private final ChannelTypeUID channelTypeUID;
68     private final ChannelStateUpdateListener channelStateUpdateListener;
69
70     private ComponentChannel(ChannelUID channelUID, ChannelState channelState, Channel channel, ChannelType type,
71             ChannelTypeUID channelTypeUID, ChannelStateUpdateListener channelStateUpdateListener) {
72         super();
73         this.channelUID = channelUID;
74         this.channelState = channelState;
75         this.channel = channel;
76         this.type = type;
77         this.channelTypeUID = channelTypeUID;
78         this.channelStateUpdateListener = channelStateUpdateListener;
79     }
80
81     public ChannelUID getChannelUID() {
82         return channelUID;
83     }
84
85     public Channel getChannel() {
86         return channel;
87     }
88
89     public ChannelState getState() {
90         return channelState;
91     }
92
93     public CompletableFuture<@Nullable Void> stop() {
94         return channelState.stop();
95     }
96
97     public CompletableFuture<@Nullable Void> start(MqttBrokerConnection connection, ScheduledExecutorService scheduler,
98             int timeout) {
99         // Make sure we set the callback again which might have been nulled during a stop
100         channelState.setChannelStateUpdateListener(this.channelStateUpdateListener);
101
102         return channelState.start(connection, scheduler, timeout);
103     }
104
105     public void addChannelTypes(MqttChannelTypeProvider channelTypeProvider) {
106         channelTypeProvider.setChannelType(channelTypeUID, type);
107     }
108
109     public void removeChannelTypes(MqttChannelTypeProvider channelTypeProvider) {
110         channelTypeProvider.removeChannelType(channelTypeUID);
111     }
112
113     public ChannelDefinition type() {
114         return new ChannelDefinitionBuilder(channelUID.getId(), channelTypeUID).build();
115     }
116
117     public void resetState() {
118         channelState.getCache().resetState();
119     }
120
121     public static class Builder {
122         private final AbstractComponent<?> component;
123         private final String channelID;
124         private final Value valueState;
125         private final String label;
126         private final ChannelStateUpdateListener channelStateUpdateListener;
127
128         private @Nullable String stateTopic;
129         private @Nullable String commandTopic;
130         private boolean retain;
131         private boolean trigger;
132         private boolean isAdvanced;
133         private @Nullable Integer qos;
134         private @Nullable Predicate<Command> commandFilter;
135
136         private @Nullable String templateIn;
137         private @Nullable String templateOut;
138
139         private String format = "%s";
140
141         public Builder(AbstractComponent<?> component, String channelID, Value valueState, String label,
142                 ChannelStateUpdateListener channelStateUpdateListener) {
143             this.component = component;
144             this.channelID = channelID;
145             this.valueState = valueState;
146             this.label = label;
147             this.isAdvanced = false;
148             this.channelStateUpdateListener = channelStateUpdateListener;
149         }
150
151         public Builder stateTopic(@Nullable String stateTopic) {
152             this.stateTopic = stateTopic;
153             return this;
154         }
155
156         public Builder stateTopic(@Nullable String stateTopic, @Nullable String... templates) {
157             this.stateTopic = stateTopic;
158             if (stateTopic != null && !stateTopic.isBlank()) {
159                 for (String template : templates) {
160                     if (template != null && !template.isBlank()) {
161                         this.templateIn = template;
162                         break;
163                     }
164                 }
165             }
166             return this;
167         }
168
169         /**
170          * @deprecated use commandTopic(String, boolean, int)
171          * @param commandTopic topic
172          * @param retain retain
173          * @return this
174          */
175         @Deprecated
176         public Builder commandTopic(@Nullable String commandTopic, boolean retain) {
177             this.commandTopic = commandTopic;
178             this.retain = retain;
179             return this;
180         }
181
182         public Builder commandTopic(@Nullable String commandTopic, boolean retain, int qos) {
183             return commandTopic(commandTopic, retain, qos, null);
184         }
185
186         public Builder commandTopic(@Nullable String commandTopic, boolean retain, int qos, @Nullable String template) {
187             this.commandTopic = commandTopic;
188             this.retain = retain;
189             this.qos = qos;
190             if (commandTopic != null && !commandTopic.isBlank()) {
191                 this.templateOut = template;
192             }
193             return this;
194         }
195
196         public Builder trigger(boolean trigger) {
197             this.trigger = trigger;
198             return this;
199         }
200
201         public Builder isAdvanced(boolean advanced) {
202             this.isAdvanced = advanced;
203             return this;
204         }
205
206         public Builder commandFilter(@Nullable Predicate<Command> commandFilter) {
207             this.commandFilter = commandFilter;
208             return this;
209         }
210
211         public Builder withFormat(String format) {
212             this.format = format;
213             return this;
214         }
215
216         public ComponentChannel build() {
217             return build(true);
218         }
219
220         public ComponentChannel build(boolean addToComponent) {
221             ChannelUID channelUID;
222             ChannelState channelState;
223             Channel channel;
224             ChannelType type;
225             ChannelTypeUID channelTypeUID;
226
227             channelUID = new ChannelUID(component.getGroupUID(), channelID);
228             channelTypeUID = new ChannelTypeUID(MqttBindingConstants.BINDING_ID,
229                     channelUID.getGroupId() + "_" + channelID);
230             channelState = new HomeAssistantChannelState(
231                     ChannelConfigBuilder.create().withRetain(retain).withQos(qos).withStateTopic(stateTopic)
232                             .withCommandTopic(commandTopic).makeTrigger(trigger).withFormatter(format).build(),
233                     channelUID, valueState, channelStateUpdateListener, commandFilter);
234
235             // disabled by default components should always show up as advanced
236             if (!component.isEnabledByDefault()) {
237                 isAdvanced = true;
238             }
239
240             if (this.trigger) {
241                 type = ChannelTypeBuilder.trigger(channelTypeUID, label)
242                         .withConfigDescriptionURI(URI.create(MqttBindingConstants.CONFIG_HA_CHANNEL))
243                         .isAdvanced(isAdvanced).build();
244             } else {
245                 StateDescriptionFragment description = valueState.createStateDescription(commandTopic == null).build();
246                 type = ChannelTypeBuilder.state(channelTypeUID, label, channelState.getItemType())
247                         .withConfigDescriptionURI(URI.create(MqttBindingConstants.CONFIG_HA_CHANNEL))
248                         .withStateDescriptionFragment(description).isAdvanced(isAdvanced).build();
249             }
250
251             Configuration configuration = new Configuration();
252             configuration.put("config", component.getChannelConfigurationJson());
253             component.getHaID().toConfig(configuration);
254
255             channel = ChannelBuilder.create(channelUID, channelState.getItemType()).withType(channelTypeUID)
256                     .withKind(type.getKind()).withLabel(label).withConfiguration(configuration).build();
257
258             ComponentChannel result = new ComponentChannel(channelUID, channelState, channel, type, channelTypeUID,
259                     channelStateUpdateListener);
260
261             TransformationServiceProvider transformationProvider = component.getTransformationServiceProvider();
262
263             final String templateIn = this.templateIn;
264             if (templateIn != null && transformationProvider != null) {
265                 channelState
266                         .addTransformation(new ChannelStateTransformation(JINJA, templateIn, transformationProvider));
267             }
268             final String templateOut = this.templateOut;
269             if (templateOut != null && transformationProvider != null) {
270                 channelState.addTransformationOut(
271                         new ChannelStateTransformation(JINJA, templateOut, transformationProvider));
272             }
273             if (addToComponent) {
274                 component.getChannelMap().put(channelID, result);
275             }
276             return result;
277         }
278     }
279 }