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.math.BigDecimal;
16 import java.util.ArrayList;
17 import java.util.List;
18 import java.util.Objects;
19 import java.util.concurrent.CompletableFuture;
20 import java.util.concurrent.ScheduledExecutorService;
21 import java.util.stream.Stream;
23 import org.eclipse.jdt.annotation.NonNullByDefault;
24 import org.eclipse.jdt.annotation.Nullable;
25 import org.openhab.binding.mqtt.generic.ChannelStateUpdateListener;
26 import org.openhab.binding.mqtt.generic.mapping.ColorMode;
27 import org.openhab.binding.mqtt.generic.values.ColorValue;
28 import org.openhab.binding.mqtt.generic.values.NumberValue;
29 import org.openhab.binding.mqtt.generic.values.OnOffValue;
30 import org.openhab.binding.mqtt.generic.values.PercentageValue;
31 import org.openhab.binding.mqtt.generic.values.TextValue;
32 import org.openhab.binding.mqtt.homeassistant.internal.ComponentChannel;
33 import org.openhab.binding.mqtt.homeassistant.internal.config.dto.AbstractChannelConfiguration;
34 import org.openhab.binding.mqtt.homeassistant.internal.exception.UnsupportedComponentException;
35 import org.openhab.core.io.transport.mqtt.MqttBrokerConnection;
36 import org.openhab.core.library.unit.Units;
37 import org.openhab.core.thing.ChannelUID;
38 import org.openhab.core.types.Command;
40 import com.google.gson.annotations.SerializedName;
43 * A MQTT light, following the
44 * https://www.home-assistant.io/components/light.mqtt/ specification.
46 * Individual concrete classes implement the differing semantics of the
47 * three different schemas.
49 * As of now, only on/off, brightness, and RGB are fully implemented and tested.
50 * HS and XY are implemented, but not tested. Color temp and effect are only
51 * implemented (but not tested) for the default schema.
53 * @author David Graeff - Initial contribution
54 * @author Cody Cutrer - Re-write for (nearly) full support
57 public abstract class Light extends AbstractComponent<Light.ChannelConfiguration>
58 implements ChannelStateUpdateListener {
59 protected static final String DEFAULT_SCHEMA = "default";
60 protected static final String JSON_SCHEMA = "json";
61 protected static final String TEMPLATE_SCHEMA = "template";
63 protected static final String STATE_CHANNEL_ID = "state";
64 protected static final String ON_OFF_CHANNEL_ID = "on_off";
65 protected static final String BRIGHTNESS_CHANNEL_ID = "brightness";
66 protected static final String COLOR_MODE_CHANNEL_ID = "color_mode";
67 protected static final String COLOR_TEMP_CHANNEL_ID = "color_temp";
68 protected static final String EFFECT_CHANNEL_ID = "effect";
69 // This channel is a synthetic channel that may send to other channels
71 protected static final String COLOR_CHANNEL_ID = "color";
73 protected static final String DUMMY_TOPIC = "dummy";
75 protected static final String ON_COMMAND_TYPE_FIRST = "first";
76 protected static final String ON_COMMAND_TYPE_BRIGHTNESS = "brightness";
77 protected static final String ON_COMMAND_TYPE_LAST = "last";
80 * Configuration class for MQTT component
82 static class ChannelConfiguration extends AbstractChannelConfiguration {
83 ChannelConfiguration() {
87 /* Attributes that control the basic structure of the light */
89 protected String schema = DEFAULT_SCHEMA;
90 protected @Nullable Boolean optimistic; // All schemas
91 protected boolean brightness = false; // JSON schema only
92 @SerializedName("color_mode")
93 protected boolean colorMode = false; // JSON schema only
94 @SerializedName("supported_color_modes")
95 protected @Nullable List<String> supportedColorModes; // JSON schema only
96 // Defines when on the payload_on is sent. Using last (the default) will send
97 // any style (brightness, color, etc)
98 // topics first and then a payload_on to the command_topic. Using first will
99 // send the payload_on and then any
100 // style topics. Using brightness will only send brightness commands instead of
101 // the payload_on to turn the light
103 @SerializedName("on_command_type")
104 protected String onCommandType = ON_COMMAND_TYPE_LAST; // Default schema only
106 /* Basic control attributes */
108 @SerializedName("state_topic")
109 protected @Nullable String stateTopic; // All Schemas
110 @SerializedName("state_value_template")
111 protected @Nullable String stateValueTemplate; // Default schema only
112 @SerializedName("state_template")
113 protected @Nullable String stateTemplate; // Template schema only
114 @SerializedName("payload_on")
115 protected String payloadOn = "ON"; // Default schema only
116 @SerializedName("payload_off")
117 protected String payloadOff = "OFF"; // Default schema only
118 @SerializedName("command_topic")
119 protected @Nullable String commandTopic; // All schemas
120 @SerializedName("command_on_template")
121 protected @Nullable String commandOnTemplate; // Template schema only; required
122 @SerializedName("command_off_template")
123 protected @Nullable String commandOffTemplate; // Template schema only; required
125 /* Brightness attributes */
127 @SerializedName("brightness_scale")
128 protected int brightnessScale = 255; // Default, JSON schemas only
129 @SerializedName("brightness_state_topic")
130 protected @Nullable String brightnessStateTopic; // Default schema only
131 @SerializedName("brightness_value_template")
132 protected @Nullable String brightnessValueTemplate; // Default schema only
133 @SerializedName("brightness_template")
134 protected @Nullable String brightnessTemplate; // Template schema only
135 @SerializedName("brightness_command_topic")
136 protected @Nullable String brightnessCommandTopic; // Default schema only
137 @SerializedName("brightness_command_template")
138 protected @Nullable String brightnessCommandTemplate; // Default schema only
140 /* White value attributes */
142 @SerializedName("white_scale")
143 protected int whiteScale = 255; // Default, JSON schemas only
144 @SerializedName("white_command_topic")
145 protected @Nullable String whiteCommandTopic; // Default schema only
147 /* Color mode attributes */
149 @SerializedName("color_mode_state_topic")
150 protected @Nullable String colorModeStateTopic; // Default schema only
151 @SerializedName("color_mode_value_template")
152 protected @Nullable String colorModeValueTemplate; // Default schema only
154 /* Color temp attributes */
156 @SerializedName("min_mireds")
157 protected @Nullable Integer minMireds; // All schemas
158 @SerializedName("max_mireds")
159 protected @Nullable Integer maxMireds; // All schemas
160 @SerializedName("color_temp_state_topic")
161 protected @Nullable String colorTempStateTopic; // Default schema only
162 @SerializedName("color_temp_value_template")
163 protected @Nullable String colorTempValueTemplate; // Default schema only
164 @SerializedName("color_temp_template")
165 protected @Nullable String colorTempTemplate; // Template schema only
166 @SerializedName("color_temp_command_topic")
167 protected @Nullable String colorTempCommandTopic; // Default schema only
168 @SerializedName("color_temp_command_template")
169 protected @Nullable String colorTempCommandTemplate; // Default schema only
171 /* Effect attributes */
172 @SerializedName("effect_list")
173 protected @Nullable List<String> effectList; // All schemas
174 @SerializedName("effect_state_topic")
175 protected @Nullable String effectStateTopic; // Default schema only
176 @SerializedName("effect_value_template")
177 protected @Nullable String effectValueTemplate; // Default schema only
178 @SerializedName("effect_template")
179 protected @Nullable String effectTemplate; // Template schema only
180 @SerializedName("effect_command_topic")
181 protected @Nullable String effectCommandTopic; // Default schema only
182 @SerializedName("effect_command_template")
183 protected @Nullable String effectCommandTemplate; // Default schema only
186 @SerializedName("hs_state_topic")
187 protected @Nullable String hsStateTopic; // Default schema only
188 @SerializedName("hs_value_template")
189 protected @Nullable String hsValueTemplate; // Default schema only
190 @SerializedName("hs_command_topic")
191 protected @Nullable String hsCommandTopic; // Default schema only
194 @SerializedName("rgb_state_topic")
195 protected @Nullable String rgbStateTopic; // Default schema only
196 @SerializedName("rgb_value_template")
197 protected @Nullable String rgbValueTemplate; // Default schema only
198 @SerializedName("red_template")
199 protected @Nullable String redTemplate; // Template schema only
200 @SerializedName("green_template")
201 protected @Nullable String greenTemplate; // Template schema only
202 @SerializedName("blue_template")
203 protected @Nullable String blueTemplate; // Template schema only
204 @SerializedName("rgb_command_topic")
205 protected @Nullable String rgbCommandTopic; // Default schema only
206 @SerializedName("rgb_command_template")
207 protected @Nullable String rgbCommandTemplate; // Default schema only
209 /* RGBW attributes */
210 @SerializedName("rgbw_state_topic")
211 protected @Nullable String rgbwStateTopic; // Default schema only
212 @SerializedName("rgbw_value_template")
213 protected @Nullable String rgbwValueTemplate; // Default schema only
214 @SerializedName("rgbw_command_topic")
215 protected @Nullable String rgbwCommandTopic; // Default schema only
216 @SerializedName("rgbw_command_template")
217 protected @Nullable String rgbwCommandTemplate; // Default schema only
219 /* RGBWW attributes */
220 @SerializedName("rgbww_state_topic")
221 protected @Nullable String rgbwwStateTopic; // Default schema only
222 @SerializedName("rgbww_value_template")
223 protected @Nullable String rgbwwValueTemplate; // Default schema only
224 @SerializedName("rgbww_command_topic")
225 protected @Nullable String rgbwwCommandTopic; // Default schema only
226 @SerializedName("rgbww_command_template")
227 protected @Nullable String rgbwwCommandTemplate; // Default schema only
230 @SerializedName("xy_command_topic")
231 protected @Nullable String xyCommandTopic; // Default schema only
232 @SerializedName("xy_state_topic")
233 protected @Nullable String xyStateTopic; // Default schema only
234 @SerializedName("xy_value_template")
235 protected @Nullable String xyValueTemplate; // Default schema only
238 protected final boolean optimistic;
239 protected boolean hasColorChannel = false;
241 protected @Nullable ComponentChannel onOffChannel;
242 protected @Nullable ComponentChannel brightnessChannel;
244 // State has to be stored here, in order to mux multiple
245 // MQTT sources into single OpenHAB channels
246 protected OnOffValue onOffValue;
247 protected PercentageValue brightnessValue;
248 protected final NumberValue colorTempValue;
249 protected final TextValue effectValue = new TextValue();
250 protected final ColorValue colorValue = new ColorValue(ColorMode.HSB, null, null, 100);
252 protected final List<ComponentChannel> hiddenChannels = new ArrayList<>();
253 protected final ChannelStateUpdateListener channelStateUpdateListener;
255 public static Light create(ComponentFactory.ComponentConfiguration builder) throws UnsupportedComponentException {
256 String schema = builder.getConfig(ChannelConfiguration.class).schema;
259 return new DefaultSchemaLight(builder);
261 throw new UnsupportedComponentException(
262 "Component '" + builder.getHaID() + "' of schema '" + schema + "' is not supported!");
266 protected Light(ComponentFactory.ComponentConfiguration builder) {
267 super(builder, ChannelConfiguration.class);
268 this.channelStateUpdateListener = builder.getUpdateListener();
271 Boolean optimistic = channelConfiguration.optimistic;
272 if (optimistic != null) {
273 this.optimistic = optimistic;
275 this.optimistic = (channelConfiguration.stateTopic == null);
278 onOffValue = new OnOffValue(channelConfiguration.payloadOn, channelConfiguration.payloadOff);
279 brightnessValue = new PercentageValue(null, new BigDecimal(channelConfiguration.brightnessScale), null, null,
282 BigDecimal min = null, max = null;
283 if (channelConfiguration.minMireds != null) {
284 min = new BigDecimal(channelConfiguration.minMireds);
286 if (channelConfiguration.maxMireds != null) {
287 max = new BigDecimal(channelConfiguration.maxMireds);
289 colorTempValue = new NumberValue(min, max, BigDecimal.ONE, Units.MIRED);
294 protected abstract void buildChannels();
297 public CompletableFuture<@Nullable Void> start(MqttBrokerConnection connection, ScheduledExecutorService scheduler,
299 return Stream.concat(channels.values().stream(), hiddenChannels.stream()) //
300 .map(v -> v.start(connection, scheduler, timeout)) //
301 .reduce(CompletableFuture.completedFuture(null), (f, v) -> f.thenCompose(b -> v));
305 public CompletableFuture<@Nullable Void> stop() {
306 return Stream.concat(channels.values().stream(), hiddenChannels.stream()) //
307 .filter(Objects::nonNull) //
308 .map(ComponentChannel::stop) //
309 .reduce(CompletableFuture.completedFuture(null), (f, v) -> f.thenCompose(b -> v));
313 public void postChannelCommand(ChannelUID channelUID, Command value) {
314 throw new UnsupportedOperationException();
318 public void triggerChannel(ChannelUID channelUID, String eventPayload) {
319 throw new UnsupportedOperationException();