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;
19 import org.eclipse.jdt.annotation.NonNullByDefault;
20 import org.eclipse.jdt.annotation.Nullable;
21 import org.openhab.binding.mqtt.generic.ChannelConfigBuilder;
22 import org.openhab.binding.mqtt.generic.ChannelState;
23 import org.openhab.binding.mqtt.generic.ChannelStateTransformation;
24 import org.openhab.binding.mqtt.generic.ChannelStateUpdateListener;
25 import org.openhab.binding.mqtt.generic.MqttChannelTypeProvider;
26 import org.openhab.binding.mqtt.generic.TransformationServiceProvider;
27 import org.openhab.binding.mqtt.generic.values.Value;
28 import org.openhab.binding.mqtt.homeassistant.generic.internal.MqttBindingConstants;
29 import org.openhab.binding.mqtt.homeassistant.internal.CFactory.ComponentConfiguration;
30 import org.openhab.core.config.core.Configuration;
31 import org.openhab.core.io.transport.mqtt.MqttBrokerConnection;
32 import org.openhab.core.thing.Channel;
33 import org.openhab.core.thing.ChannelUID;
34 import org.openhab.core.thing.binding.builder.ChannelBuilder;
35 import org.openhab.core.thing.type.ChannelDefinition;
36 import org.openhab.core.thing.type.ChannelDefinitionBuilder;
37 import org.openhab.core.thing.type.ChannelType;
38 import org.openhab.core.thing.type.ChannelTypeBuilder;
39 import org.openhab.core.thing.type.ChannelTypeUID;
40 import org.openhab.core.types.StateDescriptionFragment;
43 * An {@link AbstractComponent}s derived class consists of one or multiple channels.
44 * Each component channel consists of the determined channel type, channel type UID and the
45 * channel description itself as well as the the channels state.
47 * After the discovery process has completed and the tree of components and component channels
48 * have been built up, the channel types are registered to a custom channel type provider
49 * before adding the channel descriptions to the Thing themselves.
52 * An object of this class creates the required {@link ChannelType} and {@link ChannelTypeUID} as well
53 * as keeps the {@link ChannelState} and {@link Channel} in one place.
55 * @author David Graeff - Initial contribution
58 public class CChannel {
59 private static final String JINJA = "JINJA";
61 private final ChannelUID channelUID;
62 private final ChannelState channelState;
63 private final Channel channel;
64 private final ChannelType type;
65 private final ChannelTypeUID channelTypeUID;
66 private final ChannelStateUpdateListener channelStateUpdateListener;
68 private CChannel(ChannelUID channelUID, ChannelState channelState, Channel channel, ChannelType type,
69 ChannelTypeUID channelTypeUID, ChannelStateUpdateListener channelStateUpdateListener) {
71 this.channelUID = channelUID;
72 this.channelState = channelState;
73 this.channel = channel;
75 this.channelTypeUID = channelTypeUID;
76 this.channelStateUpdateListener = channelStateUpdateListener;
79 public ChannelUID getChannelUID() {
83 public Channel getChannel() {
87 public ChannelState getState() {
91 public CompletableFuture<@Nullable Void> stop() {
92 return channelState.stop();
95 public CompletableFuture<@Nullable Void> start(MqttBrokerConnection connection, ScheduledExecutorService scheduler,
97 // Make sure we set the callback again which might have been nulled during an stop
98 channelState.setChannelStateUpdateListener(this.channelStateUpdateListener);
100 return channelState.start(connection, scheduler, timeout);
103 public void addChannelTypes(MqttChannelTypeProvider channelTypeProvider) {
104 channelTypeProvider.setChannelType(channelTypeUID, type);
107 public void removeChannelTypes(MqttChannelTypeProvider channelTypeProvider) {
108 channelTypeProvider.removeChannelType(channelTypeUID);
111 public ChannelDefinition type() {
112 return new ChannelDefinitionBuilder(channelUID.getId(), channelTypeUID).build();
115 public void resetState() {
116 channelState.getCache().resetState();
119 public static class Builder {
120 private AbstractComponent<?> component;
121 private ComponentConfiguration componentConfiguration;
122 private String channelID;
123 private Value valueState;
124 private String label;
125 private @Nullable String state_topic;
126 private @Nullable String command_topic;
127 private boolean retain;
128 private boolean trigger;
129 private @Nullable Integer qos;
130 private ChannelStateUpdateListener channelStateUpdateListener;
132 private @Nullable String templateIn;
134 public Builder(AbstractComponent<?> component, ComponentConfiguration componentConfiguration, String channelID,
135 Value valueState, String label, ChannelStateUpdateListener channelStateUpdateListener) {
136 this.component = component;
137 this.componentConfiguration = componentConfiguration;
138 this.channelID = channelID;
139 this.valueState = valueState;
141 this.channelStateUpdateListener = channelStateUpdateListener;
144 public Builder stateTopic(@Nullable String state_topic) {
145 this.state_topic = state_topic;
149 public Builder stateTopic(@Nullable String state_topic, @Nullable String... templates) {
150 this.state_topic = state_topic;
151 if (state_topic != null && !state_topic.isBlank()) {
152 for (String template : templates) {
153 if (template != null && !template.isBlank()) {
154 this.templateIn = template;
163 * @deprecated use commandTopic(String, boolean, int)
164 * @param command_topic
169 public Builder commandTopic(@Nullable String command_topic, boolean retain) {
170 this.command_topic = command_topic;
171 this.retain = retain;
175 public Builder commandTopic(@Nullable String command_topic, boolean retain, int qos) {
176 this.command_topic = command_topic;
177 this.retain = retain;
182 public Builder trigger(boolean trigger) {
183 this.trigger = trigger;
187 public CChannel build() {
191 public CChannel build(boolean addToComponent) {
192 ChannelUID channelUID;
193 ChannelState channelState;
196 ChannelTypeUID channelTypeUID;
198 channelUID = new ChannelUID(component.channelGroupUID, channelID);
199 channelTypeUID = new ChannelTypeUID(MqttBindingConstants.BINDING_ID,
200 channelUID.getGroupId() + "_" + channelID);
201 channelState = new ChannelState(
202 ChannelConfigBuilder.create().withRetain(retain).withQos(qos).withStateTopic(state_topic)
203 .withCommandTopic(command_topic).makeTrigger(trigger).build(),
204 channelUID, valueState, channelStateUpdateListener);
206 String localStateTopic = state_topic;
207 if (localStateTopic == null || localStateTopic.isBlank() || this.trigger) {
208 type = ChannelTypeBuilder.trigger(channelTypeUID, label)
209 .withConfigDescriptionURI(URI.create(MqttBindingConstants.CONFIG_HA_CHANNEL)).build();
211 StateDescriptionFragment description = valueState.createStateDescription(command_topic == null).build();
212 type = ChannelTypeBuilder.state(channelTypeUID, label, channelState.getItemType())
213 .withConfigDescriptionURI(URI.create(MqttBindingConstants.CONFIG_HA_CHANNEL))
214 .withStateDescriptionFragment(description).build();
217 Configuration configuration = new Configuration();
218 configuration.put("config", component.channelConfigurationJson);
219 component.haID.toConfig(configuration);
221 channel = ChannelBuilder.create(channelUID, channelState.getItemType()).withType(channelTypeUID)
222 .withKind(type.getKind()).withLabel(label).withConfiguration(configuration).build();
224 CChannel result = new CChannel(channelUID, channelState, channel, type, channelTypeUID,
225 channelStateUpdateListener);
228 TransformationServiceProvider transformationProvider = componentConfiguration
229 .getTransformationServiceProvider();
231 final String templateIn = this.templateIn;
232 if (templateIn != null && transformationProvider != null) {
234 .addTransformation(new ChannelStateTransformation(JINJA, templateIn, transformationProvider));
236 if (addToComponent) {
237 component.channels.put(channelID, result);