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.hue.internal.handler;
15 import static org.junit.jupiter.api.Assertions.assertEquals;
16 import static org.mockito.ArgumentMatchers.*;
17 import static org.mockito.Mockito.*;
18 import static org.openhab.binding.hue.internal.HueBindingConstants.*;
22 import org.eclipse.jdt.annotation.NonNullByDefault;
23 import org.eclipse.jdt.annotation.Nullable;
24 import org.junit.jupiter.api.Test;
25 import org.mockito.ArgumentCaptor;
26 import org.openhab.binding.hue.internal.api.dto.clip1.FullConfig;
27 import org.openhab.binding.hue.internal.api.dto.clip1.FullLight;
28 import org.openhab.binding.hue.internal.api.dto.clip1.State.ColorMode;
29 import org.openhab.binding.hue.internal.api.dto.clip1.StateUpdate;
30 import org.openhab.core.config.core.Configuration;
31 import org.openhab.core.library.types.DecimalType;
32 import org.openhab.core.library.types.HSBType;
33 import org.openhab.core.library.types.IncreaseDecreaseType;
34 import org.openhab.core.library.types.OnOffType;
35 import org.openhab.core.library.types.PercentType;
36 import org.openhab.core.library.types.QuantityType;
37 import org.openhab.core.library.types.StringType;
38 import org.openhab.core.library.unit.Units;
39 import org.openhab.core.thing.Bridge;
40 import org.openhab.core.thing.ChannelUID;
41 import org.openhab.core.thing.Thing;
42 import org.openhab.core.thing.ThingStatus;
43 import org.openhab.core.thing.ThingUID;
44 import org.openhab.core.thing.binding.ThingHandlerCallback;
45 import org.openhab.core.types.Command;
47 import com.google.gson.Gson;
48 import com.google.gson.JsonElement;
49 import com.google.gson.JsonParser;
52 * Tests for {@link HueLightHandler}.
54 * @author Oliver Libutzki - Initial contribution
55 * @author Michael Grammling - Initial contribution
56 * @author Markus Mazurczak - Added test for OSRAM Par16 50 TW bulbs
57 * @author Andre Fuechsel - modified tests after introducing the generic thing types
58 * @author Denis Dudnik - switched to internally integrated source of Jue library
59 * @author Simon Kaufmann - migrated to plain Java test
60 * @author Christoph Weitkamp - Added support for bulbs using CIE XY colormode only
61 * @author Jacob Laursen - Add workaround for LK Wiser products
64 public class HueLightHandlerTest {
66 private static final int MIN_COLOR_TEMPERATURE = 153;
67 private static final int MAX_COLOR_TEMPERATURE = 500;
68 private static final int COLOR_TEMPERATURE_RANGE = MAX_COLOR_TEMPERATURE - MIN_COLOR_TEMPERATURE;
70 private static final String OSRAM = "OSRAM";
71 private static final String OSRAM_MODEL_TYPE = HueLightHandler.OSRAM_PAR16_50_TW_MODEL_ID;
72 private static final String OSRAM_MODEL_TYPE_ID = HueLightHandler.OSRAM_PAR16_50_TW_MODEL_ID;
74 private final Gson gson = new Gson();
77 public void assertCommandForOsramPar1650ForColorTemperatureChannelOn() {
78 String expectedReply = "{\"on\" : true, \"bri\" : 254}";
79 assertSendCommandForColorTempForPar16(OnOffType.ON, new HueLightState(OSRAM_MODEL_TYPE, OSRAM), expectedReply);
83 public void assertCommandForOsramPar1650ForColorTemperatureChannelOff() {
84 String expectedReply = "{\"on\" : false, \"transitiontime\" : 0}";
85 assertSendCommandForColorTempForPar16(OnOffType.OFF, new HueLightState(OSRAM_MODEL_TYPE, OSRAM), expectedReply);
89 public void assertCommandForOsramPar1650ForBrightnessChannelOn() {
90 String expectedReply = "{\"on\" : true, \"bri\" : 254}";
91 assertSendCommandForBrightnessForPar16(OnOffType.ON, new HueLightState(OSRAM_MODEL_TYPE, OSRAM), expectedReply);
95 public void assertCommandForOsramPar1650ForBrightnessChannelOff() {
96 String expectedReply = "{\"on\" : false, \"transitiontime\" : 0}";
97 assertSendCommandForBrightnessForPar16(OnOffType.OFF, new HueLightState(OSRAM_MODEL_TYPE, OSRAM),
102 public void assertCommandForLkWiserForBrightnessChannelOff() {
103 final String expectedReply = "{\"on\" : false, \"transitiontime\" : 0}";
104 final String vendor = "Schneider Electric";
105 assertSendCommand(CHANNEL_BRIGHTNESS, OnOffType.OFF,
106 new HueLightState(HueLightHandler.LK_WISER_MODEL_ID, vendor), expectedReply,
107 HueLightHandler.LK_WISER_MODEL_ID, vendor);
111 public void assertCommandForColorChannelOn() {
112 String expectedReply = "{\"on\" : true}";
113 assertSendCommandForColor(OnOffType.ON, new HueLightState(), expectedReply);
117 public void assertCommandForColorTemperatureChannelOn() {
118 String expectedReply = "{\"on\" : true}";
119 assertSendCommandForColorTemp(OnOffType.ON, new HueLightState(), expectedReply);
123 public void assertCommandForColorChannelOff() {
124 String expectedReply = "{\"on\" : false}";
125 assertSendCommandForColor(OnOffType.OFF, new HueLightState(), expectedReply);
129 public void assertCommandForColorTemperatureChannelOff() {
130 String expectedReply = "{\"on\" : false}";
131 assertSendCommandForColorTemp(OnOffType.OFF, new HueLightState(), expectedReply);
135 public void assertCommandForColorTemperatureChannel0Percent() {
136 String expectedReply = "{\"ct\" : 153, \"transitiontime\" : 4}";
137 assertSendCommandForColorTemp(new PercentType(0), new HueLightState(), expectedReply);
141 public void assertCommandForColorTemperatureChannel50Percent() {
142 String expectedReply = "{\"ct\" : 327, \"transitiontime\" : 4}";
143 assertSendCommandForColorTemp(new PercentType(50), new HueLightState(), expectedReply);
147 public void assertCommandForColorTemperatureChannel1000Percent() {
148 String expectedReply = "{\"ct\" : 500, \"transitiontime\" : 4}";
149 assertSendCommandForColorTemp(new PercentType(100), new HueLightState(), expectedReply);
153 public void assertDecimalTypeCommandForColorTemperatureAbsChannel6500Kelvin() {
154 String expectedReply = "{\"ct\" : 153, \"transitiontime\" : 4}";
155 assertSendCommandForColorTempAbs(new DecimalType(6500), new HueLightState(), expectedReply);
159 public void assertDecimalTypeCommandForColorTemperatureAbsChannel4500Kelvin() {
160 String expectedReply = "{\"ct\" : 222, \"transitiontime\" : 4}";
161 assertSendCommandForColorTempAbs(new DecimalType(4500), new HueLightState(), expectedReply);
165 public void assertDecimalTypeCommandForColorTemperatureAbsChannel2000Kelvin() {
166 String expectedReply = "{\"ct\" : 500, \"transitiontime\" : 4}";
167 assertSendCommandForColorTempAbs(new DecimalType(2000), new HueLightState(), expectedReply);
171 public void assertQuantityTypeCommandForColorTemperatureAbsChannel6500Kelvin() {
172 String expectedReply = "{\"ct\" : 153, \"transitiontime\" : 4}";
173 assertSendCommandForColorTempAbs(new QuantityType<>(6500, Units.KELVIN), new HueLightState(), expectedReply);
177 public void assertQuantityTypeCommandForColorTemperatureAbsChannel4500Kelvin() {
178 String expectedReply = "{\"ct\" : 222, \"transitiontime\" : 4}";
179 assertSendCommandForColorTempAbs(new QuantityType<>(4500, Units.KELVIN), new HueLightState(), expectedReply);
183 public void assertQuantityTypeCommandForColorTemperatureAbsChannel2000Kelvin() {
184 String expectedReply = "{\"ct\" : 500, \"transitiontime\" : 4}";
185 assertSendCommandForColorTempAbs(new QuantityType<>(2000, Units.KELVIN), new HueLightState(), expectedReply);
189 public void assertQuantityTypeCommandForColorTemperatureAbsChannel500Mired() {
190 String expectedReply = "{\"ct\" : 500, \"transitiontime\" : 4}";
191 assertSendCommandForColorTempAbs(new QuantityType<>(500, Units.MIRED), new HueLightState(), expectedReply);
195 public void assertPercentageValueOfColorTemperatureWhenCt153() {
196 int expectedReply = 0;
197 asserttoColorTemperaturePercentType(153, expectedReply);
201 public void assertPercentageValueOfColorTemperatureWhenCt326() {
202 int expectedReply = 50;
203 asserttoColorTemperaturePercentType(326, expectedReply);
207 public void assertPercentageValueOfColorTemperatureWhenCt500() {
208 int expectedReply = 100;
209 asserttoColorTemperaturePercentType(500, expectedReply);
213 public void assertCommandForColorChannel0Percent() {
214 String expectedReply = "{\"on\" : false, \"transitiontime\" : 4}";
215 assertSendCommandForColor(new PercentType(0), new HueLightState(), expectedReply);
219 public void assertCommandForColorChannel50Percent() {
220 String expectedReply = "{\"bri\" : 127, \"on\" : true, \"transitiontime\" : 4}";
221 assertSendCommandForColor(new PercentType(50), new HueLightState(), expectedReply);
225 public void assertCommandForColorChannel100Percent() {
226 String expectedReply = "{\"bri\" : 254, \"on\" : true, \"transitiontime\" : 4}";
227 assertSendCommandForColor(new PercentType(100), new HueLightState(), expectedReply);
231 public void assertCommandForColorChannelBlack() {
232 String expectedReply = "{\"on\" : false}";
233 assertSendCommandForColor(HSBType.BLACK, new HueLightState(), expectedReply);
237 public void assertCommandForColorChannelRed() {
238 String expectedReply = "{\"bri\" : 254, \"sat\" : 254, \"hue\" : 0, \"transitiontime\" : 4}";
239 assertSendCommandForColor(HSBType.RED, new HueLightState(), expectedReply);
243 public void assertCommandForColorChannelGreen() {
244 String expectedReply = "{\"bri\" : 254, \"sat\" : 254, \"hue\" : 21845, \"transitiontime\" : 4}";
245 assertSendCommandForColor(HSBType.GREEN, new HueLightState(), expectedReply);
249 public void assertCommandForColorChannelBlue() {
250 String expectedReply = "{\"bri\" : 254, \"sat\" : 254, \"hue\" : 43690, \"transitiontime\" : 4}";
251 assertSendCommandForColor(HSBType.BLUE, new HueLightState(), expectedReply);
255 public void assertCommandForColorChannelWhite() {
256 String expectedReply = "{\"bri\" : 254, \"sat\" : 0, \"hue\" : 0, \"transitiontime\" : 4}";
257 assertSendCommandForColor(HSBType.WHITE, new HueLightState(), expectedReply);
261 public void assertXYCommandForColorChannelBlack() {
262 String expectedReply = "{\"on\" : false}";
263 assertSendCommandForColor(HSBType.BLACK, new HueLightState().colormode(ColorMode.XY), expectedReply);
267 public void assertXYCommandForColorChannelWhite() {
268 String expectedReply = "{\"xy\" : [ 0.3227 , 0.32900003 ], \"bri\" : 254, \"transitiontime\" : 4}";
269 assertSendCommandForColor(HSBType.WHITE, new HueLightState().colormode(ColorMode.XY), expectedReply);
273 public void assertXYCommandForColorChannelColorful() {
274 String expectedReply = "{\"xy\" : [ 0.14649999 , 0.115600005 ], \"bri\" : 127, \"transitiontime\" : 4}";
275 assertSendCommandForColor(new HSBType("220,90,50"), new HueLightState().colormode(ColorMode.XY), expectedReply);
279 public void assertCommandForColorChannelIncrease() {
280 HueLightState currentState = new HueLightState().bri(1).on(false);
281 String expectedReply = "{\"bri\" : 30, \"on\" : true, \"transitiontime\" : 4}";
282 assertSendCommandForColor(IncreaseDecreaseType.INCREASE, currentState, expectedReply);
284 currentState.bri(200).on(true);
285 expectedReply = "{\"bri\" : 230, \"transitiontime\" : 4}";
286 assertSendCommandForColor(IncreaseDecreaseType.INCREASE, currentState, expectedReply);
288 currentState.bri(230);
289 expectedReply = "{\"bri\" : 254, \"transitiontime\" : 4}";
290 assertSendCommandForColor(IncreaseDecreaseType.INCREASE, currentState, expectedReply);
294 public void assertCommandForColorChannelDecrease() {
295 HueLightState currentState = new HueLightState().bri(200);
296 String expectedReply = "{\"bri\" : 170, \"transitiontime\" : 4}";
297 assertSendCommandForColor(IncreaseDecreaseType.DECREASE, currentState, expectedReply);
299 currentState.bri(20);
300 expectedReply = "{\"on\" : false, \"transitiontime\" : 4}";
301 assertSendCommandForColor(IncreaseDecreaseType.DECREASE, currentState, expectedReply);
305 public void assertCommandForBrightnessChannel50Percent() {
306 HueLightState currentState = new HueLightState();
307 String expectedReply = "{\"bri\" : 127, \"on\" : true, \"transitiontime\" : 4}";
308 assertSendCommandForBrightness(new PercentType(50), currentState, expectedReply);
312 public void assertCommandForBrightnessChannelIncrease() {
313 HueLightState currentState = new HueLightState().bri(1).on(false);
314 String expectedReply = "{\"bri\" : 30, \"on\" : true, \"transitiontime\" : 4}";
315 assertSendCommandForBrightness(IncreaseDecreaseType.INCREASE, currentState, expectedReply);
317 currentState.bri(200).on(true);
318 expectedReply = "{\"bri\" : 230, \"transitiontime\" : 4}";
319 assertSendCommandForBrightness(IncreaseDecreaseType.INCREASE, currentState, expectedReply);
321 currentState.bri(230);
322 expectedReply = "{\"bri\" : 254, \"transitiontime\" : 4}";
323 assertSendCommandForBrightness(IncreaseDecreaseType.INCREASE, currentState, expectedReply);
327 public void assertCommandForBrightnessChannelDecrease() {
328 HueLightState currentState = new HueLightState().bri(200);
329 String expectedReply = "{\"bri\" : 170, \"transitiontime\" : 4}";
330 assertSendCommandForBrightness(IncreaseDecreaseType.DECREASE, currentState, expectedReply);
332 currentState.bri(20);
333 expectedReply = "{\"on\" : false, \"transitiontime\" : 4}";
334 assertSendCommandForBrightness(IncreaseDecreaseType.DECREASE, currentState, expectedReply);
338 public void assertCommandForBrightnessChannelOff() {
339 HueLightState currentState = new HueLightState();
340 String expectedReply = "{\"on\" : false}";
341 assertSendCommandForBrightness(OnOffType.OFF, currentState, expectedReply);
345 public void assertCommandForBrightnessChannelOn() {
346 HueLightState currentState = new HueLightState();
347 String expectedReply = "{\"on\" : true}";
348 assertSendCommandForBrightness(OnOffType.ON, currentState, expectedReply);
352 public void assertCommandForAlertChannel() {
353 HueLightState currentState = new HueLightState().alert("NONE");
354 String expectedReply = "{\"alert\" : \"none\"}";
355 assertSendCommandForAlert(new StringType("NONE"), currentState, expectedReply);
357 currentState.alert("NONE");
358 expectedReply = "{\"alert\" : \"select\"}";
359 assertSendCommandForAlert(new StringType("SELECT"), currentState, expectedReply);
361 currentState.alert("LSELECT");
362 expectedReply = "{\"alert\" : \"lselect\"}";
363 assertSendCommandForAlert(new StringType("LSELECT"), currentState, expectedReply);
367 public void assertCommandForEffectChannel() {
368 HueLightState currentState = new HueLightState().effect("ON");
369 String expectedReply = "{\"effect\" : \"colorloop\"}";
370 assertSendCommandForEffect(OnOffType.ON, currentState, expectedReply);
372 currentState.effect("OFF");
373 expectedReply = "{\"effect\" : \"none\"}";
374 assertSendCommandForEffect(OnOffType.OFF, currentState, expectedReply);
377 private void assertSendCommandForColorTempForPar16(Command command, HueLightState currentState,
378 String expectedReply) {
379 assertSendCommand(CHANNEL_COLORTEMPERATURE, command, currentState, expectedReply, OSRAM_MODEL_TYPE_ID, OSRAM);
382 private void assertSendCommandForBrightnessForPar16(Command command, HueLightState currentState,
383 String expectedReply) {
384 assertSendCommand(CHANNEL_BRIGHTNESS, command, currentState, expectedReply, OSRAM_MODEL_TYPE_ID, OSRAM);
387 private void assertSendCommandForColor(Command command, HueLightState currentState, String expectedReply) {
388 assertSendCommand(CHANNEL_COLOR, command, currentState, expectedReply);
391 private void assertSendCommandForColorTemp(Command command, HueLightState currentState, String expectedReply) {
392 assertSendCommand(CHANNEL_COLORTEMPERATURE, command, currentState, expectedReply);
395 private void assertSendCommandForColorTempAbs(Command command, HueLightState currentState, String expectedReply) {
396 assertSendCommand(CHANNEL_COLORTEMPERATURE_ABS, command, currentState, expectedReply);
399 private void asserttoColorTemperaturePercentType(int ctValue, int expectedPercent) {
400 int percent = (int) Math.round(((ctValue - MIN_COLOR_TEMPERATURE) * 100.0) / COLOR_TEMPERATURE_RANGE);
401 assertEquals(percent, expectedPercent);
404 private void assertSendCommandForBrightness(Command command, HueLightState currentState, String expectedReply) {
405 assertSendCommand(CHANNEL_BRIGHTNESS, command, currentState, expectedReply);
408 private void assertSendCommandForAlert(Command command, HueLightState currentState, String expectedReply) {
409 assertSendCommand(CHANNEL_ALERT, command, currentState, expectedReply);
412 private void assertSendCommandForEffect(Command command, HueLightState currentState, String expectedReply) {
413 assertSendCommand(CHANNEL_EFFECT, command, currentState, expectedReply);
416 private void assertSendCommand(String channel, Command command, HueLightState currentState, String expectedReply) {
417 assertSendCommand(channel, command, currentState, expectedReply, "LCT001", "Philips");
420 @SuppressWarnings("null")
421 private void assertSendCommand(String channel, Command command, HueLightState currentState, String expectedReply,
422 String expectedModel, String expectedVendor) {
423 FullLight light = gson.fromJson(currentState.toString(), FullConfig.class).getLights().get(0);
425 Bridge mockBridge = mock(Bridge.class);
426 when(mockBridge.getStatus()).thenReturn(ThingStatus.ONLINE);
428 Thing mockThing = mock(Thing.class);
429 when(mockThing.getConfiguration()).thenReturn(new Configuration(Map.of(LIGHT_ID, "1")));
431 HueClient mockClient = mock(HueClient.class);
432 when(mockClient.getLightById(any())).thenReturn(light);
436 HueLightHandler hueLightHandler = new HueLightHandler(mockThing, mock(HueStateDescriptionProvider.class)) {
438 protected synchronized @Nullable HueClient getHueClient() {
443 protected @Nullable Bridge getBridge() {
447 hueLightHandler.setCallback(mock(ThingHandlerCallback.class));
448 hueLightHandler.initialize();
450 verify(mockThing).setProperty(eq(Thing.PROPERTY_MODEL_ID), eq(expectedModel));
451 verify(mockThing).setProperty(eq(Thing.PROPERTY_VENDOR), eq(expectedVendor));
453 hueLightHandler.handleCommand(new ChannelUID(new ThingUID("hue::test"), channel), command);
455 ArgumentCaptor<StateUpdate> captorStateUpdate = ArgumentCaptor.forClass(StateUpdate.class);
456 verify(mockClient).updateLightState(any(LightStatusListener.class), any(FullLight.class),
457 captorStateUpdate.capture(), eq(fadeTime));
458 assertJson(expectedReply, captorStateUpdate.getValue().toJson());
461 private void assertJson(String expected, String actual) {
462 JsonElement jsonExpected = JsonParser.parseString(expected);
463 JsonElement jsonActual = JsonParser.parseString(actual);
464 assertEquals(jsonExpected, jsonActual);