2 * Copyright (c) 2010-2022 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.FullConfig;
27 import org.openhab.binding.hue.internal.FullLight;
28 import org.openhab.binding.hue.internal.State.ColorMode;
29 import org.openhab.binding.hue.internal.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.StringType;
37 import org.openhab.core.thing.Bridge;
38 import org.openhab.core.thing.ChannelUID;
39 import org.openhab.core.thing.Thing;
40 import org.openhab.core.thing.ThingStatus;
41 import org.openhab.core.thing.ThingUID;
42 import org.openhab.core.thing.binding.ThingHandlerCallback;
43 import org.openhab.core.types.Command;
45 import com.google.gson.Gson;
46 import com.google.gson.JsonElement;
47 import com.google.gson.JsonParser;
50 * Tests for {@link HueLightHandler}.
52 * @author Oliver Libutzki - Initial contribution
53 * @author Michael Grammling - Initial contribution
54 * @author Markus Mazurczak - Added test for OSRAM Par16 50 TW bulbs
55 * @author Andre Fuechsel - modified tests after introducing the generic thing types
56 * @author Denis Dudnik - switched to internally integrated source of Jue library
57 * @author Simon Kaufmann - migrated to plain Java test
58 * @author Christoph Weitkamp - Added support for bulbs using CIE XY colormode only
59 * @author Jacob Laursen - Add workaround for LK Wiser products
62 public class HueLightHandlerTest {
64 private static final int MIN_COLOR_TEMPERATURE = 153;
65 private static final int MAX_COLOR_TEMPERATURE = 500;
66 private static final int COLOR_TEMPERATURE_RANGE = MAX_COLOR_TEMPERATURE - MIN_COLOR_TEMPERATURE;
68 private static final String OSRAM = "OSRAM";
69 private static final String OSRAM_MODEL_TYPE = HueLightHandler.OSRAM_PAR16_50_TW_MODEL_ID;
70 private static final String OSRAM_MODEL_TYPE_ID = HueLightHandler.OSRAM_PAR16_50_TW_MODEL_ID;
72 private final Gson gson = new Gson();
75 public void assertCommandForOsramPar1650ForColorTemperatureChannelOn() {
76 String expectedReply = "{\"on\" : true, \"bri\" : 254}";
77 assertSendCommandForColorTempForPar16(OnOffType.ON, new HueLightState(OSRAM_MODEL_TYPE, OSRAM), expectedReply);
81 public void assertCommandForOsramPar1650ForColorTemperatureChannelOff() {
82 String expectedReply = "{\"on\" : false, \"transitiontime\" : 0}";
83 assertSendCommandForColorTempForPar16(OnOffType.OFF, new HueLightState(OSRAM_MODEL_TYPE, OSRAM), expectedReply);
87 public void assertCommandForOsramPar1650ForBrightnessChannelOn() {
88 String expectedReply = "{\"on\" : true, \"bri\" : 254}";
89 assertSendCommandForBrightnessForPar16(OnOffType.ON, new HueLightState(OSRAM_MODEL_TYPE, OSRAM), expectedReply);
93 public void assertCommandForOsramPar1650ForBrightnessChannelOff() {
94 String expectedReply = "{\"on\" : false, \"transitiontime\" : 0}";
95 assertSendCommandForBrightnessForPar16(OnOffType.OFF, new HueLightState(OSRAM_MODEL_TYPE, OSRAM),
100 public void assertCommandForLkWiserForBrightnessChannelOff() {
101 final String expectedReply = "{\"on\" : false, \"transitiontime\" : 0}";
102 final String vendor = "Schneider Electric";
103 assertSendCommand(CHANNEL_BRIGHTNESS, OnOffType.OFF,
104 new HueLightState(HueLightHandler.LK_WISER_MODEL_ID, vendor), expectedReply,
105 HueLightHandler.LK_WISER_MODEL_ID, vendor);
109 public void assertCommandForColorChannelOn() {
110 String expectedReply = "{\"on\" : true}";
111 assertSendCommandForColor(OnOffType.ON, new HueLightState(), expectedReply);
115 public void assertCommandForColorTemperatureChannelOn() {
116 String expectedReply = "{\"on\" : true}";
117 assertSendCommandForColorTemp(OnOffType.ON, new HueLightState(), expectedReply);
121 public void assertCommandForColorChannelOff() {
122 String expectedReply = "{\"on\" : false}";
123 assertSendCommandForColor(OnOffType.OFF, new HueLightState(), expectedReply);
127 public void assertCommandForColorTemperatureChannelOff() {
128 String expectedReply = "{\"on\" : false}";
129 assertSendCommandForColorTemp(OnOffType.OFF, new HueLightState(), expectedReply);
133 public void assertCommandForColorTemperatureChannel0Percent() {
134 String expectedReply = "{\"ct\" : 153, \"transitiontime\" : 4}";
135 assertSendCommandForColorTemp(new PercentType(0), new HueLightState(), expectedReply);
139 public void assertCommandForColorTemperatureChannel50Percent() {
140 String expectedReply = "{\"ct\" : 327, \"transitiontime\" : 4}";
141 assertSendCommandForColorTemp(new PercentType(50), new HueLightState(), expectedReply);
145 public void assertCommandForColorTemperatureChannel1000Percent() {
146 String expectedReply = "{\"ct\" : 500, \"transitiontime\" : 4}";
147 assertSendCommandForColorTemp(new PercentType(100), new HueLightState(), expectedReply);
151 public void assertCommandForColorTemperatureAbsChannel6500Kelvin() {
152 String expectedReply = "{\"ct\" : 153, \"transitiontime\" : 4}";
153 assertSendCommandForColorTempAbs(new DecimalType(6500), new HueLightState(), expectedReply);
157 public void assertCommandForColorTemperatureAbsChannel4500Kelvin() {
158 String expectedReply = "{\"ct\" : 222, \"transitiontime\" : 4}";
159 assertSendCommandForColorTempAbs(new DecimalType(4500), new HueLightState(), expectedReply);
163 public void assertCommandForColorTemperatureAbsChannel2000Kelvin() {
164 String expectedReply = "{\"ct\" : 500, \"transitiontime\" : 4}";
165 assertSendCommandForColorTempAbs(new DecimalType(2000), new HueLightState(), expectedReply);
169 public void assertPercentageValueOfColorTemperatureWhenCt153() {
170 int expectedReply = 0;
171 asserttoColorTemperaturePercentType(153, expectedReply);
175 public void assertPercentageValueOfColorTemperatureWhenCt326() {
176 int expectedReply = 50;
177 asserttoColorTemperaturePercentType(326, expectedReply);
181 public void assertPercentageValueOfColorTemperatureWhenCt500() {
182 int expectedReply = 100;
183 asserttoColorTemperaturePercentType(500, expectedReply);
187 public void assertCommandForColorChannel0Percent() {
188 String expectedReply = "{\"on\" : false, \"transitiontime\" : 4}";
189 assertSendCommandForColor(new PercentType(0), new HueLightState(), expectedReply);
193 public void assertCommandForColorChannel50Percent() {
194 String expectedReply = "{\"bri\" : 127, \"on\" : true, \"transitiontime\" : 4}";
195 assertSendCommandForColor(new PercentType(50), new HueLightState(), expectedReply);
199 public void assertCommandForColorChannel100Percent() {
200 String expectedReply = "{\"bri\" : 254, \"on\" : true, \"transitiontime\" : 4}";
201 assertSendCommandForColor(new PercentType(100), new HueLightState(), expectedReply);
205 public void assertCommandForColorChannelBlack() {
206 String expectedReply = "{\"on\" : false}";
207 assertSendCommandForColor(HSBType.BLACK, new HueLightState(), expectedReply);
211 public void assertCommandForColorChannelRed() {
212 String expectedReply = "{\"bri\" : 254, \"sat\" : 254, \"hue\" : 0, \"transitiontime\" : 4}";
213 assertSendCommandForColor(HSBType.RED, new HueLightState(), expectedReply);
217 public void assertCommandForColorChannelGreen() {
218 String expectedReply = "{\"bri\" : 254, \"sat\" : 254, \"hue\" : 21845, \"transitiontime\" : 4}";
219 assertSendCommandForColor(HSBType.GREEN, new HueLightState(), expectedReply);
223 public void assertCommandForColorChannelBlue() {
224 String expectedReply = "{\"bri\" : 254, \"sat\" : 254, \"hue\" : 43690, \"transitiontime\" : 4}";
225 assertSendCommandForColor(HSBType.BLUE, new HueLightState(), expectedReply);
229 public void assertCommandForColorChannelWhite() {
230 String expectedReply = "{\"bri\" : 254, \"sat\" : 0, \"hue\" : 0, \"transitiontime\" : 4}";
231 assertSendCommandForColor(HSBType.WHITE, new HueLightState(), expectedReply);
235 public void assertXYCommandForColorChannelBlack() {
236 String expectedReply = "{\"on\" : false}";
237 assertSendCommandForColor(HSBType.BLACK, new HueLightState().colormode(ColorMode.XY), expectedReply);
241 public void assertXYCommandForColorChannelWhite() {
242 String expectedReply = "{\"xy\" : [ 0.31271592 , 0.32900152 ], \"bri\" : 254, \"transitiontime\" : 4}";
243 assertSendCommandForColor(HSBType.WHITE, new HueLightState().colormode(ColorMode.XY), expectedReply);
247 public void assertXYCommandForColorChannelColorful() {
248 String expectedReply = "{\"xy\" : [ 0.16969365 , 0.12379659 ], \"bri\" : 127, \"transitiontime\" : 4}";
249 assertSendCommandForColor(new HSBType("220,90,50"), new HueLightState().colormode(ColorMode.XY), expectedReply);
253 public void asserCommandForColorChannelIncrease() {
254 HueLightState currentState = new HueLightState().bri(1).on(false);
255 String expectedReply = "{\"bri\" : 30, \"on\" : true, \"transitiontime\" : 4}";
256 assertSendCommandForColor(IncreaseDecreaseType.INCREASE, currentState, expectedReply);
258 currentState.bri(200).on(true);
259 expectedReply = "{\"bri\" : 230, \"transitiontime\" : 4}";
260 assertSendCommandForColor(IncreaseDecreaseType.INCREASE, currentState, expectedReply);
262 currentState.bri(230);
263 expectedReply = "{\"bri\" : 254, \"transitiontime\" : 4}";
264 assertSendCommandForColor(IncreaseDecreaseType.INCREASE, currentState, expectedReply);
268 public void asserCommandForColorChannelDecrease() {
269 HueLightState currentState = new HueLightState().bri(200);
270 String expectedReply = "{\"bri\" : 170, \"transitiontime\" : 4}";
271 assertSendCommandForColor(IncreaseDecreaseType.DECREASE, currentState, expectedReply);
273 currentState.bri(20);
274 expectedReply = "{\"on\" : false, \"transitiontime\" : 4}";
275 assertSendCommandForColor(IncreaseDecreaseType.DECREASE, currentState, expectedReply);
279 public void assertCommandForBrightnessChannel50Percent() {
280 HueLightState currentState = new HueLightState();
281 String expectedReply = "{\"bri\" : 127, \"on\" : true, \"transitiontime\" : 4}";
282 assertSendCommandForBrightness(new PercentType(50), currentState, expectedReply);
286 public void assertCommandForBrightnessChannelIncrease() {
287 HueLightState currentState = new HueLightState().bri(1).on(false);
288 String expectedReply = "{\"bri\" : 30, \"on\" : true, \"transitiontime\" : 4}";
289 assertSendCommandForBrightness(IncreaseDecreaseType.INCREASE, currentState, expectedReply);
291 currentState.bri(200).on(true);
292 expectedReply = "{\"bri\" : 230, \"transitiontime\" : 4}";
293 assertSendCommandForBrightness(IncreaseDecreaseType.INCREASE, currentState, expectedReply);
295 currentState.bri(230);
296 expectedReply = "{\"bri\" : 254, \"transitiontime\" : 4}";
297 assertSendCommandForBrightness(IncreaseDecreaseType.INCREASE, currentState, expectedReply);
301 public void assertCommandForBrightnessChannelDecrease() {
302 HueLightState currentState = new HueLightState().bri(200);
303 String expectedReply = "{\"bri\" : 170, \"transitiontime\" : 4}";
304 assertSendCommandForBrightness(IncreaseDecreaseType.DECREASE, currentState, expectedReply);
306 currentState.bri(20);
307 expectedReply = "{\"on\" : false, \"transitiontime\" : 4}";
308 assertSendCommandForBrightness(IncreaseDecreaseType.DECREASE, currentState, expectedReply);
312 public void assertCommandForBrightnessChannelOff() {
313 HueLightState currentState = new HueLightState();
314 String expectedReply = "{\"on\" : false}";
315 assertSendCommandForBrightness(OnOffType.OFF, currentState, expectedReply);
319 public void assertCommandForBrightnessChannelOn() {
320 HueLightState currentState = new HueLightState();
321 String expectedReply = "{\"on\" : true}";
322 assertSendCommandForBrightness(OnOffType.ON, currentState, expectedReply);
326 public void assertCommandForAlertChannel() {
327 HueLightState currentState = new HueLightState().alert("NONE");
328 String expectedReply = "{\"alert\" : \"none\"}";
329 assertSendCommandForAlert(new StringType("NONE"), currentState, expectedReply);
331 currentState.alert("NONE");
332 expectedReply = "{\"alert\" : \"select\"}";
333 assertSendCommandForAlert(new StringType("SELECT"), currentState, expectedReply);
335 currentState.alert("LSELECT");
336 expectedReply = "{\"alert\" : \"lselect\"}";
337 assertSendCommandForAlert(new StringType("LSELECT"), currentState, expectedReply);
341 public void assertCommandForEffectChannel() {
342 HueLightState currentState = new HueLightState().effect("ON");
343 String expectedReply = "{\"effect\" : \"colorloop\"}";
344 assertSendCommandForEffect(OnOffType.ON, currentState, expectedReply);
346 currentState.effect("OFF");
347 expectedReply = "{\"effect\" : \"none\"}";
348 assertSendCommandForEffect(OnOffType.OFF, currentState, expectedReply);
351 private void assertSendCommandForColorTempForPar16(Command command, HueLightState currentState,
352 String expectedReply) {
353 assertSendCommand(CHANNEL_COLORTEMPERATURE, command, currentState, expectedReply, OSRAM_MODEL_TYPE_ID, OSRAM);
356 private void assertSendCommandForBrightnessForPar16(Command command, HueLightState currentState,
357 String expectedReply) {
358 assertSendCommand(CHANNEL_BRIGHTNESS, command, currentState, expectedReply, OSRAM_MODEL_TYPE_ID, OSRAM);
361 private void assertSendCommandForColor(Command command, HueLightState currentState, String expectedReply) {
362 assertSendCommand(CHANNEL_COLOR, command, currentState, expectedReply);
365 private void assertSendCommandForColorTemp(Command command, HueLightState currentState, String expectedReply) {
366 assertSendCommand(CHANNEL_COLORTEMPERATURE, command, currentState, expectedReply);
369 private void assertSendCommandForColorTempAbs(Command command, HueLightState currentState, String expectedReply) {
370 assertSendCommand(CHANNEL_COLORTEMPERATURE_ABS, command, currentState, expectedReply);
373 private void asserttoColorTemperaturePercentType(int ctValue, int expectedPercent) {
374 int percent = (int) Math.round(((ctValue - MIN_COLOR_TEMPERATURE) * 100.0) / COLOR_TEMPERATURE_RANGE);
375 assertEquals(percent, expectedPercent);
378 private void assertSendCommandForBrightness(Command command, HueLightState currentState, String expectedReply) {
379 assertSendCommand(CHANNEL_BRIGHTNESS, command, currentState, expectedReply);
382 private void assertSendCommandForAlert(Command command, HueLightState currentState, String expectedReply) {
383 assertSendCommand(CHANNEL_ALERT, command, currentState, expectedReply);
386 private void assertSendCommandForEffect(Command command, HueLightState currentState, String expectedReply) {
387 assertSendCommand(CHANNEL_EFFECT, command, currentState, expectedReply);
390 private void assertSendCommand(String channel, Command command, HueLightState currentState, String expectedReply) {
391 assertSendCommand(channel, command, currentState, expectedReply, "LCT001", "Philips");
394 @SuppressWarnings("null")
395 private void assertSendCommand(String channel, Command command, HueLightState currentState, String expectedReply,
396 String expectedModel, String expectedVendor) {
397 FullLight light = gson.fromJson(currentState.toString(), FullConfig.class).getLights().get(0);
399 Bridge mockBridge = mock(Bridge.class);
400 when(mockBridge.getStatus()).thenReturn(ThingStatus.ONLINE);
402 Thing mockThing = mock(Thing.class);
403 when(mockThing.getConfiguration()).thenReturn(new Configuration(Map.of(LIGHT_ID, "1")));
405 HueClient mockClient = mock(HueClient.class);
406 when(mockClient.getLightById(any())).thenReturn(light);
410 HueLightHandler hueLightHandler = new HueLightHandler(mockThing, mock(HueStateDescriptionProvider.class)) {
412 protected synchronized @Nullable HueClient getHueClient() {
417 protected @Nullable Bridge getBridge() {
421 hueLightHandler.setCallback(mock(ThingHandlerCallback.class));
422 hueLightHandler.initialize();
424 verify(mockThing).setProperty(eq(Thing.PROPERTY_MODEL_ID), eq(expectedModel));
425 verify(mockThing).setProperty(eq(Thing.PROPERTY_VENDOR), eq(expectedVendor));
427 hueLightHandler.handleCommand(new ChannelUID(new ThingUID("hue::test"), channel), command);
429 ArgumentCaptor<StateUpdate> captorStateUpdate = ArgumentCaptor.forClass(StateUpdate.class);
430 verify(mockClient).updateLightState(any(LightStatusListener.class), any(FullLight.class),
431 captorStateUpdate.capture(), eq(fadeTime));
432 assertJson(expectedReply, captorStateUpdate.getValue().toJson());
435 private void assertJson(String expected, String actual) {
436 JsonElement jsonExpected = JsonParser.parseString(expected);
437 JsonElement jsonActual = JsonParser.parseString(actual);
438 assertEquals(jsonExpected, jsonActual);