2 * Copyright (c) 2010-2021 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
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
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.mqtt.homeassistant.internal;
16 import java.util.concurrent.CompletableFuture;
17 import java.util.concurrent.ScheduledExecutorService;
18 import java.util.function.Predicate;
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;
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.
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.
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.
57 * @author David Graeff - Initial contribution
60 public class ComponentChannel {
61 private static final String JINJA = "JINJA";
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;
70 private ComponentChannel(ChannelUID channelUID, ChannelState channelState, Channel channel, ChannelType type,
71 ChannelTypeUID channelTypeUID, ChannelStateUpdateListener channelStateUpdateListener) {
73 this.channelUID = channelUID;
74 this.channelState = channelState;
75 this.channel = channel;
77 this.channelTypeUID = channelTypeUID;
78 this.channelStateUpdateListener = channelStateUpdateListener;
81 public ChannelUID getChannelUID() {
85 public Channel getChannel() {
89 public ChannelState getState() {
93 public CompletableFuture<@Nullable Void> stop() {
94 return channelState.stop();
97 public CompletableFuture<@Nullable Void> start(MqttBrokerConnection connection, ScheduledExecutorService scheduler,
99 // Make sure we set the callback again which might have been nulled during an stop
100 channelState.setChannelStateUpdateListener(this.channelStateUpdateListener);
102 return channelState.start(connection, scheduler, timeout);
105 public void addChannelTypes(MqttChannelTypeProvider channelTypeProvider) {
106 channelTypeProvider.setChannelType(channelTypeUID, type);
109 public void removeChannelTypes(MqttChannelTypeProvider channelTypeProvider) {
110 channelTypeProvider.removeChannelType(channelTypeUID);
113 public ChannelDefinition type() {
114 return new ChannelDefinitionBuilder(channelUID.getId(), channelTypeUID).build();
117 public void resetState() {
118 channelState.getCache().resetState();
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;
128 private @Nullable String state_topic;
129 private @Nullable String command_topic;
130 private boolean retain;
131 private boolean trigger;
132 private @Nullable Integer qos;
133 private @Nullable Predicate<Command> commandFilter;
135 private @Nullable String templateIn;
136 private @Nullable String templateOut;
138 public Builder(AbstractComponent<?> component, String channelID, Value valueState, String label,
139 ChannelStateUpdateListener channelStateUpdateListener) {
140 this.component = component;
141 this.channelID = channelID;
142 this.valueState = valueState;
144 this.channelStateUpdateListener = channelStateUpdateListener;
147 public Builder stateTopic(@Nullable String state_topic) {
148 this.state_topic = state_topic;
152 public Builder stateTopic(@Nullable String state_topic, @Nullable String... templates) {
153 this.state_topic = state_topic;
154 if (state_topic != null && !state_topic.isBlank()) {
155 for (String template : templates) {
156 if (template != null && !template.isBlank()) {
157 this.templateIn = template;
166 * @deprecated use commandTopic(String, boolean, int)
167 * @param command_topic topic
168 * @param retain retain
172 public Builder commandTopic(@Nullable String command_topic, boolean retain) {
173 this.command_topic = command_topic;
174 this.retain = retain;
178 public Builder commandTopic(@Nullable String command_topic, boolean retain, int qos) {
179 return commandTopic(command_topic, retain, qos, null);
182 public Builder commandTopic(@Nullable String command_topic, boolean retain, int qos,
183 @Nullable String template) {
184 this.command_topic = command_topic;
185 this.retain = retain;
187 if (command_topic != null && !command_topic.isBlank()) {
188 this.templateOut = template;
193 public Builder trigger(boolean trigger) {
194 this.trigger = trigger;
198 public Builder commandFilter(@Nullable Predicate<Command> commandFilter) {
199 this.commandFilter = commandFilter;
203 public ComponentChannel build() {
207 public ComponentChannel build(boolean addToComponent) {
208 ChannelUID channelUID;
209 ChannelState channelState;
212 ChannelTypeUID channelTypeUID;
214 channelUID = new ChannelUID(component.getGroupUID(), channelID);
215 channelTypeUID = new ChannelTypeUID(MqttBindingConstants.BINDING_ID,
216 channelUID.getGroupId() + "_" + channelID);
217 channelState = new HomeAssistantChannelState(
218 ChannelConfigBuilder.create().withRetain(retain).withQos(qos).withStateTopic(state_topic)
219 .withCommandTopic(command_topic).makeTrigger(trigger).build(),
220 channelUID, valueState, channelStateUpdateListener, commandFilter);
222 String localStateTopic = state_topic;
223 if (localStateTopic == null || localStateTopic.isBlank() || this.trigger) {
224 type = ChannelTypeBuilder.trigger(channelTypeUID, label)
225 .withConfigDescriptionURI(URI.create(MqttBindingConstants.CONFIG_HA_CHANNEL)).build();
227 StateDescriptionFragment description = valueState.createStateDescription(command_topic == null).build();
228 type = ChannelTypeBuilder.state(channelTypeUID, label, channelState.getItemType())
229 .withConfigDescriptionURI(URI.create(MqttBindingConstants.CONFIG_HA_CHANNEL))
230 .withStateDescriptionFragment(description).build();
233 Configuration configuration = new Configuration();
234 configuration.put("config", component.getChannelConfigurationJson());
235 component.getHaID().toConfig(configuration);
237 channel = ChannelBuilder.create(channelUID, channelState.getItemType()).withType(channelTypeUID)
238 .withKind(type.getKind()).withLabel(label).withConfiguration(configuration).build();
240 ComponentChannel result = new ComponentChannel(channelUID, channelState, channel, type, channelTypeUID,
241 channelStateUpdateListener);
243 TransformationServiceProvider transformationProvider = component.getTransformationServiceProvider();
245 final String templateIn = this.templateIn;
246 if (templateIn != null && transformationProvider != null) {
248 .addTransformation(new ChannelStateTransformation(JINJA, templateIn, transformationProvider));
250 final String templateOut = this.templateOut;
251 if (templateOut != null && transformationProvider != null) {
252 channelState.addTransformationOut(
253 new ChannelStateTransformation(JINJA, templateOut, transformationProvider));
255 if (addToComponent) {
256 component.getChannelMap().put(channelID, result);