]> git.basschouten.com Git - openhab-addons.git/blob
10947929466e4564386a5c66774f89b868fb90a1
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2024 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 static org.hamcrest.CoreMatchers.is;
16 import static org.hamcrest.CoreMatchers.notNullValue;
17 import static org.hamcrest.CoreMatchers.nullValue;
18 import static org.hamcrest.MatcherAssert.assertThat;
19
20 import java.math.BigDecimal;
21 import java.math.MathContext;
22 import java.util.Set;
23
24 import org.eclipse.jdt.annotation.NonNullByDefault;
25 import org.eclipse.jdt.annotation.Nullable;
26 import org.junit.jupiter.api.Test;
27 import org.openhab.binding.mqtt.generic.values.ColorValue;
28 import org.openhab.binding.mqtt.generic.values.OnOffValue;
29 import org.openhab.binding.mqtt.generic.values.PercentageValue;
30 import org.openhab.binding.mqtt.generic.values.TextValue;
31 import org.openhab.binding.mqtt.homeassistant.internal.ComponentChannel;
32 import org.openhab.core.library.types.DecimalType;
33 import org.openhab.core.library.types.HSBType;
34 import org.openhab.core.library.types.OnOffType;
35 import org.openhab.core.library.types.PercentType;
36 import org.openhab.core.library.types.StringType;
37
38 /**
39  * Tests for {@link Light} confirming to the default schema
40  *
41  * @author Anton Kharuzhy - Initial contribution
42  */
43 @NonNullByDefault
44 public class DefaultSchemaLightTests extends AbstractComponentTests {
45     public static final String CONFIG_TOPIC = "light/0x0000000000000000_light_zigbee2mqtt";
46
47     @Test
48     public void testRgb() throws InterruptedException {
49         // @formatter:off
50         var component = (Light) discoverComponent(configTopicToMqtt(CONFIG_TOPIC),
51                 """
52                 { \
53                   "availability": [ \
54                     { \
55                       "topic": "zigbee2mqtt/bridge/state" \
56                     } \
57                   ], \
58                   "device": { \
59                     "identifiers": [ \
60                       "zigbee2mqtt_0x0000000000000000" \
61                     ], \
62                     "manufacturer": "Lights inc", \
63                     "model": "light v1", \
64                     "name": "Light", \
65                     "sw_version": "Zigbee2MQTT 1.18.2" \
66                   }, \
67                   "name": "light", \
68                   "state_topic": "zigbee2mqtt/light/state", \
69                   "command_topic": "zigbee2mqtt/light/set/state", \
70                   "state_value_template": "{{ value_json.power }}", \
71                   "payload_on": "ON_", \
72                   "payload_off": "OFF_", \
73                   "rgb_state_topic": "zigbee2mqtt/light/rgb", \
74                   "rgb_command_topic": "zigbee2mqtt/light/set/rgb", \
75                   "rgb_value_template": "{{ value_json.rgb }}", \
76                   "brightness_state_topic": "zigbee2mqtt/light/brightness", \
77                   "brightness_command_topic": "zigbee2mqtt/light/set/brightness", \
78                   "brightness_value_template": "{{ value_json.br }}" \
79                 }\
80                 """);
81         // @formatter:on
82
83         assertThat(component.channels.size(), is(1));
84         assertThat(component.getName(), is("light"));
85
86         assertChannel(component, Light.COLOR_CHANNEL_ID, "", "dummy", "Color", ColorValue.class);
87
88         @Nullable
89         ComponentChannel onOffChannel = component.onOffChannel;
90         assertThat(onOffChannel, is(notNullValue()));
91         if (onOffChannel != null) {
92             assertChannel(onOffChannel, "zigbee2mqtt/light/state", "zigbee2mqtt/light/set/state", "On/Off State",
93                     OnOffValue.class);
94         }
95         @Nullable
96         ComponentChannel brightnessChannel = component.brightnessChannel;
97         assertThat(brightnessChannel, is(notNullValue()));
98         if (brightnessChannel != null) {
99             assertChannel(brightnessChannel, "zigbee2mqtt/light/brightness", "zigbee2mqtt/light/set/brightness",
100                     "Brightness", PercentageValue.class);
101         }
102
103         publishMessage("zigbee2mqtt/light/state", "{\"power\": \"ON_\"}");
104         assertState(component, Light.COLOR_CHANNEL_ID, HSBType.WHITE);
105         publishMessage("zigbee2mqtt/light/rgb", "{\"rgb\": \"10,20,30\"}");
106         assertState(component, Light.COLOR_CHANNEL_ID, HSBType.fromRGB(10, 20, 30));
107         publishMessage("zigbee2mqtt/light/rgb", "{\"rgb\": \"255,255,255\"}");
108         assertState(component, Light.COLOR_CHANNEL_ID, HSBType.WHITE);
109
110         sendCommand(component, Light.COLOR_CHANNEL_ID, HSBType.BLUE);
111         assertPublished("zigbee2mqtt/light/set/rgb", "0,0,255");
112
113         // Brightness commands should route to the correct topic
114         sendCommand(component, Light.COLOR_CHANNEL_ID, new PercentType(50));
115         assertPublished("zigbee2mqtt/light/set/brightness", "128");
116
117         // OnOff commands should route to the correct topic
118         sendCommand(component, Light.COLOR_CHANNEL_ID, OnOffType.OFF);
119         assertPublished("zigbee2mqtt/light/set/state", "OFF_");
120     }
121
122     @Test
123     public void testRgbWithoutBrightness() throws InterruptedException {
124         // @formatter:off
125         var component = (Light) discoverComponent(configTopicToMqtt(CONFIG_TOPIC),
126                 """
127                 { \
128                   "name": "light", \
129                   "state_topic": "zigbee2mqtt/light/state", \
130                   "command_topic": "zigbee2mqtt/light/set/state", \
131                   "state_value_template": "{{ value_json.power }}", \
132                   "payload_on": "ON_", \
133                   "payload_off": "OFF_", \
134                   "rgb_state_topic": "zigbee2mqtt/light/rgb", \
135                   "rgb_command_topic": "zigbee2mqtt/light/set/rgb", \
136                   "rgb_value_template": "{{ value_json.rgb }}"\
137                 }\
138                 """);
139         // @formatter:on
140
141         publishMessage("zigbee2mqtt/light/rgb", "{\"rgb\": \"255,255,255\"}");
142         assertState(component, Light.COLOR_CHANNEL_ID, HSBType.WHITE);
143
144         // Brightness commands should route to the correct topic, converted to RGB
145         sendCommand(component, Light.COLOR_CHANNEL_ID, new PercentType(50));
146         assertPublished("zigbee2mqtt/light/set/rgb", "128,128,128");
147
148         // OnOff commands should route to the correct topic
149         sendCommand(component, Light.COLOR_CHANNEL_ID, OnOffType.OFF);
150         assertPublished("zigbee2mqtt/light/set/state", "OFF_");
151     }
152
153     @Test
154     public void testHsb() throws InterruptedException {
155         // @formatter:off
156         var component = (Light) discoverComponent(configTopicToMqtt(CONFIG_TOPIC),
157                 """
158                 { \
159                   "name": "light", \
160                   "state_topic": "zigbee2mqtt/light/state", \
161                   "command_topic": "zigbee2mqtt/light/set/state", \
162                   "state_value_template": "{{ value_json.power }}", \
163                   "payload_on": "ON_", \
164                   "payload_off": "OFF_", \
165                   "hs_state_topic": "zigbee2mqtt/light/hs", \
166                   "hs_command_topic": "zigbee2mqtt/light/set/hs", \
167                   "hs_value_template": "{{ value_json.hs }}", \
168                   "brightness_state_topic": "zigbee2mqtt/light/brightness", \
169                   "brightness_command_topic": "zigbee2mqtt/light/set/brightness", \
170                   "brightness_value_template": "{{ value_json.br }}" \
171                 }\
172                 """);
173         // @formatter:on
174
175         assertThat(component.channels.size(), is(1));
176         assertThat(component.getName(), is("light"));
177
178         assertChannel(component, Light.COLOR_CHANNEL_ID, "", "dummy", "Color", ColorValue.class);
179
180         @Nullable
181         ComponentChannel onOffChannel = component.onOffChannel;
182         assertThat(onOffChannel, is(notNullValue()));
183         if (onOffChannel != null) {
184             assertChannel(onOffChannel, "zigbee2mqtt/light/state", "zigbee2mqtt/light/set/state", "On/Off State",
185                     OnOffValue.class);
186         }
187         @Nullable
188         ComponentChannel brightnessChannel = component.brightnessChannel;
189         assertThat(brightnessChannel, is(notNullValue()));
190         if (brightnessChannel != null) {
191             assertChannel(brightnessChannel, "zigbee2mqtt/light/brightness", "zigbee2mqtt/light/set/brightness",
192                     "Brightness", PercentageValue.class);
193         }
194
195         publishMessage("zigbee2mqtt/light/hs", "{\"hs\": \"180,50\"}");
196         publishMessage("zigbee2mqtt/light/brightness", "{\"br\": \"128\"}");
197         assertState(component, Light.COLOR_CHANNEL_ID, new HSBType(new DecimalType(180), new PercentType(50),
198                 new PercentType(new BigDecimal(128 * 100).divide(new BigDecimal(255), MathContext.DECIMAL128))));
199
200         sendCommand(component, Light.COLOR_CHANNEL_ID, HSBType.BLUE);
201         assertPublished("zigbee2mqtt/light/set/brightness", "255");
202         assertPublished("zigbee2mqtt/light/set/hs", "240,100");
203
204         // Brightness commands should route to the correct topic
205         sendCommand(component, Light.COLOR_CHANNEL_ID, new PercentType(50));
206         assertPublished("zigbee2mqtt/light/set/brightness", "128");
207
208         // OnOff commands should route to the correct topic
209         sendCommand(component, Light.COLOR_CHANNEL_ID, OnOffType.OFF);
210         assertPublished("zigbee2mqtt/light/set/state", "OFF_");
211     }
212
213     @Test
214     public void testBrightnessAndOnOff() throws InterruptedException {
215         // @formatter:off
216         var component = (Light) discoverComponent(configTopicToMqtt(CONFIG_TOPIC),
217                 """
218                 { \
219                   "name": "light", \
220                   "state_topic": "zigbee2mqtt/light/state", \
221                   "command_topic": "zigbee2mqtt/light/set/state", \
222                   "state_value_template": "{{ value_json.power }}", \
223                   "brightness_state_topic": "zigbee2mqtt/light/brightness", \
224                   "brightness_command_topic": "zigbee2mqtt/light/set/brightness", \
225                   "payload_on": "ON_", \
226                   "payload_off": "OFF_" \
227                 }\
228                 """);
229         // @formatter:on
230
231         assertThat(component.channels.size(), is(1));
232         assertThat(component.getName(), is("light"));
233
234         assertChannel(component, Light.BRIGHTNESS_CHANNEL_ID, "zigbee2mqtt/light/brightness",
235                 "zigbee2mqtt/light/set/brightness", "Brightness", PercentageValue.class);
236         @Nullable
237         ComponentChannel onOffChannel = component.onOffChannel;
238         assertThat(onOffChannel, is(notNullValue()));
239         if (onOffChannel != null) {
240             assertChannel(onOffChannel, "zigbee2mqtt/light/state", "zigbee2mqtt/light/set/state", "On/Off State",
241                     OnOffValue.class);
242         }
243
244         publishMessage("zigbee2mqtt/light/brightness", "128");
245         assertState(component, Light.BRIGHTNESS_CHANNEL_ID,
246                 new PercentType(new BigDecimal(128 * 100).divide(new BigDecimal(255), MathContext.DECIMAL128)));
247         publishMessage("zigbee2mqtt/light/brightness", "64");
248         assertState(component, Light.BRIGHTNESS_CHANNEL_ID,
249                 new PercentType(new BigDecimal(64 * 100).divide(new BigDecimal(255), MathContext.DECIMAL128)));
250
251         sendCommand(component, Light.BRIGHTNESS_CHANNEL_ID, OnOffType.OFF);
252         assertPublished("zigbee2mqtt/light/set/state", "OFF_");
253
254         sendCommand(component, Light.BRIGHTNESS_CHANNEL_ID, OnOffType.ON);
255         assertPublished("zigbee2mqtt/light/set/state", "ON_");
256     }
257
258     @Test
259     public void testOnOffOnly() throws InterruptedException {
260         // @formatter:off
261         var component = (Light) discoverComponent(configTopicToMqtt(CONFIG_TOPIC),
262                 """
263                 { \
264                   "name": "light", \
265                   "state_topic": "zigbee2mqtt/light/state", \
266                   "command_topic": "zigbee2mqtt/light/set/state", \
267                   "state_value_template": "{{ value_json.power }}", \
268                   "payload_on": "ON_", \
269                   "payload_off": "OFF_" \
270                 }\
271                 """);
272         // @formatter:on
273
274         assertThat(component.channels.size(), is(1));
275         assertThat(component.getName(), is("light"));
276
277         assertChannel(component, Light.ON_OFF_CHANNEL_ID, "zigbee2mqtt/light/state", "zigbee2mqtt/light/set/state",
278                 "On/Off State", OnOffValue.class);
279         assertThat(component.brightnessChannel, is(nullValue()));
280
281         publishMessage("zigbee2mqtt/light/state", "{\"power\": \"ON_\"}");
282         assertState(component, Light.ON_OFF_CHANNEL_ID, OnOffType.ON);
283         publishMessage("zigbee2mqtt/light/state", "{\"power\": \"OFF_\"}");
284         assertState(component, Light.ON_OFF_CHANNEL_ID, OnOffType.OFF);
285
286         sendCommand(component, Light.ON_OFF_CHANNEL_ID, OnOffType.OFF);
287         assertPublished("zigbee2mqtt/light/set/state", "OFF_");
288     }
289
290     @Test
291     public void testOnOffWithEffect() throws InterruptedException {
292         // @formatter:off
293         var component = (Light) discoverComponent(configTopicToMqtt(CONFIG_TOPIC),
294                 "{ " +
295                         "  \"name\": \"light\", " +
296                         "  \"state_topic\": \"zigbee2mqtt/light/state\", " +
297                         "  \"command_topic\": \"zigbee2mqtt/light/set/state\", " +
298                         "  \"effect_command_topic\": \"zigbee2mqtt/light/set/effect\", " +
299                         "  \"state_value_template\": \"{{ value_json.power }}\", " +
300                         "  \"effect_list\": [\"party\", \"rainbow\"]," +
301                         "  \"effect_state_topic\": \"zigbee2mqtt/light/effect\"" +
302                         "}");
303         // @formatter:on
304
305         assertThat(component.channels.size(), is(2));
306         assertThat(component.getName(), is("light"));
307
308         assertChannel(component, Light.ON_OFF_CHANNEL_ID, "zigbee2mqtt/light/state", "zigbee2mqtt/light/set/state",
309                 "On/Off State", OnOffValue.class);
310         assertChannel(component, Light.EFFECT_CHANNEL_ID, "zigbee2mqtt/light/effect", "zigbee2mqtt/light/set/effect",
311                 "Lighting Effect", TextValue.class);
312
313         publishMessage("zigbee2mqtt/light/state", "{\"power\": \"ON\"}");
314         assertState(component, Light.ON_OFF_CHANNEL_ID, OnOffType.ON);
315         publishMessage("zigbee2mqtt/light/effect", "party");
316         assertState(component, Light.EFFECT_CHANNEL_ID, new StringType("party"));
317         publishMessage("zigbee2mqtt/light/state", "{\"power\": \"OFF\"}");
318         assertState(component, Light.ON_OFF_CHANNEL_ID, OnOffType.OFF);
319
320         sendCommand(component, Light.EFFECT_CHANNEL_ID, new StringType("rainbow"));
321         assertPublished("zigbee2mqtt/light/set/effect", "rainbow");
322     }
323
324     @Override
325     protected Set<String> getConfigTopics() {
326         return Set.of(CONFIG_TOPIC);
327     }
328 }