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.clip2;
15 import static org.junit.jupiter.api.Assertions.*;
17 import java.io.BufferedReader;
18 import java.io.FileReader;
19 import java.io.IOException;
20 import java.lang.reflect.Field;
21 import java.time.Duration;
22 import java.time.Instant;
23 import java.time.ZoneId;
24 import java.util.List;
26 import java.util.Optional;
29 import org.eclipse.jdt.annotation.NonNullByDefault;
30 import org.junit.jupiter.api.Test;
31 import org.openhab.binding.hue.internal.dto.clip2.ActionEntry;
32 import org.openhab.binding.hue.internal.dto.clip2.Alerts;
33 import org.openhab.binding.hue.internal.dto.clip2.Button;
34 import org.openhab.binding.hue.internal.dto.clip2.Dimming;
35 import org.openhab.binding.hue.internal.dto.clip2.Effects;
36 import org.openhab.binding.hue.internal.dto.clip2.Event;
37 import org.openhab.binding.hue.internal.dto.clip2.LightLevel;
38 import org.openhab.binding.hue.internal.dto.clip2.MetaData;
39 import org.openhab.binding.hue.internal.dto.clip2.MirekSchema;
40 import org.openhab.binding.hue.internal.dto.clip2.Motion;
41 import org.openhab.binding.hue.internal.dto.clip2.Power;
42 import org.openhab.binding.hue.internal.dto.clip2.ProductData;
43 import org.openhab.binding.hue.internal.dto.clip2.RelativeRotary;
44 import org.openhab.binding.hue.internal.dto.clip2.Resource;
45 import org.openhab.binding.hue.internal.dto.clip2.ResourceReference;
46 import org.openhab.binding.hue.internal.dto.clip2.Resources;
47 import org.openhab.binding.hue.internal.dto.clip2.Rotation;
48 import org.openhab.binding.hue.internal.dto.clip2.RotationEvent;
49 import org.openhab.binding.hue.internal.dto.clip2.Temperature;
50 import org.openhab.binding.hue.internal.dto.clip2.TimedEffects;
51 import org.openhab.binding.hue.internal.dto.clip2.enums.ActionType;
52 import org.openhab.binding.hue.internal.dto.clip2.enums.Archetype;
53 import org.openhab.binding.hue.internal.dto.clip2.enums.BatteryStateType;
54 import org.openhab.binding.hue.internal.dto.clip2.enums.ButtonEventType;
55 import org.openhab.binding.hue.internal.dto.clip2.enums.DirectionType;
56 import org.openhab.binding.hue.internal.dto.clip2.enums.EffectType;
57 import org.openhab.binding.hue.internal.dto.clip2.enums.ResourceType;
58 import org.openhab.binding.hue.internal.dto.clip2.enums.RotationEventType;
59 import org.openhab.binding.hue.internal.dto.clip2.enums.ZigbeeStatus;
60 import org.openhab.binding.hue.internal.dto.clip2.helper.Setters;
61 import org.openhab.binding.hue.internal.serialization.InstantDeserializer;
62 import org.openhab.core.library.types.DateTimeType;
63 import org.openhab.core.library.types.DecimalType;
64 import org.openhab.core.library.types.HSBType;
65 import org.openhab.core.library.types.OnOffType;
66 import org.openhab.core.library.types.PercentType;
67 import org.openhab.core.library.types.QuantityType;
68 import org.openhab.core.library.types.StringType;
69 import org.openhab.core.types.State;
70 import org.openhab.core.types.UnDefType;
71 import org.openhab.core.util.ColorUtil;
73 import com.google.gson.Gson;
74 import com.google.gson.GsonBuilder;
75 import com.google.gson.JsonElement;
76 import com.google.gson.JsonObject;
77 import com.google.gson.JsonParser;
78 import com.google.gson.JsonSyntaxException;
81 * JUnit test for CLIP 2 DTOs.
83 * @author Andrew Fiddian-Green - Initial contribution
88 private static final Gson GSON = new GsonBuilder().registerTypeAdapter(Instant.class, new InstantDeserializer())
90 private static final Double MINIMUM_DIMMING_LEVEL = Double.valueOf(12.34f);
93 * Load the test JSON payload string from a file
95 private String load(String fileName) {
96 try (FileReader file = new FileReader(String.format("src/test/resources/%s.json", fileName));
97 BufferedReader reader = new BufferedReader(file)) {
98 StringBuilder builder = new StringBuilder();
100 while ((line = reader.readLine()) != null) {
101 builder.append(line).append("\n");
103 return builder.toString();
104 } catch (IOException e) {
105 fail(e.getMessage());
112 String json = load(ResourceType.BUTTON.name().toLowerCase());
113 Resources resources = GSON.fromJson(json, Resources.class);
114 assertNotNull(resources);
115 List<Resource> list = resources.getResources();
117 assertEquals(1, list.size());
118 Resource item = list.get(0);
119 assertEquals(ResourceType.BUTTON, item.getType());
120 Button button = item.getButton();
121 assertNotNull(button);
122 assertEquals(new DecimalType(2003),
123 item.getButtonEventState(Map.of("00000000-0000-0000-0000-000000000001", 2)));
124 assertEquals(new DateTimeType("2023-09-17T18:51:36.959+0000"),
125 item.getButtonLastUpdatedState(ZoneId.of("UTC")));
129 void testButtonDeprecated() {
130 String json = load(ResourceType.BUTTON.name().toLowerCase() + "_deprecated");
131 Resources resources = GSON.fromJson(json, Resources.class);
132 assertNotNull(resources);
133 List<Resource> list = resources.getResources();
135 assertEquals(43, list.size());
136 Resource item = list.get(0);
137 assertEquals(ResourceType.BUTTON, item.getType());
138 Button button = item.getButton();
139 assertNotNull(button);
140 assertEquals(ButtonEventType.SHORT_RELEASE, button.getLastEvent());
145 String json = load(ResourceType.DEVICE.name().toLowerCase());
146 Resources resources = GSON.fromJson(json, Resources.class);
147 assertNotNull(resources);
148 List<Resource> list = resources.getResources();
150 assertEquals(34, list.size());
151 boolean itemFound = false;
152 for (Resource item : list) {
153 assertEquals(ResourceType.DEVICE, item.getType());
154 ProductData productData = item.getProductData();
155 assertNotNull(productData);
156 if (productData.getProductArchetype() == Archetype.BRIDGE_V2) {
158 assertEquals("BSB002", productData.getModelId());
159 assertEquals("Signify Netherlands B.V.", productData.getManufacturerName());
160 assertEquals("Philips hue", productData.getProductName());
161 assertNull(productData.getHardwarePlatformType());
162 assertTrue(productData.getCertified());
163 assertEquals("1.53.1953188020", productData.getSoftwareVersion());
167 assertTrue(itemFound);
171 void testDevicePower() {
172 String json = load(ResourceType.DEVICE_POWER.name().toLowerCase());
173 Resources resources = GSON.fromJson(json, Resources.class);
174 assertNotNull(resources);
175 List<Resource> list = resources.getResources();
177 assertEquals(16, list.size());
178 Resource item = list.get(0);
179 assertEquals(ResourceType.DEVICE_POWER, item.getType());
180 Power power = item.getPowerState();
181 assertNotNull(power);
182 assertEquals(60, power.getBatteryLevel());
183 assertEquals(BatteryStateType.NORMAL, power.getBatteryState());
187 void testGroupedLight() {
188 String json = load(ResourceType.GROUPED_LIGHT.name().toLowerCase());
189 Resources resources = GSON.fromJson(json, Resources.class);
190 assertNotNull(resources);
191 List<Resource> list = resources.getResources();
193 assertEquals(15, list.size());
195 for (Resource item : list) {
196 assertEquals(ResourceType.GROUPED_LIGHT, item.getType());
198 switch (item.getId()) {
199 case "db4fd630-3798-40de-b642-c1ef464bf770":
201 assertEquals(OnOffType.OFF, item.getOnOffState());
202 assertEquals(PercentType.ZERO, item.getBrightnessState());
203 alert = item.getAlerts();
204 assertNotNull(alert);
205 for (ActionType actionValue : alert.getActionValues()) {
206 assertEquals(ActionType.BREATHE, actionValue);
209 case "9228d710-3c54-4ae4-8c88-bfe57d8fd220":
211 assertEquals(OnOffType.ON, item.getOnOffState());
212 assertEquals(PercentType.HUNDRED, item.getBrightnessState());
213 alert = item.getAlerts();
214 assertNotNull(alert);
215 for (ActionType actionValue : alert.getActionValues()) {
216 assertEquals(ActionType.BREATHE, actionValue);
222 assertEquals(2, itemsFound);
227 String json = load(ResourceType.LIGHT.name().toLowerCase());
228 Resources resources = GSON.fromJson(json, Resources.class);
229 assertNotNull(resources);
230 List<Resource> list = resources.getResources();
232 assertEquals(17, list.size());
233 int itemFoundCount = 0;
234 for (Resource item : list) {
235 assertEquals(ResourceType.LIGHT, item.getType());
236 MetaData metaData = item.getMetaData();
237 assertNotNull(metaData);
238 String name = metaData.getName();
241 if (name.contains("Bay Window Lamp")) {
243 assertEquals(ResourceType.LIGHT, item.getType());
244 assertEquals(OnOffType.OFF, item.getOnOffState());
245 state = item.getBrightnessState();
246 assertTrue(state instanceof PercentType);
247 assertEquals(0, ((PercentType) state).doubleValue(), 0.1);
248 item.setOnOff(OnOffType.ON);
249 state = item.getBrightnessState();
250 assertTrue(state instanceof PercentType);
251 assertEquals(93.0, ((PercentType) state).doubleValue(), 0.1);
252 assertEquals(UnDefType.UNDEF, item.getColorTemperaturePercentState());
253 state = item.getColorState();
254 assertTrue(state instanceof HSBType);
255 double[] xy = ColorUtil.hsbToXY((HSBType) state);
256 assertEquals(0.6367, xy[0], 0.01); // note: rounding errors !!
257 assertEquals(0.3503, xy[1], 0.01); // note: rounding errors !!
258 assertEquals(item.getBrightnessState(), ((HSBType) state).getBrightness());
259 Alerts alert = item.getAlerts();
260 assertNotNull(alert);
261 for (ActionType actionValue : alert.getActionValues()) {
262 assertEquals(ActionType.BREATHE, actionValue);
265 if (name.contains("Table Lamp A")) {
267 assertEquals(ResourceType.LIGHT, item.getType());
268 assertEquals(OnOffType.OFF, item.getOnOffState());
269 state = item.getBrightnessState();
270 assertTrue(state instanceof PercentType);
271 assertEquals(0, ((PercentType) state).doubleValue(), 0.1);
272 item.setOnOff(OnOffType.ON);
273 state = item.getBrightnessState();
274 assertTrue(state instanceof PercentType);
275 assertEquals(56.7, ((PercentType) state).doubleValue(), 0.1);
276 MirekSchema mirekSchema = item.getMirekSchema();
277 assertNotNull(mirekSchema);
278 assertEquals(153, mirekSchema.getMirekMinimum());
279 assertEquals(454, mirekSchema.getMirekMaximum());
281 // test color temperature percent value on light's own scale
282 state = item.getColorTemperaturePercentState();
283 assertTrue(state instanceof PercentType);
284 assertEquals(96.3, ((PercentType) state).doubleValue(), 0.1);
285 state = item.getColorTemperatureAbsoluteState();
286 assertTrue(state instanceof QuantityType<?>);
287 assertEquals(2257.3, ((QuantityType<?>) state).doubleValue(), 0.1);
289 // test color temperature percent value on the default (full) scale
290 MirekSchema temp = item.getMirekSchema();
291 item.setMirekSchema(MirekSchema.DEFAULT_SCHEMA);
292 state = item.getColorTemperaturePercentState();
293 assertTrue(state instanceof PercentType);
294 assertEquals(83.6, ((PercentType) state).doubleValue(), 0.1);
295 state = item.getColorTemperatureAbsoluteState();
296 assertTrue(state instanceof QuantityType<?>);
297 assertEquals(2257.3, ((QuantityType<?>) state).doubleValue(), 0.1);
298 item.setMirekSchema(temp);
300 // change colour temperature percent to zero
301 Setters.setColorTemperaturePercent(item, PercentType.ZERO, null);
302 assertEquals(PercentType.ZERO, item.getColorTemperaturePercentState());
303 state = item.getColorTemperatureAbsoluteState();
304 assertTrue(state instanceof QuantityType<?>);
305 assertEquals(6535.9, ((QuantityType<?>) state).doubleValue(), 0.1);
307 // change colour temperature percent to 100
308 Setters.setColorTemperaturePercent(item, PercentType.HUNDRED, null);
309 assertEquals(PercentType.HUNDRED, item.getColorTemperaturePercentState());
310 state = item.getColorTemperatureAbsoluteState();
311 assertTrue(state instanceof QuantityType<?>);
312 assertEquals(2202.6, ((QuantityType<?>) state).doubleValue(), 0.1);
314 // change colour temperature kelvin to 4000 K
315 Setters.setColorTemperatureAbsolute(item, QuantityType.valueOf("4000 K"), null);
316 state = item.getColorTemperaturePercentState();
317 assertTrue(state instanceof PercentType);
318 assertEquals(32.2, ((PercentType) state).doubleValue(), 0.1);
319 assertEquals(QuantityType.valueOf("4000 K"), item.getColorTemperatureAbsoluteState());
321 assertEquals(UnDefType.NULL, item.getColorState());
322 Alerts alert = item.getAlerts();
323 assertNotNull(alert);
324 for (ActionType actionValue : alert.getActionValues()) {
325 assertEquals(ActionType.BREATHE, actionValue);
329 assertEquals(2, itemFoundCount);
333 void testLightLevel() {
334 String json = load(ResourceType.LIGHT_LEVEL.name().toLowerCase());
335 Resources resources = GSON.fromJson(json, Resources.class);
336 assertNotNull(resources);
337 List<Resource> list = resources.getResources();
339 assertEquals(1, list.size());
340 Resource item = list.get(0);
341 assertEquals(ResourceType.LIGHT_LEVEL, item.getType());
342 Boolean enabled = item.getEnabled();
343 assertNotNull(enabled);
345 assertEquals(QuantityType.valueOf("1.2792921774337476 lx"), item.getLightLevelState());
346 assertEquals(new DateTimeType("2023-09-11T19:20:02.958+0000"),
347 item.getLightLevelLastUpdatedState(ZoneId.of("UTC")));
351 void testLightLevelDeprecated() {
352 String json = load(ResourceType.LIGHT_LEVEL.name().toLowerCase() + "_deprecated");
353 Resources resources = GSON.fromJson(json, Resources.class);
354 assertNotNull(resources);
355 List<Resource> list = resources.getResources();
357 assertEquals(1, list.size());
358 Resource item = list.get(0);
359 assertEquals(ResourceType.LIGHT_LEVEL, item.getType());
360 Boolean enabled = item.getEnabled();
361 assertNotNull(enabled);
363 LightLevel lightLevel = item.getLightLevel();
364 assertNotNull(lightLevel);
365 assertEquals(12725, lightLevel.getLightLevel());
366 assertTrue(lightLevel.isLightLevelValid());
370 void testRelativeRotaryDeprecated() {
371 String json = load(ResourceType.RELATIVE_ROTARY.name().toLowerCase() + "_deprecated");
372 Resources resources = GSON.fromJson(json, Resources.class);
373 assertNotNull(resources);
374 List<Resource> list = resources.getResources();
376 assertEquals(1, list.size());
377 Resource item = list.get(0);
378 assertEquals(ResourceType.RELATIVE_ROTARY, item.getType());
379 RelativeRotary relativeRotary = item.getRelativeRotary();
380 assertNotNull(relativeRotary);
381 RotationEvent rotationEvent = relativeRotary.getLastEvent();
382 assertNotNull(rotationEvent);
383 assertEquals(RotationEventType.REPEAT, rotationEvent.getAction());
384 Rotation rotation = rotationEvent.getRotation();
385 assertNotNull(rotation);
386 assertEquals(DirectionType.CLOCK_WISE, rotation.getDirection());
387 assertEquals(400, rotation.getDuration());
388 assertEquals(30, rotation.getSteps());
389 assertEquals(new DecimalType(30), relativeRotary.getStepsState());
390 assertEquals(new StringType(ButtonEventType.REPEAT.name()), relativeRotary.getActionState());
394 void testResourceMerging() {
395 // create resource one
396 Resource one = new Resource(ResourceType.LIGHT).setId("AARDVARK");
398 // preset the minimum dimming level
400 Dimming dimming = new Dimming().setMinimumDimmingLevel(MINIMUM_DIMMING_LEVEL);
401 Field dimming2 = one.getClass().getDeclaredField("dimming");
402 dimming2.setAccessible(true);
403 dimming2.set(one, dimming);
404 } catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) {
407 Setters.setColorXy(one, HSBType.RED, null);
408 Setters.setDimming(one, PercentType.HUNDRED, null);
409 assertTrue(one.getColorState() instanceof HSBType);
410 assertEquals(PercentType.HUNDRED, one.getBrightnessState());
411 assertTrue(HSBType.RED.closeTo((HSBType) one.getColorState(), 0.01));
413 // switching off should change HSB and Brightness
414 one.setOnOff(OnOffType.OFF);
415 assertEquals(0, ((HSBType) one.getColorState()).getBrightness().doubleValue(), 0.01);
416 assertEquals(PercentType.ZERO, one.getBrightnessState());
417 one.setOnOff(OnOffType.ON);
419 // setting brightness to zero should change it to the minimum dimming level
420 Setters.setDimming(one, PercentType.ZERO, null);
421 assertEquals(MINIMUM_DIMMING_LEVEL, ((HSBType) one.getColorState()).getBrightness().doubleValue(), 0.01);
422 assertEquals(MINIMUM_DIMMING_LEVEL, ((PercentType) one.getBrightnessState()).doubleValue(), 0.01);
423 one.setOnOff(OnOffType.ON);
425 // null its Dimming field
427 Field dimming = one.getClass().getDeclaredField("dimming");
428 dimming.setAccessible(true);
429 dimming.set(one, null);
430 } catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) {
434 // confirm that brightness is no longer valid, and therefore that color has also changed
435 assertEquals(UnDefType.NULL, one.getBrightnessState());
436 assertTrue(one.getColorState() instanceof HSBType);
437 assertTrue((new HSBType(DecimalType.ZERO, PercentType.HUNDRED, new PercentType(50)))
438 .closeTo((HSBType) one.getColorState(), 0.01));
440 PercentType testBrightness = new PercentType(42);
442 // create resource two
443 Resource two = new Resource(ResourceType.DEVICE).setId("ALLIGATOR");
445 Setters.setDimming(two, testBrightness, null);
446 assertEquals(UnDefType.NULL, two.getColorState());
447 assertEquals(testBrightness, two.getBrightnessState());
450 Setters.setResource(one, two);
452 // confirm that brightness and color are both once more valid
453 assertEquals("AARDVARK", one.getId());
454 assertEquals(ResourceType.LIGHT, one.getType());
455 assertEquals(testBrightness, one.getBrightnessState());
456 assertTrue(one.getColorState() instanceof HSBType);
457 assertTrue((new HSBType(DecimalType.ZERO, PercentType.HUNDRED, testBrightness))
458 .closeTo((HSBType) one.getColorState(), 0.01));
462 void testRoomGroup() {
463 String json = load(ResourceType.ROOM.name().toLowerCase());
464 Resources resources = GSON.fromJson(json, Resources.class);
465 assertNotNull(resources);
466 List<Resource> list = resources.getResources();
468 assertEquals(6, list.size());
469 Resource item = list.get(0);
470 assertEquals(ResourceType.ROOM, item.getType());
471 List<ResourceReference> children = item.getChildren();
472 assertEquals(2, children.size());
473 ResourceReference child = children.get(0);
474 assertNotNull(child);
475 assertEquals("0d47bd3d-d82b-4a21-893c-299bff18e22a", child.getId());
476 assertEquals(ResourceType.DEVICE, child.getType());
477 List<ResourceReference> services = item.getServiceReferences();
478 assertEquals(1, services.size());
479 ResourceReference service = services.get(0);
480 assertNotNull(service);
481 assertEquals("08947162-67be-4ed5-bfce-f42dade42416", service.getId());
482 assertEquals(ResourceType.GROUPED_LIGHT, service.getType());
487 String json = load(ResourceType.SCENE.name().toLowerCase());
488 Resources resources = GSON.fromJson(json, Resources.class);
489 assertNotNull(resources);
490 List<Resource> list = resources.getResources();
492 assertEquals(123, list.size());
493 Resource item = list.get(0);
494 List<ActionEntry> actions = item.getActions();
495 assertNotNull(actions);
496 assertEquals(3, actions.size());
497 ActionEntry actionEntry = actions.get(0);
498 assertNotNull(actionEntry);
499 Resource action = actionEntry.getAction();
500 assertNotNull(action);
501 assertEquals(OnOffType.ON, action.getOnOffState());
505 void testSmartScene() {
506 String json = load(ResourceType.SMART_SCENE.name().toLowerCase());
507 Resources resources = GSON.fromJson(json, Resources.class);
508 assertNotNull(resources);
509 List<Resource> list = resources.getResources();
511 assertEquals(1, list.size());
512 Resource item = list.get(0);
513 ResourceReference group = item.getGroup();
514 assertNotNull(group);
515 String groupId = group.getId();
516 assertNotNull(groupId);
517 assertFalse(groupId.isBlank());
518 ResourceType type = group.getType();
520 assertEquals(ResourceType.ROOM, type);
521 Optional<Boolean> state = item.getSmartSceneActive();
522 assertTrue(state.isPresent());
523 assertFalse(state.get());
527 void testSensor2Motion() {
528 String json = load(ResourceType.MOTION.name().toLowerCase());
529 Resources resources = GSON.fromJson(json, Resources.class);
530 assertNotNull(resources);
531 List<Resource> list = resources.getResources();
533 assertEquals(1, list.size());
534 Resource item = list.get(0);
535 assertEquals(ResourceType.MOTION, item.getType());
536 Boolean enabled = item.getEnabled();
537 assertNotNull(enabled);
539 assertEquals(OnOffType.ON, item.getMotionState());
540 assertEquals(new DateTimeType("2023-09-04T20:04:30.395+0000"),
541 item.getMotionLastUpdatedState(ZoneId.of("UTC")));
545 void testSensor2MotionDeprecated() {
546 String json = load(ResourceType.MOTION.name().toLowerCase() + "_deprecated");
547 Resources resources = GSON.fromJson(json, Resources.class);
548 assertNotNull(resources);
549 List<Resource> list = resources.getResources();
551 assertEquals(1, list.size());
552 Resource item = list.get(0);
553 assertEquals(ResourceType.MOTION, item.getType());
554 Boolean enabled = item.getEnabled();
555 assertNotNull(enabled);
557 Motion motion = item.getMotion();
558 assertNotNull(motion);
559 assertTrue(motion.isMotion());
560 assertTrue(motion.isMotionValid());
564 void testSetGetPureColors() {
565 Resource resource = new Resource(ResourceType.LIGHT);
566 assertNotNull(resource);
568 HSBType cyan = new HSBType("180,100,100");
569 HSBType yellow = new HSBType("60,100,100");
570 HSBType magenta = new HSBType("300,100,100");
572 for (HSBType color : Set.of(HSBType.WHITE, HSBType.RED, HSBType.GREEN, HSBType.BLUE, cyan, yellow, magenta)) {
573 Setters.setColorXy(resource, color, null);
574 State state = resource.getColorState();
575 assertTrue(state instanceof HSBType);
576 assertTrue(color.closeTo((HSBType) state, 0.01));
581 void testSseLightOrGroupEvent() {
582 String json = load("event");
583 List<Event> eventList = GSON.fromJson(json, Event.EVENT_LIST_TYPE);
584 assertNotNull(eventList);
585 assertEquals(3, eventList.size());
586 Event event = eventList.get(0);
587 List<Resource> resources = event.getData();
588 assertEquals(9, resources.size());
589 for (Resource r : resources) {
590 ResourceType type = r.getType();
591 assertTrue(ResourceType.LIGHT == type || ResourceType.GROUPED_LIGHT == type);
596 void testSseSceneEvent() {
597 String json = load("event");
598 List<Event> eventList = GSON.fromJson(json, Event.EVENT_LIST_TYPE);
599 assertNotNull(eventList);
600 assertEquals(3, eventList.size());
601 Event event = eventList.get(2);
602 List<Resource> resources = event.getData();
603 assertEquals(6, resources.size());
604 Resource resource = resources.get(1);
605 assertEquals(ResourceType.SCENE, resource.getType());
606 JsonObject status = resource.getStatus();
607 assertNotNull(status);
608 JsonElement active = status.get("active");
609 assertNotNull(active);
610 assertTrue(active.isJsonPrimitive());
611 assertEquals("inactive", active.getAsString());
612 Optional<Boolean> isActive = resource.getSceneActive();
613 assertTrue(isActive.isPresent());
614 assertEquals(Boolean.FALSE, isActive.get());
618 void testTemperature() {
619 String json = load(ResourceType.TEMPERATURE.name().toLowerCase());
620 Resources resources = GSON.fromJson(json, Resources.class);
621 assertNotNull(resources);
622 List<Resource> list = resources.getResources();
624 assertEquals(1, list.size());
625 Resource item = list.get(0);
626 assertEquals(ResourceType.TEMPERATURE, item.getType());
627 Boolean enabled = item.getEnabled();
628 assertNotNull(enabled);
630 assertEquals(QuantityType.valueOf("23.34 °C"), item.getTemperatureState());
631 assertEquals(new DateTimeType("2023-09-06T18:22:07.016+0000"),
632 item.getTemperatureLastUpdatedState(ZoneId.of("UTC")));
636 void testTemperatureDeprecated() {
637 String json = load(ResourceType.TEMPERATURE.name().toLowerCase() + "_deprecated");
638 Resources resources = GSON.fromJson(json, Resources.class);
639 assertNotNull(resources);
640 List<Resource> list = resources.getResources();
642 assertEquals(1, list.size());
643 Resource item = list.get(0);
644 assertEquals(ResourceType.TEMPERATURE, item.getType());
645 Boolean enabled = item.getEnabled();
646 assertNotNull(enabled);
648 Temperature temperature = item.getTemperature();
649 assertNotNull(temperature);
650 assertEquals(17.2, temperature.getTemperature(), 0.1);
651 assertTrue(temperature.isTemperatureValid());
655 void testValidJson() {
656 for (ResourceType res : ResourceType.values()) {
657 if (!ResourceType.SSE_TYPES.contains(res)) {
659 String file = res.name().toLowerCase();
660 String json = load(file);
661 JsonElement jsonElement = JsonParser.parseString(json);
662 assertTrue(jsonElement.isJsonObject());
663 } catch (JsonSyntaxException e) {
671 void testZigbeeStatus() {
672 String json = load(ResourceType.ZIGBEE_CONNECTIVITY.name().toLowerCase());
673 Resources resources = GSON.fromJson(json, Resources.class);
674 assertNotNull(resources);
675 List<Resource> list = resources.getResources();
677 assertEquals(35, list.size());
678 Resource item = list.get(0);
679 assertEquals(ResourceType.ZIGBEE_CONNECTIVITY, item.getType());
680 ZigbeeStatus zigbeeStatus = item.getZigbeeStatus();
681 assertNotNull(zigbeeStatus);
682 assertEquals("Connected", zigbeeStatus.toString());
686 void testZoneGroup() {
687 String json = load(ResourceType.ZONE.name().toLowerCase());
688 Resources resources = GSON.fromJson(json, Resources.class);
689 assertNotNull(resources);
690 List<Resource> list = resources.getResources();
692 assertEquals(7, list.size());
693 Resource item = list.get(0);
694 assertEquals(ResourceType.ZONE, item.getType());
695 List<ResourceReference> children = item.getChildren();
696 assertEquals(1, children.size());
697 ResourceReference child = children.get(0);
698 assertNotNull(child);
699 assertEquals("bcad47a0-3f1f-498c-a8aa-3cf389965219", child.getId());
700 assertEquals(ResourceType.LIGHT, child.getType());
701 List<ResourceReference> services = item.getServiceReferences();
702 assertEquals(1, services.size());
703 ResourceReference service = services.get(0);
704 assertNotNull(service);
705 assertEquals("db4fd630-3798-40de-b642-c1ef464bf770", service.getId());
706 assertEquals(ResourceType.GROUPED_LIGHT, service.getType());
710 void testFixedEffectSetter() {
713 Effects resultEffect;
716 source = new Resource(ResourceType.LIGHT);
717 target = new Resource(ResourceType.LIGHT);
718 Setters.setResource(target, source);
719 assertNull(target.getFixedEffects());
721 // valid source fixed effects
722 source = new Resource(ResourceType.LIGHT).setFixedEffects(
723 new Effects().setStatusValues(List.of("NO_EFFECT", "SPARKLE", "CANDLE")).setEffect(EffectType.SPARKLE));
724 target = new Resource(ResourceType.LIGHT);
725 Setters.setResource(target, source);
726 resultEffect = target.getFixedEffects();
727 assertNotNull(resultEffect);
728 assertEquals(EffectType.SPARKLE, resultEffect.getEffect());
729 assertEquals(3, resultEffect.getStatusValues().size());
731 // valid but different source and target fixed effects
732 source = new Resource(ResourceType.LIGHT).setFixedEffects(
733 new Effects().setStatusValues(List.of("NO_EFFECT", "SPARKLE", "CANDLE")).setEffect(EffectType.SPARKLE));
734 target = new Resource(ResourceType.LIGHT).setFixedEffects(
735 new Effects().setStatusValues(List.of("NO_EFFECT", "FIRE")).setEffect(EffectType.FIRE));
736 Setters.setResource(target, source);
737 resultEffect = target.getFixedEffects();
738 assertNotNull(resultEffect);
739 assertNotEquals(EffectType.SPARKLE, resultEffect.getEffect());
740 assertEquals(3, resultEffect.getStatusValues().size());
742 // partly valid source fixed effects
743 source = new Resource(ResourceType.LIGHT).setFixedEffects(new Effects().setStatusValues(List.of("SPARKLE"))
744 .setEffect(EffectType.SPARKLE).setStatusValues(List.of()));
745 target = new Resource(ResourceType.LIGHT);
746 Setters.setResource(target, source);
747 resultEffect = target.getFixedEffects();
748 assertNotNull(resultEffect);
749 assertEquals(EffectType.SPARKLE, resultEffect.getEffect());
750 assertEquals(0, resultEffect.getStatusValues().size());
751 assertFalse(resultEffect.allows(EffectType.SPARKLE));
752 assertFalse(resultEffect.allows(EffectType.NO_EFFECT));
756 void testTimedEffectSetter() {
759 Effects resultEffect;
762 source = new Resource(ResourceType.LIGHT);
763 target = new Resource(ResourceType.LIGHT);
764 Setters.setResource(target, source);
765 assertNull(target.getTimedEffects());
767 // valid source timed effects
768 source = new Resource(ResourceType.LIGHT).setTimedEffects((TimedEffects) new TimedEffects()
769 .setStatusValues(List.of("NO_EFFECT", "SUNRISE")).setEffect(EffectType.NO_EFFECT));
770 target = new Resource(ResourceType.LIGHT);
771 Setters.setResource(target, source);
772 resultEffect = target.getTimedEffects();
773 assertNotNull(resultEffect);
774 assertEquals(EffectType.NO_EFFECT, resultEffect.getEffect());
775 assertEquals(2, resultEffect.getStatusValues().size());
777 // valid but different source and target timed effects
778 source = new Resource(ResourceType.LIGHT)
779 .setTimedEffects((TimedEffects) new TimedEffects().setDuration(Duration.ofMinutes(11))
780 .setStatusValues(List.of("NO_EFFECT", "SPARKLE", "CANDLE")).setEffect(EffectType.SPARKLE));
781 target = new Resource(ResourceType.LIGHT).setTimedEffects((TimedEffects) new TimedEffects()
782 .setStatusValues(List.of("NO_EFFECT", "FIRE")).setEffect(EffectType.FIRE));
783 Setters.setResource(target, source);
784 resultEffect = target.getTimedEffects();
785 assertNotNull(resultEffect);
786 assertNotEquals(EffectType.SPARKLE, resultEffect.getEffect());
787 assertEquals(3, resultEffect.getStatusValues().size());
788 assertTrue(resultEffect instanceof TimedEffects);
789 assertEquals(Duration.ofMinutes(11), ((TimedEffects) resultEffect).getDuration());
791 // partly valid source timed effects
792 source = new Resource(ResourceType.LIGHT).setTimedEffects((TimedEffects) new TimedEffects()
793 .setStatusValues(List.of("SUNRISE")).setEffect(EffectType.SUNRISE).setStatusValues(List.of()));
794 target = new Resource(ResourceType.LIGHT);
795 Setters.setResource(target, source);
796 resultEffect = target.getTimedEffects();
797 assertNotNull(resultEffect);
798 assertEquals(EffectType.SUNRISE, resultEffect.getEffect());
799 assertEquals(0, resultEffect.getStatusValues().size());
800 assertFalse(resultEffect.allows(EffectType.SPARKLE));
801 assertFalse(resultEffect.allows(EffectType.NO_EFFECT));
802 assertTrue(resultEffect instanceof TimedEffects);
803 assertNull(((TimedEffects) resultEffect).getDuration());
805 target.setTimedEffectsDuration(Duration.ofSeconds(22));
806 assertEquals(Duration.ofSeconds(22), ((TimedEffects) resultEffect).getDuration());
808 // source timed effect with duration
809 source = new Resource(ResourceType.LIGHT)
810 .setTimedEffects((TimedEffects) new TimedEffects().setDuration(Duration.ofMillis(44))
811 .setStatusValues(List.of("SUNRISE")).setEffect(EffectType.SUNRISE).setStatusValues(List.of()));
812 target = new Resource(ResourceType.LIGHT);
813 Setters.setResource(target, source);
814 resultEffect = target.getTimedEffects();
815 assertNotNull(resultEffect);
816 assertTrue(resultEffect instanceof TimedEffects);
817 assertEquals(Duration.ofMillis(44), ((TimedEffects) resultEffect).getDuration());