2 * Copyright (c) 2010-2023 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 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;
20 import java.math.BigDecimal;
21 import java.math.MathContext;
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;
37 * Tests for {@link Light} confirming to the default schema
39 * @author Anton Kharuzhy - Initial contribution
42 public class DefaultSchemaLightTests extends AbstractComponentTests {
43 public static final String CONFIG_TOPIC = "light/0x0000000000000000_light_zigbee2mqtt";
46 public void testRgb() throws InterruptedException {
48 var component = (Light) discoverComponent(configTopicToMqtt(CONFIG_TOPIC),
50 " \"availability\": [ " +
52 " \"topic\": \"zigbee2mqtt/bridge/state\" " +
56 " \"identifiers\": [ " +
57 " \"zigbee2mqtt_0x0000000000000000\" " +
59 " \"manufacturer\": \"Lights inc\", " +
60 " \"model\": \"light v1\", " +
61 " \"name\": \"Light\", " +
62 " \"sw_version\": \"Zigbee2MQTT 1.18.2\" " +
64 " \"name\": \"light\", " +
65 " \"state_topic\": \"zigbee2mqtt/light/state\", " +
66 " \"command_topic\": \"zigbee2mqtt/light/set/state\", " +
67 " \"state_value_template\": \"{{ value_json.power }}\", " +
68 " \"payload_on\": \"ON_\", " +
69 " \"payload_off\": \"OFF_\", " +
70 " \"rgb_state_topic\": \"zigbee2mqtt/light/rgb\", " +
71 " \"rgb_command_topic\": \"zigbee2mqtt/light/set/rgb\", " +
72 " \"rgb_value_template\": \"{{ value_json.rgb }}\", " +
73 " \"brightness_state_topic\": \"zigbee2mqtt/light/brightness\", " +
74 " \"brightness_command_topic\": \"zigbee2mqtt/light/set/brightness\", " +
75 " \"brightness_value_template\": \"{{ value_json.br }}\" " +
79 assertThat(component.channels.size(), is(1));
80 assertThat(component.getName(), is("light"));
82 assertChannel(component, Light.COLOR_CHANNEL_ID, "", "dummy", "Color", ColorValue.class);
85 ComponentChannel onOffChannel = component.onOffChannel;
86 assertThat(onOffChannel, is(notNullValue()));
87 if (onOffChannel != null) {
88 assertChannel(onOffChannel, "zigbee2mqtt/light/state", "zigbee2mqtt/light/set/state", "On/Off State",
92 ComponentChannel brightnessChannel = component.brightnessChannel;
93 assertThat(brightnessChannel, is(notNullValue()));
94 if (brightnessChannel != null) {
95 assertChannel(brightnessChannel, "zigbee2mqtt/light/brightness", "zigbee2mqtt/light/set/brightness",
96 "Brightness", PercentageValue.class);
99 publishMessage("zigbee2mqtt/light/state", "{\"power\": \"ON_\"}");
100 assertState(component, Light.COLOR_CHANNEL_ID, HSBType.WHITE);
101 publishMessage("zigbee2mqtt/light/rgb", "{\"rgb\": \"10,20,30\"}");
102 assertState(component, Light.COLOR_CHANNEL_ID, HSBType.fromRGB(10, 20, 30));
103 publishMessage("zigbee2mqtt/light/rgb", "{\"rgb\": \"255,255,255\"}");
104 assertState(component, Light.COLOR_CHANNEL_ID, HSBType.WHITE);
106 sendCommand(component, Light.COLOR_CHANNEL_ID, HSBType.BLUE);
107 assertPublished("zigbee2mqtt/light/set/rgb", "0,0,255");
109 // Brightness commands should route to the correct topic
110 sendCommand(component, Light.COLOR_CHANNEL_ID, new PercentType(50));
111 assertPublished("zigbee2mqtt/light/set/brightness", "128");
113 // OnOff commands should route to the correct topic
114 sendCommand(component, Light.COLOR_CHANNEL_ID, OnOffType.OFF);
115 assertPublished("zigbee2mqtt/light/set/state", "OFF_");
119 public void testRgbWithoutBrightness() throws InterruptedException {
121 var component = (Light) discoverComponent(configTopicToMqtt(CONFIG_TOPIC),
123 " \"name\": \"light\", " +
124 " \"state_topic\": \"zigbee2mqtt/light/state\", " +
125 " \"command_topic\": \"zigbee2mqtt/light/set/state\", " +
126 " \"state_value_template\": \"{{ value_json.power }}\", " +
127 " \"payload_on\": \"ON_\", " +
128 " \"payload_off\": \"OFF_\", " +
129 " \"rgb_state_topic\": \"zigbee2mqtt/light/rgb\", " +
130 " \"rgb_command_topic\": \"zigbee2mqtt/light/set/rgb\", " +
131 " \"rgb_value_template\": \"{{ value_json.rgb }}\"" +
135 publishMessage("zigbee2mqtt/light/rgb", "{\"rgb\": \"255,255,255\"}");
136 assertState(component, Light.COLOR_CHANNEL_ID, HSBType.WHITE);
138 // Brightness commands should route to the correct topic, converted to RGB
139 sendCommand(component, Light.COLOR_CHANNEL_ID, new PercentType(50));
140 assertPublished("zigbee2mqtt/light/set/rgb", "127,127,127");
142 // OnOff commands should route to the correct topic
143 sendCommand(component, Light.COLOR_CHANNEL_ID, OnOffType.OFF);
144 assertPublished("zigbee2mqtt/light/set/state", "OFF_");
148 public void testHsb() throws InterruptedException {
150 var component = (Light) discoverComponent(configTopicToMqtt(CONFIG_TOPIC),
152 " \"name\": \"light\", " +
153 " \"state_topic\": \"zigbee2mqtt/light/state\", " +
154 " \"command_topic\": \"zigbee2mqtt/light/set/state\", " +
155 " \"state_value_template\": \"{{ value_json.power }}\", " +
156 " \"payload_on\": \"ON_\", " +
157 " \"payload_off\": \"OFF_\", " +
158 " \"hs_state_topic\": \"zigbee2mqtt/light/hs\", " +
159 " \"hs_command_topic\": \"zigbee2mqtt/light/set/hs\", " +
160 " \"hs_value_template\": \"{{ value_json.hs }}\", " +
161 " \"brightness_state_topic\": \"zigbee2mqtt/light/brightness\", " +
162 " \"brightness_command_topic\": \"zigbee2mqtt/light/set/brightness\", " +
163 " \"brightness_value_template\": \"{{ value_json.br }}\" " +
167 assertThat(component.channels.size(), is(1));
168 assertThat(component.getName(), is("light"));
170 assertChannel(component, Light.COLOR_CHANNEL_ID, "", "dummy", "Color", ColorValue.class);
173 ComponentChannel onOffChannel = component.onOffChannel;
174 assertThat(onOffChannel, is(notNullValue()));
175 if (onOffChannel != null) {
176 assertChannel(onOffChannel, "zigbee2mqtt/light/state", "zigbee2mqtt/light/set/state", "On/Off State",
180 ComponentChannel brightnessChannel = component.brightnessChannel;
181 assertThat(brightnessChannel, is(notNullValue()));
182 if (brightnessChannel != null) {
183 assertChannel(brightnessChannel, "zigbee2mqtt/light/brightness", "zigbee2mqtt/light/set/brightness",
184 "Brightness", PercentageValue.class);
187 publishMessage("zigbee2mqtt/light/hs", "{\"hs\": \"180,50\"}");
188 publishMessage("zigbee2mqtt/light/brightness", "{\"br\": \"128\"}");
189 assertState(component, Light.COLOR_CHANNEL_ID, new HSBType(new DecimalType(180), new PercentType(50),
190 new PercentType(new BigDecimal(128 * 100).divide(new BigDecimal(255), MathContext.DECIMAL128))));
192 sendCommand(component, Light.COLOR_CHANNEL_ID, HSBType.BLUE);
193 assertPublished("zigbee2mqtt/light/set/brightness", "255");
194 assertPublished("zigbee2mqtt/light/set/hs", "240,100");
196 // Brightness commands should route to the correct topic
197 sendCommand(component, Light.COLOR_CHANNEL_ID, new PercentType(50));
198 assertPublished("zigbee2mqtt/light/set/brightness", "128");
200 // OnOff commands should route to the correct topic
201 sendCommand(component, Light.COLOR_CHANNEL_ID, OnOffType.OFF);
202 assertPublished("zigbee2mqtt/light/set/state", "OFF_");
206 public void testBrightnessAndOnOff() throws InterruptedException {
208 var component = (Light) discoverComponent(configTopicToMqtt(CONFIG_TOPIC),
210 " \"name\": \"light\", " +
211 " \"state_topic\": \"zigbee2mqtt/light/state\", " +
212 " \"command_topic\": \"zigbee2mqtt/light/set/state\", " +
213 " \"state_value_template\": \"{{ value_json.power }}\", " +
214 " \"brightness_state_topic\": \"zigbee2mqtt/light/brightness\", " +
215 " \"brightness_command_topic\": \"zigbee2mqtt/light/set/brightness\", " +
216 " \"payload_on\": \"ON_\", " +
217 " \"payload_off\": \"OFF_\" " +
221 assertThat(component.channels.size(), is(1));
222 assertThat(component.getName(), is("light"));
224 assertChannel(component, Light.BRIGHTNESS_CHANNEL_ID, "zigbee2mqtt/light/brightness",
225 "zigbee2mqtt/light/set/brightness", "Brightness", PercentageValue.class);
227 ComponentChannel onOffChannel = component.onOffChannel;
228 assertThat(onOffChannel, is(notNullValue()));
229 if (onOffChannel != null) {
230 assertChannel(onOffChannel, "zigbee2mqtt/light/state", "zigbee2mqtt/light/set/state", "On/Off State",
234 publishMessage("zigbee2mqtt/light/brightness", "128");
235 assertState(component, Light.BRIGHTNESS_CHANNEL_ID,
236 new PercentType(new BigDecimal(128 * 100).divide(new BigDecimal(255), MathContext.DECIMAL128)));
237 publishMessage("zigbee2mqtt/light/brightness", "64");
238 assertState(component, Light.BRIGHTNESS_CHANNEL_ID,
239 new PercentType(new BigDecimal(64 * 100).divide(new BigDecimal(255), MathContext.DECIMAL128)));
241 sendCommand(component, Light.BRIGHTNESS_CHANNEL_ID, OnOffType.OFF);
242 assertPublished("zigbee2mqtt/light/set/state", "OFF_");
244 sendCommand(component, Light.BRIGHTNESS_CHANNEL_ID, OnOffType.ON);
245 assertPublished("zigbee2mqtt/light/set/state", "ON_");
249 public void testOnOffOnly() throws InterruptedException {
251 var component = (Light) discoverComponent(configTopicToMqtt(CONFIG_TOPIC),
253 " \"name\": \"light\", " +
254 " \"state_topic\": \"zigbee2mqtt/light/state\", " +
255 " \"command_topic\": \"zigbee2mqtt/light/set/state\", " +
256 " \"state_value_template\": \"{{ value_json.power }}\", " +
257 " \"payload_on\": \"ON_\", " +
258 " \"payload_off\": \"OFF_\" " +
262 assertThat(component.channels.size(), is(1));
263 assertThat(component.getName(), is("light"));
265 assertChannel(component, Light.ON_OFF_CHANNEL_ID, "zigbee2mqtt/light/state", "zigbee2mqtt/light/set/state",
266 "On/Off State", OnOffValue.class);
267 assertThat(component.brightnessChannel, is(nullValue()));
269 publishMessage("zigbee2mqtt/light/state", "{\"power\": \"ON_\"}");
270 assertState(component, Light.ON_OFF_CHANNEL_ID, OnOffType.ON);
271 publishMessage("zigbee2mqtt/light/state", "{\"power\": \"OFF_\"}");
272 assertState(component, Light.ON_OFF_CHANNEL_ID, OnOffType.OFF);
274 sendCommand(component, Light.ON_OFF_CHANNEL_ID, OnOffType.OFF);
275 assertPublished("zigbee2mqtt/light/set/state", "OFF_");
279 protected Set<String> getConfigTopics() {
280 return Set.of(CONFIG_TOPIC);