]> git.basschouten.com Git - openhab-addons.git/blob
782268cdab047c1b60fc745760818eb3274acf54
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
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
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.mqtt.homeassistant.internal.component;
14
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;
22
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;
39
40 import com.google.gson.annotations.SerializedName;
41
42 /**
43  * A MQTT light, following the
44  * https://www.home-assistant.io/components/light.mqtt/ specification.
45  *
46  * Individual concrete classes implement the differing semantics of the
47  * three different schemas.
48  *
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.
52  *
53  * @author David Graeff - Initial contribution
54  * @author Cody Cutrer - Re-write for (nearly) full support
55  */
56 @NonNullByDefault
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";
62
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
70     // underneath
71     protected static final String COLOR_CHANNEL_ID = "color";
72
73     protected static final String DUMMY_TOPIC = "dummy";
74
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";
78
79     /**
80      * Configuration class for MQTT component
81      */
82     static class ChannelConfiguration extends AbstractChannelConfiguration {
83         ChannelConfiguration() {
84             super("MQTT Light");
85         }
86
87         /* Attributes that control the basic structure of the light */
88
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<LightColorMode> 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
102         // on.
103         @SerializedName("on_command_type")
104         protected String onCommandType = ON_COMMAND_TYPE_LAST; // Default schema only
105
106         /* Basic control attributes */
107
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
124
125         /* Brightness attributes */
126
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
139
140         /* White value attributes */
141
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
146
147         /* Color mode attributes */
148
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
153
154         /* Color temp attributes */
155
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
170
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
184
185         /* HS attributes */
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
192
193         /* RGB attributes */
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
208
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
218
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
228
229         /* XY attributes */
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
236     }
237
238     protected final boolean optimistic;
239     protected boolean hasColorChannel = false;
240
241     protected @Nullable ComponentChannel onOffChannel;
242     protected @Nullable ComponentChannel brightnessChannel;
243
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);
251
252     protected final List<ComponentChannel> hiddenChannels = new ArrayList<>();
253     protected final ChannelStateUpdateListener channelStateUpdateListener;
254
255     public static Light create(ComponentFactory.ComponentConfiguration builder) throws UnsupportedComponentException {
256         String schema = builder.getConfig(ChannelConfiguration.class).schema;
257         switch (schema) {
258             case DEFAULT_SCHEMA:
259                 return new DefaultSchemaLight(builder);
260             case JSON_SCHEMA:
261                 return new JSONSchemaLight(builder);
262             default:
263                 throw new UnsupportedComponentException(
264                         "Component '" + builder.getHaID() + "' of schema '" + schema + "' is not supported!");
265         }
266     }
267
268     protected Light(ComponentFactory.ComponentConfiguration builder) {
269         super(builder, ChannelConfiguration.class);
270         this.channelStateUpdateListener = builder.getUpdateListener();
271
272         @Nullable
273         Boolean optimistic = channelConfiguration.optimistic;
274         if (optimistic != null) {
275             this.optimistic = optimistic;
276         } else {
277             this.optimistic = (channelConfiguration.stateTopic == null);
278         }
279
280         onOffValue = new OnOffValue(channelConfiguration.payloadOn, channelConfiguration.payloadOff);
281         brightnessValue = new PercentageValue(null, new BigDecimal(channelConfiguration.brightnessScale), null, null,
282                 null);
283         @Nullable
284         BigDecimal min = null, max = null;
285         if (channelConfiguration.minMireds != null) {
286             min = new BigDecimal(channelConfiguration.minMireds);
287         }
288         if (channelConfiguration.maxMireds != null) {
289             max = new BigDecimal(channelConfiguration.maxMireds);
290         }
291         colorTempValue = new NumberValue(min, max, BigDecimal.ONE, Units.MIRED);
292
293         buildChannels();
294     }
295
296     protected abstract void buildChannels();
297
298     @Override
299     public CompletableFuture<@Nullable Void> start(MqttBrokerConnection connection, ScheduledExecutorService scheduler,
300             int timeout) {
301         return Stream.concat(channels.values().stream(), hiddenChannels.stream()) //
302                 .map(v -> v.start(connection, scheduler, timeout)) //
303                 .reduce(CompletableFuture.completedFuture(null), (f, v) -> f.thenCompose(b -> v));
304     }
305
306     @Override
307     public CompletableFuture<@Nullable Void> stop() {
308         return Stream.concat(channels.values().stream(), hiddenChannels.stream()) //
309                 .filter(Objects::nonNull) //
310                 .map(ComponentChannel::stop) //
311                 .reduce(CompletableFuture.completedFuture(null), (f, v) -> f.thenCompose(b -> v));
312     }
313
314     @Override
315     public void postChannelCommand(ChannelUID channelUID, Command value) {
316         throw new UnsupportedOperationException();
317     }
318
319     @Override
320     public void triggerChannel(ChannelUID channelUID, String eventPayload) {
321         throw new UnsupportedOperationException();
322     }
323 }