2 * Copyright (c) 2010-2024 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;
15 import java.util.Objects;
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.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;
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.
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.
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.
56 * @author David Graeff - Initial contribution
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;
66 private ComponentChannel(ChannelState channelState, Channel channel, @Nullable StateDescription stateDescription,
67 @Nullable CommandDescription commandDescription, ChannelStateUpdateListener channelStateUpdateListener) {
69 this.channelState = channelState;
70 this.channel = channel;
71 this.stateDescription = stateDescription;
72 this.commandDescription = commandDescription;
73 this.channelStateUpdateListener = channelStateUpdateListener;
76 public Channel getChannel() {
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())
85 channelState.setChannelUID(channelUID);
88 public void clearConfiguration() {
89 channel = ChannelBuilder.create(channel).withConfiguration(new Configuration()).build();
92 public ChannelState getState() {
96 public @Nullable StateDescription getStateDescription() {
97 return stateDescription;
100 public @Nullable CommandDescription getCommandDescription() {
101 return commandDescription;
104 public CompletableFuture<@Nullable Void> stop() {
105 return channelState.stop();
108 public CompletableFuture<@Nullable Void> start(MqttBrokerConnection connection, ScheduledExecutorService scheduler,
110 // Make sure we set the callback again which might have been nulled during a stop
111 channelState.setChannelStateUpdateListener(this.channelStateUpdateListener);
113 return channelState.start(connection, scheduler, timeout);
116 public ChannelDefinition channelDefinition() {
117 return new ChannelDefinitionBuilder(channel.getUID().getId(),
118 Objects.requireNonNull(channel.getChannelTypeUID())).withLabel(channel.getLabel()).build();
121 public void resetState() {
122 channelState.getCache().resetState();
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;
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;
142 private @Nullable String templateIn;
143 private @Nullable String templateOut;
145 private String format = "%s";
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;
154 this.isAdvanced = false;
155 this.channelStateUpdateListener = channelStateUpdateListener;
158 public Builder stateTopic(@Nullable String stateTopic) {
159 this.stateTopic = stateTopic;
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;
177 * @deprecated use commandTopic(String, boolean, int)
178 * @param commandTopic topic
179 * @param retain retain
183 public Builder commandTopic(@Nullable String commandTopic, boolean retain) {
184 this.commandTopic = commandTopic;
185 this.retain = retain;
189 public Builder commandTopic(@Nullable String commandTopic, boolean retain, int qos) {
190 return commandTopic(commandTopic, retain, qos, null);
193 public Builder commandTopic(@Nullable String commandTopic, boolean retain, int qos, @Nullable String template) {
194 this.commandTopic = commandTopic;
195 this.retain = retain;
197 if (commandTopic != null && !commandTopic.isBlank()) {
198 this.templateOut = template;
203 public Builder trigger(boolean trigger) {
204 this.trigger = trigger;
208 public Builder isAdvanced(boolean advanced) {
209 this.isAdvanced = advanced;
213 public Builder withAutoUpdatePolicy(@Nullable AutoUpdatePolicy autoUpdatePolicy) {
214 this.autoUpdatePolicy = autoUpdatePolicy;
218 public Builder commandFilter(@Nullable Predicate<Command> commandFilter) {
219 this.commandFilter = commandFilter;
223 public Builder withFormat(String format) {
224 this.format = format;
228 public ComponentChannel build() {
232 public ComponentChannel build(boolean addToComponent) {
233 ChannelUID channelUID;
234 ChannelState channelState;
236 ChannelTransformation incomingTransformation = null, outgoingTransformation = null;
238 channelUID = component.buildChannelUID(channelID);
239 ChannelConfigBuilder channelConfigBuilder = ChannelConfigBuilder.create().withRetain(retain).withQos(qos)
240 .withStateTopic(stateTopic).withCommandTopic(commandTopic).makeTrigger(trigger)
241 .withFormatter(format);
243 String localTemplateIn = templateIn;
244 if (localTemplateIn != null) {
245 incomingTransformation = new HomeAssistantChannelTransformation(component.getJinjava(), component,
248 String localTemplateOut = templateOut;
249 if (localTemplateOut != null) {
250 outgoingTransformation = new HomeAssistantChannelTransformation(component.getJinjava(), component,
254 channelState = new HomeAssistantChannelState(channelConfigBuilder.build(), channelUID, valueState,
255 channelStateUpdateListener, commandFilter, incomingTransformation, outgoingTransformation);
257 // disabled by default components should always show up as advanced
258 if (!component.isEnabledByDefault()) {
262 channelTypeUID = new ChannelTypeUID(channelTypeUID.getBindingId(),
263 channelTypeUID.getId() + "-advanced");
267 StateDescription stateDescription = null;
268 CommandDescription commandDescription = null;
270 kind = ChannelKind.TRIGGER;
272 kind = ChannelKind.STATE;
273 stateDescription = valueState.createStateDescription(commandTopic == null).build().toStateDescription();
274 commandDescription = valueState.createCommandDescription().build();
277 Configuration configuration = new Configuration();
278 configuration.put("config", component.getChannelConfigurationJson());
279 component.getHaID().toConfig(configuration);
281 channel = ChannelBuilder.create(channelUID, channelState.getItemType()).withType(channelTypeUID)
282 .withKind(kind).withLabel(label).withConfiguration(configuration)
283 .withAutoUpdatePolicy(autoUpdatePolicy).build();
285 ComponentChannel result = new ComponentChannel(channelState, channel, stateDescription, commandDescription,
286 channelStateUpdateListener);
288 if (addToComponent) {
289 component.getChannelMap().put(channelID, result);