2 * Copyright (c) 2010-2022 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.component;
15 import java.util.List;
16 import java.util.concurrent.CompletableFuture;
17 import java.util.concurrent.ScheduledExecutorService;
18 import java.util.stream.Stream;
20 import org.eclipse.jdt.annotation.NonNullByDefault;
21 import org.eclipse.jdt.annotation.Nullable;
22 import org.openhab.binding.mqtt.generic.ChannelStateUpdateListener;
23 import org.openhab.binding.mqtt.generic.mapping.ColorMode;
24 import org.openhab.binding.mqtt.generic.values.ColorValue;
25 import org.openhab.binding.mqtt.homeassistant.internal.ComponentChannel;
26 import org.openhab.binding.mqtt.homeassistant.internal.config.dto.AbstractChannelConfiguration;
27 import org.openhab.core.io.transport.mqtt.MqttBrokerConnection;
28 import org.openhab.core.thing.ChannelUID;
29 import org.openhab.core.types.Command;
30 import org.openhab.core.types.State;
32 import com.google.gson.annotations.SerializedName;
35 * A MQTT light, following the https://www.home-assistant.io/components/light.mqtt/ specification.
37 * This class condenses the three state/command topics (for ON/OFF, Brightness, Color) to one
40 * @author David Graeff - Initial contribution
43 public class Light extends AbstractComponent<Light.ChannelConfiguration> implements ChannelStateUpdateListener {
44 public static final String SWITCH_CHANNEL_ID = "light"; // Randomly chosen channel "ID"
45 public static final String BRIGHTNESS_CHANNEL_ID = "brightness"; // Randomly chosen channel "ID"
46 public static final String COLOR_CHANNEL_ID = "color"; // Randomly chosen channel "ID"
49 * Configuration class for MQTT component
51 static class ChannelConfiguration extends AbstractChannelConfiguration {
52 ChannelConfiguration() {
56 @SerializedName("brightness_scale")
57 protected int brightnessScale = 255;
58 protected boolean optimistic = false;
59 @SerializedName("effect_list")
60 protected @Nullable List<String> effectList;
62 // Defines when on the payload_on is sent. Using last (the default) will send any style (brightness, color, etc)
63 // topics first and then a payload_on to the command_topic. Using first will send the payload_on and then any
64 // style topics. Using brightness will only send brightness commands instead of the payload_on to turn the light
66 @SerializedName("on_command_type")
67 protected String onCommandType = "last";
69 @SerializedName("state_topic")
70 protected @Nullable String stateTopic;
71 @SerializedName("command_topic")
72 protected @Nullable String commandTopic;
73 @SerializedName("state_value_template")
74 protected @Nullable String stateValueTemplate;
76 @SerializedName("brightness_state_topic")
77 protected @Nullable String brightnessStateTopic;
78 @SerializedName("brightness_command_topic")
79 protected @Nullable String brightnessCommandTopic;
80 @SerializedName("brightness_value_template")
81 protected @Nullable String brightnessValueTemplate;
83 @SerializedName("color_temp_state_topic")
84 protected @Nullable String colorTempStateTopic;
85 @SerializedName("color_temp_command_topic")
86 protected @Nullable String colorTempCommandTopic;
87 @SerializedName("color_temp_value_template")
88 protected @Nullable String colorTempValueTemplate;
90 @SerializedName("effect_command_topic")
91 protected @Nullable String effectCommandTopic;
92 @SerializedName("effect_state_topic")
93 protected @Nullable String effectStateTopic;
94 @SerializedName("effect_value_template")
95 protected @Nullable String effectValueTemplate;
97 @SerializedName("rgb_command_topic")
98 protected @Nullable String rgbCommandTopic;
99 @SerializedName("rgb_state_topic")
100 protected @Nullable String rgbStateTopic;
101 @SerializedName("rgb_value_template")
102 protected @Nullable String rgbValueTemplate;
103 @SerializedName("rgb_command_template")
104 protected @Nullable String rgbCommandTemplate;
106 @SerializedName("white_value_command_topic")
107 protected @Nullable String whiteValueCommandTopic;
108 @SerializedName("white_value_state_topic")
109 protected @Nullable String whiteValueStateTopic;
110 @SerializedName("white_value_template")
111 protected @Nullable String whiteValueTemplate;
113 @SerializedName("xy_command_topic")
114 protected @Nullable String xyCommandTopic;
115 @SerializedName("xy_state_topic")
116 protected @Nullable String xyStateTopic;
117 @SerializedName("xy_value_template")
118 protected @Nullable String xyValueTemplate;
120 @SerializedName("payload_on")
121 protected String payloadOn = "ON";
122 @SerializedName("payload_off")
123 protected String payloadOff = "OFF";
126 protected ComponentChannel colorChannel;
127 protected ComponentChannel switchChannel;
128 protected ComponentChannel brightnessChannel;
129 private final @Nullable ChannelStateUpdateListener channelStateUpdateListener;
131 public Light(ComponentFactory.ComponentConfiguration builder) {
132 super(builder, ChannelConfiguration.class);
133 this.channelStateUpdateListener = builder.getUpdateListener();
134 ColorValue value = new ColorValue(ColorMode.RGB, channelConfiguration.payloadOn,
135 channelConfiguration.payloadOff, 100);
137 // Create three MQTT subscriptions and use this class object as update listener
138 switchChannel = buildChannel(SWITCH_CHANNEL_ID, value, channelConfiguration.getName(), this)
139 .stateTopic(channelConfiguration.stateTopic, channelConfiguration.stateValueTemplate,
140 channelConfiguration.getValueTemplate())
141 .commandTopic(channelConfiguration.commandTopic, channelConfiguration.isRetain(),
142 channelConfiguration.getQos())
145 colorChannel = buildChannel(COLOR_CHANNEL_ID, value, channelConfiguration.getName(), this)
146 .stateTopic(channelConfiguration.rgbStateTopic, channelConfiguration.rgbValueTemplate)
147 .commandTopic(channelConfiguration.rgbCommandTopic, channelConfiguration.isRetain(),
148 channelConfiguration.getQos())
151 brightnessChannel = buildChannel(BRIGHTNESS_CHANNEL_ID, value, channelConfiguration.getName(), this)
152 .stateTopic(channelConfiguration.brightnessStateTopic, channelConfiguration.brightnessValueTemplate)
153 .commandTopic(channelConfiguration.brightnessCommandTopic, channelConfiguration.isRetain(),
154 channelConfiguration.getQos())
157 channels.put(COLOR_CHANNEL_ID, colorChannel);
161 public CompletableFuture<@Nullable Void> start(MqttBrokerConnection connection, ScheduledExecutorService scheduler,
163 return Stream.of(switchChannel, brightnessChannel, colorChannel) //
164 .map(v -> v.start(connection, scheduler, timeout)) //
165 .reduce(CompletableFuture.completedFuture(null), (f, v) -> f.thenCompose(b -> v));
169 public CompletableFuture<@Nullable Void> stop() {
170 return Stream.of(switchChannel, brightnessChannel, colorChannel) //
171 .map(v -> v.stop()) //
172 .reduce(CompletableFuture.completedFuture(null), (f, v) -> f.thenCompose(b -> v));
176 * Proxy method to condense all three MQTT subscriptions to one channel
179 public void updateChannelState(ChannelUID channelUID, State value) {
180 ChannelStateUpdateListener listener = channelStateUpdateListener;
181 if (listener != null) {
182 listener.updateChannelState(colorChannel.getChannelUID(), value);
187 * Proxy method to condense all three MQTT subscriptions to one channel
190 public void postChannelCommand(ChannelUID channelUID, Command value) {
191 ChannelStateUpdateListener listener = channelStateUpdateListener;
192 if (listener != null) {
193 listener.postChannelCommand(colorChannel.getChannelUID(), value);
198 * Proxy method to condense all three MQTT subscriptions to one channel
201 public void triggerChannel(ChannelUID channelUID, String eventPayload) {
202 ChannelStateUpdateListener listener = channelStateUpdateListener;
203 if (listener != null) {
204 listener.triggerChannel(colorChannel.getChannelUID(), eventPayload);