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),
53 "topic": "zigbee2mqtt/bridge/state" \
58 "zigbee2mqtt_0x0000000000000000" \
60 "manufacturer": "Lights inc", \
61 "model": "light v1", \
63 "sw_version": "Zigbee2MQTT 1.18.2" \
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 }}" \
81 assertThat(component.channels.size(), is(1));
82 assertThat(component.getName(), is("light"));
84 assertChannel(component, Light.COLOR_CHANNEL_ID, "", "dummy", "Color", ColorValue.class);
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",
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);
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);
108 sendCommand(component, Light.COLOR_CHANNEL_ID, HSBType.BLUE);
109 assertPublished("zigbee2mqtt/light/set/rgb", "0,0,255");
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");
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_");
121 public void testRgbWithoutBrightness() throws InterruptedException {
123 var component = (Light) discoverComponent(configTopicToMqtt(CONFIG_TOPIC),
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 }}"\
139 publishMessage("zigbee2mqtt/light/rgb", "{\"rgb\": \"255,255,255\"}");
140 assertState(component, Light.COLOR_CHANNEL_ID, HSBType.WHITE);
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");
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_");
152 public void testHsb() throws InterruptedException {
154 var component = (Light) discoverComponent(configTopicToMqtt(CONFIG_TOPIC),
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 }}" \
173 assertThat(component.channels.size(), is(1));
174 assertThat(component.getName(), is("light"));
176 assertChannel(component, Light.COLOR_CHANNEL_ID, "", "dummy", "Color", ColorValue.class);
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",
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);
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))));
198 sendCommand(component, Light.COLOR_CHANNEL_ID, HSBType.BLUE);
199 assertPublished("zigbee2mqtt/light/set/brightness", "255");
200 assertPublished("zigbee2mqtt/light/set/hs", "240,100");
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");
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_");
212 public void testBrightnessAndOnOff() throws InterruptedException {
214 var component = (Light) discoverComponent(configTopicToMqtt(CONFIG_TOPIC),
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_" \
229 assertThat(component.channels.size(), is(1));
230 assertThat(component.getName(), is("light"));
232 assertChannel(component, Light.BRIGHTNESS_CHANNEL_ID, "zigbee2mqtt/light/brightness",
233 "zigbee2mqtt/light/set/brightness", "Brightness", PercentageValue.class);
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",
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)));
249 sendCommand(component, Light.BRIGHTNESS_CHANNEL_ID, OnOffType.OFF);
250 assertPublished("zigbee2mqtt/light/set/state", "OFF_");
252 sendCommand(component, Light.BRIGHTNESS_CHANNEL_ID, OnOffType.ON);
253 assertPublished("zigbee2mqtt/light/set/state", "ON_");
257 public void testOnOffOnly() throws InterruptedException {
259 var component = (Light) discoverComponent(configTopicToMqtt(CONFIG_TOPIC),
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_" \
272 assertThat(component.channels.size(), is(1));
273 assertThat(component.getName(), is("light"));
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()));
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);
284 sendCommand(component, Light.ON_OFF_CHANNEL_ID, OnOffType.OFF);
285 assertPublished("zigbee2mqtt/light/set/state", "OFF_");
289 protected Set<String> getConfigTopics() {
290 return Set.of(CONFIG_TOPIC);