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