2 * Copyright (c) 2010-2024 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.ArrayList;
25 import java.util.List;
27 import java.util.Optional;
30 import org.eclipse.jdt.annotation.NonNullByDefault;
31 import org.junit.jupiter.api.Test;
32 import org.openhab.binding.hue.internal.api.dto.clip2.ActionEntry;
33 import org.openhab.binding.hue.internal.api.dto.clip2.Alerts;
34 import org.openhab.binding.hue.internal.api.dto.clip2.Button;
35 import org.openhab.binding.hue.internal.api.dto.clip2.ContactReport;
36 import org.openhab.binding.hue.internal.api.dto.clip2.Dimming;
37 import org.openhab.binding.hue.internal.api.dto.clip2.Effects;
38 import org.openhab.binding.hue.internal.api.dto.clip2.Event;
39 import org.openhab.binding.hue.internal.api.dto.clip2.LightLevel;
40 import org.openhab.binding.hue.internal.api.dto.clip2.MetaData;
41 import org.openhab.binding.hue.internal.api.dto.clip2.MirekSchema;
42 import org.openhab.binding.hue.internal.api.dto.clip2.Motion;
43 import org.openhab.binding.hue.internal.api.dto.clip2.Power;
44 import org.openhab.binding.hue.internal.api.dto.clip2.ProductData;
45 import org.openhab.binding.hue.internal.api.dto.clip2.RelativeRotary;
46 import org.openhab.binding.hue.internal.api.dto.clip2.Resource;
47 import org.openhab.binding.hue.internal.api.dto.clip2.ResourceReference;
48 import org.openhab.binding.hue.internal.api.dto.clip2.Resources;
49 import org.openhab.binding.hue.internal.api.dto.clip2.Rotation;
50 import org.openhab.binding.hue.internal.api.dto.clip2.RotationEvent;
51 import org.openhab.binding.hue.internal.api.dto.clip2.TamperReport;
52 import org.openhab.binding.hue.internal.api.dto.clip2.Temperature;
53 import org.openhab.binding.hue.internal.api.dto.clip2.TimedEffects;
54 import org.openhab.binding.hue.internal.api.dto.clip2.enums.ActionType;
55 import org.openhab.binding.hue.internal.api.dto.clip2.enums.Archetype;
56 import org.openhab.binding.hue.internal.api.dto.clip2.enums.BatteryStateType;
57 import org.openhab.binding.hue.internal.api.dto.clip2.enums.ButtonEventType;
58 import org.openhab.binding.hue.internal.api.dto.clip2.enums.DirectionType;
59 import org.openhab.binding.hue.internal.api.dto.clip2.enums.EffectType;
60 import org.openhab.binding.hue.internal.api.dto.clip2.enums.ResourceType;
61 import org.openhab.binding.hue.internal.api.dto.clip2.enums.RotationEventType;
62 import org.openhab.binding.hue.internal.api.dto.clip2.enums.ZigbeeStatus;
63 import org.openhab.binding.hue.internal.api.dto.clip2.helper.Setters;
64 import org.openhab.binding.hue.internal.api.serialization.InstantDeserializer;
65 import org.openhab.core.library.types.DateTimeType;
66 import org.openhab.core.library.types.DecimalType;
67 import org.openhab.core.library.types.HSBType;
68 import org.openhab.core.library.types.OnOffType;
69 import org.openhab.core.library.types.OpenClosedType;
70 import org.openhab.core.library.types.PercentType;
71 import org.openhab.core.library.types.QuantityType;
72 import org.openhab.core.library.types.StringType;
73 import org.openhab.core.types.State;
74 import org.openhab.core.types.UnDefType;
75 import org.openhab.core.util.ColorUtil;
77 import com.google.gson.Gson;
78 import com.google.gson.GsonBuilder;
79 import com.google.gson.JsonElement;
80 import com.google.gson.JsonObject;
81 import com.google.gson.JsonParser;
82 import com.google.gson.JsonSyntaxException;
85 * JUnit test for CLIP 2 DTOs.
87 * @author Andrew Fiddian-Green - Initial contribution
92 private static final Gson GSON = new GsonBuilder().registerTypeAdapter(Instant.class, new InstantDeserializer())
94 private static final Double MINIMUM_DIMMING_LEVEL = Double.valueOf(12.34f);
97 * Load the test JSON payload string from a file
99 private String load(String fileName) {
100 try (FileReader file = new FileReader(String.format("src/test/resources/%s.json", fileName));
101 BufferedReader reader = new BufferedReader(file)) {
102 StringBuilder builder = new StringBuilder();
104 while ((line = reader.readLine()) != null) {
105 builder.append(line).append("\n");
107 return builder.toString();
108 } catch (IOException e) {
109 fail(e.getMessage());
116 String json = load(ResourceType.BUTTON.name().toLowerCase());
117 Resources resources = GSON.fromJson(json, Resources.class);
118 assertNotNull(resources);
119 List<Resource> list = resources.getResources();
121 assertEquals(1, list.size());
122 Resource item = list.get(0);
123 assertEquals(ResourceType.BUTTON, item.getType());
124 Button button = item.getButton();
125 assertNotNull(button);
126 assertEquals(new DecimalType(2003),
127 item.getButtonEventState(Map.of("00000000-0000-0000-0000-000000000001", 2)));
128 assertEquals(new DateTimeType("2023-09-17T18:51:36.959+0000"),
129 item.getButtonLastUpdatedState(ZoneId.of("UTC")));
133 void testButtonDeprecated() {
134 String json = load(ResourceType.BUTTON.name().toLowerCase() + "_deprecated");
135 Resources resources = GSON.fromJson(json, Resources.class);
136 assertNotNull(resources);
137 List<Resource> list = resources.getResources();
139 assertEquals(43, list.size());
140 Resource item = list.get(0);
141 assertEquals(ResourceType.BUTTON, item.getType());
142 Button button = item.getButton();
143 assertNotNull(button);
144 assertEquals(ButtonEventType.SHORT_RELEASE, button.getLastEvent());
149 String json = load(ResourceType.DEVICE.name().toLowerCase());
150 Resources resources = GSON.fromJson(json, Resources.class);
151 assertNotNull(resources);
152 List<Resource> list = resources.getResources();
154 assertEquals(34, list.size());
155 boolean itemFound = false;
156 for (Resource item : list) {
157 assertEquals(ResourceType.DEVICE, item.getType());
158 ProductData productData = item.getProductData();
159 assertNotNull(productData);
160 if (productData.getProductArchetype() == Archetype.BRIDGE_V2) {
162 assertEquals("BSB002", productData.getModelId());
163 assertEquals("Signify Netherlands B.V.", productData.getManufacturerName());
164 assertEquals("Philips hue", productData.getProductName());
165 assertNull(productData.getHardwarePlatformType());
166 assertTrue(productData.getCertified());
167 assertEquals("1.53.1953188020", productData.getSoftwareVersion());
171 assertTrue(itemFound);
175 void testDevicePower() {
176 String json = load(ResourceType.DEVICE_POWER.name().toLowerCase());
177 Resources resources = GSON.fromJson(json, Resources.class);
178 assertNotNull(resources);
179 List<Resource> list = resources.getResources();
181 assertEquals(16, list.size());
182 Resource item = list.get(0);
183 assertEquals(ResourceType.DEVICE_POWER, item.getType());
184 Power power = item.getPowerState();
185 assertNotNull(power);
186 assertEquals(60, power.getBatteryLevel());
187 assertEquals(BatteryStateType.NORMAL, power.getBatteryState());
191 void testGroupedLight() {
192 String json = load(ResourceType.GROUPED_LIGHT.name().toLowerCase());
193 Resources resources = GSON.fromJson(json, Resources.class);
194 assertNotNull(resources);
195 List<Resource> list = resources.getResources();
197 assertEquals(15, list.size());
199 for (Resource item : list) {
200 assertEquals(ResourceType.GROUPED_LIGHT, item.getType());
202 switch (item.getId()) {
203 case "db4fd630-3798-40de-b642-c1ef464bf770":
205 assertEquals(OnOffType.OFF, item.getOnOffState());
206 assertEquals(PercentType.ZERO, item.getBrightnessState());
207 alert = item.getAlerts();
208 assertNotNull(alert);
209 for (ActionType actionValue : alert.getActionValues()) {
210 assertEquals(ActionType.BREATHE, actionValue);
213 case "9228d710-3c54-4ae4-8c88-bfe57d8fd220":
215 assertEquals(OnOffType.ON, item.getOnOffState());
216 assertEquals(PercentType.HUNDRED, item.getBrightnessState());
217 alert = item.getAlerts();
218 assertNotNull(alert);
219 for (ActionType actionValue : alert.getActionValues()) {
220 assertEquals(ActionType.BREATHE, actionValue);
226 assertEquals(2, itemsFound);
231 String json = load(ResourceType.LIGHT.name().toLowerCase());
232 Resources resources = GSON.fromJson(json, Resources.class);
233 assertNotNull(resources);
234 List<Resource> list = resources.getResources();
236 assertEquals(17, list.size());
237 int itemFoundCount = 0;
238 for (Resource item : list) {
239 assertEquals(ResourceType.LIGHT, item.getType());
240 MetaData metaData = item.getMetaData();
241 assertNotNull(metaData);
242 String name = metaData.getName();
245 if (name.contains("Bay Window Lamp")) {
247 assertEquals(ResourceType.LIGHT, item.getType());
248 assertEquals(OnOffType.OFF, item.getOnOffState());
249 state = item.getBrightnessState();
250 assertTrue(state instanceof PercentType);
251 assertEquals(0, ((PercentType) state).doubleValue(), 0.1);
252 item.setOnOff(OnOffType.ON);
253 state = item.getBrightnessState();
254 assertTrue(state instanceof PercentType);
255 assertEquals(93.0, ((PercentType) state).doubleValue(), 0.1);
256 assertEquals(UnDefType.UNDEF, item.getColorTemperaturePercentState());
257 state = item.getColorState();
258 assertTrue(state instanceof HSBType);
259 double[] xy = ColorUtil.hsbToXY((HSBType) state);
260 assertEquals(0.6367, xy[0], 0.01); // note: rounding errors !!
261 assertEquals(0.3503, xy[1], 0.01); // note: rounding errors !!
262 assertEquals(item.getBrightnessState(), ((HSBType) state).getBrightness());
263 Alerts alert = item.getAlerts();
264 assertNotNull(alert);
265 for (ActionType actionValue : alert.getActionValues()) {
266 assertEquals(ActionType.BREATHE, actionValue);
269 if (name.contains("Table Lamp A")) {
271 assertEquals(ResourceType.LIGHT, item.getType());
272 assertEquals(OnOffType.OFF, item.getOnOffState());
273 state = item.getBrightnessState();
274 assertTrue(state instanceof PercentType);
275 assertEquals(0, ((PercentType) state).doubleValue(), 0.1);
276 item.setOnOff(OnOffType.ON);
277 state = item.getBrightnessState();
278 assertTrue(state instanceof PercentType);
279 assertEquals(56.7, ((PercentType) state).doubleValue(), 0.1);
280 MirekSchema mirekSchema = item.getMirekSchema();
281 assertNotNull(mirekSchema);
282 assertEquals(153, mirekSchema.getMirekMinimum());
283 assertEquals(454, mirekSchema.getMirekMaximum());
285 // test color temperature percent value on light's own scale
286 state = item.getColorTemperaturePercentState();
287 assertTrue(state instanceof PercentType);
288 assertEquals(96.3, ((PercentType) state).doubleValue(), 0.1);
289 state = item.getColorTemperatureAbsoluteState();
290 assertTrue(state instanceof QuantityType<?>);
291 assertEquals(2257.3, ((QuantityType<?>) state).doubleValue(), 0.1);
293 // test color temperature percent value on the default (full) scale
294 MirekSchema temp = item.getMirekSchema();
295 item.setMirekSchema(MirekSchema.DEFAULT_SCHEMA);
296 state = item.getColorTemperaturePercentState();
297 assertTrue(state instanceof PercentType);
298 assertEquals(83.6, ((PercentType) state).doubleValue(), 0.1);
299 state = item.getColorTemperatureAbsoluteState();
300 assertTrue(state instanceof QuantityType<?>);
301 assertEquals(2257.3, ((QuantityType<?>) state).doubleValue(), 0.1);
302 item.setMirekSchema(temp);
304 // change colour temperature percent to zero
305 Setters.setColorTemperaturePercent(item, PercentType.ZERO, null);
306 assertEquals(PercentType.ZERO, item.getColorTemperaturePercentState());
307 state = item.getColorTemperatureAbsoluteState();
308 assertTrue(state instanceof QuantityType<?>);
309 assertEquals(6535.9, ((QuantityType<?>) state).doubleValue(), 0.1);
311 // change colour temperature percent to 100
312 Setters.setColorTemperaturePercent(item, PercentType.HUNDRED, null);
313 assertEquals(PercentType.HUNDRED, item.getColorTemperaturePercentState());
314 state = item.getColorTemperatureAbsoluteState();
315 assertTrue(state instanceof QuantityType<?>);
316 assertEquals(2202.6, ((QuantityType<?>) state).doubleValue(), 0.1);
318 // change colour temperature kelvin to 4000 K
319 Setters.setColorTemperatureAbsolute(item, QuantityType.valueOf("4000 K"), null);
320 state = item.getColorTemperaturePercentState();
321 assertTrue(state instanceof PercentType);
322 assertEquals(32.2, ((PercentType) state).doubleValue(), 0.1);
323 assertEquals(QuantityType.valueOf("4000 K"), item.getColorTemperatureAbsoluteState());
325 assertEquals(UnDefType.NULL, item.getColorState());
326 Alerts alert = item.getAlerts();
327 assertNotNull(alert);
328 for (ActionType actionValue : alert.getActionValues()) {
329 assertEquals(ActionType.BREATHE, actionValue);
333 assertEquals(2, itemFoundCount);
337 void testLightLevel() {
338 String json = load(ResourceType.LIGHT_LEVEL.name().toLowerCase());
339 Resources resources = GSON.fromJson(json, Resources.class);
340 assertNotNull(resources);
341 List<Resource> list = resources.getResources();
343 assertEquals(1, list.size());
344 Resource item = list.get(0);
345 assertEquals(ResourceType.LIGHT_LEVEL, item.getType());
346 Boolean enabled = item.getEnabled();
347 assertNotNull(enabled);
349 assertEquals(QuantityType.valueOf("1.2792921774337476 lx"), item.getLightLevelState());
350 assertEquals(new DateTimeType("2023-09-11T19:20:02.958+0000"),
351 item.getLightLevelLastUpdatedState(ZoneId.of("UTC")));
355 void testLightLevelDeprecated() {
356 String json = load(ResourceType.LIGHT_LEVEL.name().toLowerCase() + "_deprecated");
357 Resources resources = GSON.fromJson(json, Resources.class);
358 assertNotNull(resources);
359 List<Resource> list = resources.getResources();
361 assertEquals(1, list.size());
362 Resource item = list.get(0);
363 assertEquals(ResourceType.LIGHT_LEVEL, item.getType());
364 Boolean enabled = item.getEnabled();
365 assertNotNull(enabled);
367 LightLevel lightLevel = item.getLightLevel();
368 assertNotNull(lightLevel);
369 assertEquals(12725, lightLevel.getLightLevel());
370 assertTrue(lightLevel.isLightLevelValid());
374 void testRelativeRotaryDeprecated() {
375 String json = load(ResourceType.RELATIVE_ROTARY.name().toLowerCase() + "_deprecated");
376 Resources resources = GSON.fromJson(json, Resources.class);
377 assertNotNull(resources);
378 List<Resource> list = resources.getResources();
380 assertEquals(1, list.size());
381 Resource item = list.get(0);
382 assertEquals(ResourceType.RELATIVE_ROTARY, item.getType());
383 RelativeRotary relativeRotary = item.getRelativeRotary();
384 assertNotNull(relativeRotary);
385 RotationEvent rotationEvent = relativeRotary.getLastEvent();
386 assertNotNull(rotationEvent);
387 assertEquals(RotationEventType.REPEAT, rotationEvent.getAction());
388 Rotation rotation = rotationEvent.getRotation();
389 assertNotNull(rotation);
390 assertEquals(DirectionType.CLOCK_WISE, rotation.getDirection());
391 assertEquals(400, rotation.getDuration());
392 assertEquals(30, rotation.getSteps());
393 assertEquals(new DecimalType(30), relativeRotary.getStepsState());
394 assertEquals(new StringType(ButtonEventType.REPEAT.name()), relativeRotary.getActionState());
398 void testResourceMerging() {
399 // create resource one
400 Resource one = new Resource(ResourceType.LIGHT).setId("AARDVARK");
402 // preset the minimum dimming level
404 Dimming dimming = new Dimming().setMinimumDimmingLevel(MINIMUM_DIMMING_LEVEL);
405 Field dimming2 = one.getClass().getDeclaredField("dimming");
406 dimming2.setAccessible(true);
407 dimming2.set(one, dimming);
408 } catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) {
411 Setters.setColorXy(one, HSBType.RED, null);
412 Setters.setDimming(one, PercentType.HUNDRED, null);
413 assertTrue(one.getColorState() instanceof HSBType);
414 assertEquals(PercentType.HUNDRED, one.getBrightnessState());
415 assertTrue(HSBType.RED.closeTo((HSBType) one.getColorState(), 0.01));
417 // switching off should change HSB and Brightness
418 one.setOnOff(OnOffType.OFF);
419 assertEquals(0, ((HSBType) one.getColorState()).getBrightness().doubleValue(), 0.01);
420 assertEquals(PercentType.ZERO, one.getBrightnessState());
421 one.setOnOff(OnOffType.ON);
423 // setting brightness to zero should change it to the minimum dimming level
424 Setters.setDimming(one, PercentType.ZERO, null);
425 assertEquals(MINIMUM_DIMMING_LEVEL, ((HSBType) one.getColorState()).getBrightness().doubleValue(), 0.01);
426 assertEquals(MINIMUM_DIMMING_LEVEL, ((PercentType) one.getBrightnessState()).doubleValue(), 0.01);
427 one.setOnOff(OnOffType.ON);
429 // null its Dimming field
431 Field dimming = one.getClass().getDeclaredField("dimming");
432 dimming.setAccessible(true);
433 dimming.set(one, null);
434 } catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) {
438 // confirm that brightness is no longer valid, and therefore that color has also changed
439 assertEquals(UnDefType.NULL, one.getBrightnessState());
440 assertTrue(one.getColorState() instanceof HSBType);
441 assertTrue((new HSBType(DecimalType.ZERO, PercentType.HUNDRED, new PercentType(50)))
442 .closeTo((HSBType) one.getColorState(), 0.01));
444 PercentType testBrightness = new PercentType(42);
446 // create resource two
447 Resource two = new Resource(ResourceType.DEVICE).setId("ALLIGATOR");
449 Setters.setDimming(two, testBrightness, null);
450 assertEquals(UnDefType.NULL, two.getColorState());
451 assertEquals(testBrightness, two.getBrightnessState());
454 Setters.setResource(one, two);
456 // confirm that brightness and color are both once more valid
457 assertEquals("AARDVARK", one.getId());
458 assertEquals(ResourceType.LIGHT, one.getType());
459 assertEquals(testBrightness, one.getBrightnessState());
460 assertTrue(one.getColorState() instanceof HSBType);
461 assertTrue((new HSBType(DecimalType.ZERO, PercentType.HUNDRED, testBrightness))
462 .closeTo((HSBType) one.getColorState(), 0.01));
466 void testRoomGroup() {
467 String json = load(ResourceType.ROOM.name().toLowerCase());
468 Resources resources = GSON.fromJson(json, Resources.class);
469 assertNotNull(resources);
470 List<Resource> list = resources.getResources();
472 assertEquals(6, list.size());
473 Resource item = list.get(0);
474 assertEquals(ResourceType.ROOM, item.getType());
475 List<ResourceReference> children = item.getChildren();
476 assertEquals(2, children.size());
477 ResourceReference child = children.get(0);
478 assertNotNull(child);
479 assertEquals("0d47bd3d-d82b-4a21-893c-299bff18e22a", child.getId());
480 assertEquals(ResourceType.DEVICE, child.getType());
481 List<ResourceReference> services = item.getServiceReferences();
482 assertEquals(1, services.size());
483 ResourceReference service = services.get(0);
484 assertNotNull(service);
485 assertEquals("08947162-67be-4ed5-bfce-f42dade42416", service.getId());
486 assertEquals(ResourceType.GROUPED_LIGHT, service.getType());
491 String json = load(ResourceType.SCENE.name().toLowerCase());
492 Resources resources = GSON.fromJson(json, Resources.class);
493 assertNotNull(resources);
494 List<Resource> list = resources.getResources();
496 assertEquals(123, list.size());
497 Resource item = list.get(0);
498 List<ActionEntry> actions = item.getActions();
499 assertNotNull(actions);
500 assertEquals(3, actions.size());
501 ActionEntry actionEntry = actions.get(0);
502 assertNotNull(actionEntry);
503 Resource action = actionEntry.getAction();
504 assertNotNull(action);
505 assertEquals(OnOffType.ON, action.getOnOffState());
509 void testSmartScene() {
510 String json = load(ResourceType.SMART_SCENE.name().toLowerCase());
511 Resources resources = GSON.fromJson(json, Resources.class);
512 assertNotNull(resources);
513 List<Resource> list = resources.getResources();
515 assertEquals(1, list.size());
516 Resource item = list.get(0);
517 ResourceReference group = item.getGroup();
518 assertNotNull(group);
519 String groupId = group.getId();
520 assertNotNull(groupId);
521 assertFalse(groupId.isBlank());
522 ResourceType type = group.getType();
524 assertEquals(ResourceType.ROOM, type);
525 Optional<Boolean> state = item.getSmartSceneActive();
526 assertTrue(state.isPresent());
527 assertFalse(state.get());
531 void testSensor2Motion() {
532 String json = load(ResourceType.MOTION.name().toLowerCase());
533 Resources resources = GSON.fromJson(json, Resources.class);
534 assertNotNull(resources);
535 List<Resource> list = resources.getResources();
537 assertEquals(1, list.size());
538 Resource item = list.get(0);
539 assertEquals(ResourceType.MOTION, item.getType());
540 Boolean enabled = item.getEnabled();
541 assertNotNull(enabled);
543 assertEquals(OnOffType.ON, item.getMotionState());
544 assertEquals(new DateTimeType("2023-09-04T20:04:30.395+0000"),
545 item.getMotionLastUpdatedState(ZoneId.of("UTC")));
549 void testSensor2MotionDeprecated() {
550 String json = load(ResourceType.MOTION.name().toLowerCase() + "_deprecated");
551 Resources resources = GSON.fromJson(json, Resources.class);
552 assertNotNull(resources);
553 List<Resource> list = resources.getResources();
555 assertEquals(1, list.size());
556 Resource item = list.get(0);
557 assertEquals(ResourceType.MOTION, item.getType());
558 Boolean enabled = item.getEnabled();
559 assertNotNull(enabled);
561 Motion motion = item.getMotion();
562 assertNotNull(motion);
563 assertTrue(motion.isMotion());
564 assertTrue(motion.isMotionValid());
568 void testSetGetPureColors() {
569 Resource resource = new Resource(ResourceType.LIGHT);
570 assertNotNull(resource);
572 HSBType cyan = new HSBType("180,100,100");
573 HSBType yellow = new HSBType("60,100,100");
574 HSBType magenta = new HSBType("300,100,100");
576 for (HSBType color : Set.of(HSBType.WHITE, HSBType.RED, HSBType.GREEN, HSBType.BLUE, cyan, yellow, magenta)) {
577 Setters.setColorXy(resource, color, null);
578 State state = resource.getColorState();
579 assertTrue(state instanceof HSBType);
580 assertTrue(color.closeTo((HSBType) state, 0.01));
585 void testSseLightOrGroupEvent() {
586 String json = load("event");
587 List<Event> eventList = GSON.fromJson(json, Event.EVENT_LIST_TYPE);
588 assertNotNull(eventList);
589 assertEquals(3, eventList.size());
590 Event event = eventList.get(0);
591 List<Resource> resources = event.getData();
592 assertEquals(9, resources.size());
593 for (Resource r : resources) {
594 ResourceType type = r.getType();
595 assertTrue(ResourceType.LIGHT == type || ResourceType.GROUPED_LIGHT == type);
600 void testSseSceneEvent() {
601 String json = load("event");
602 List<Event> eventList = GSON.fromJson(json, Event.EVENT_LIST_TYPE);
603 assertNotNull(eventList);
604 assertEquals(3, eventList.size());
605 Event event = eventList.get(2);
606 List<Resource> resources = event.getData();
607 assertEquals(6, resources.size());
608 Resource resource = resources.get(1);
609 assertEquals(ResourceType.SCENE, resource.getType());
610 JsonObject status = resource.getStatus();
611 assertNotNull(status);
612 JsonElement active = status.get("active");
613 assertNotNull(active);
614 assertTrue(active.isJsonPrimitive());
615 assertEquals("inactive", active.getAsString());
616 Optional<Boolean> isActive = resource.getSceneActive();
617 assertTrue(isActive.isPresent());
618 assertEquals(Boolean.FALSE, isActive.get());
622 void testTemperature() {
623 String json = load(ResourceType.TEMPERATURE.name().toLowerCase());
624 Resources resources = GSON.fromJson(json, Resources.class);
625 assertNotNull(resources);
626 List<Resource> list = resources.getResources();
628 assertEquals(1, list.size());
629 Resource item = list.get(0);
630 assertEquals(ResourceType.TEMPERATURE, item.getType());
631 Boolean enabled = item.getEnabled();
632 assertNotNull(enabled);
634 assertEquals(QuantityType.valueOf("23.34 °C"), item.getTemperatureState());
635 assertEquals(new DateTimeType("2023-09-06T18:22:07.016+0000"),
636 item.getTemperatureLastUpdatedState(ZoneId.of("UTC")));
640 void testTemperatureDeprecated() {
641 String json = load(ResourceType.TEMPERATURE.name().toLowerCase() + "_deprecated");
642 Resources resources = GSON.fromJson(json, Resources.class);
643 assertNotNull(resources);
644 List<Resource> list = resources.getResources();
646 assertEquals(1, list.size());
647 Resource item = list.get(0);
648 assertEquals(ResourceType.TEMPERATURE, item.getType());
649 Boolean enabled = item.getEnabled();
650 assertNotNull(enabled);
652 Temperature temperature = item.getTemperature();
653 assertNotNull(temperature);
654 assertEquals(17.2, temperature.getTemperature(), 0.1);
655 assertTrue(temperature.isTemperatureValid());
659 void testValidJson() {
660 for (ResourceType res : ResourceType.values()) {
661 if (!ResourceType.SSE_TYPES.contains(res)) {
663 String file = res.name().toLowerCase();
664 String json = load(file);
665 JsonElement jsonElement = JsonParser.parseString(json);
666 assertTrue(jsonElement.isJsonObject());
667 } catch (JsonSyntaxException e) {
675 void testZigbeeStatus() {
676 String json = load(ResourceType.ZIGBEE_CONNECTIVITY.name().toLowerCase());
677 Resources resources = GSON.fromJson(json, Resources.class);
678 assertNotNull(resources);
679 List<Resource> list = resources.getResources();
681 assertEquals(35, list.size());
682 Resource item = list.get(0);
683 assertEquals(ResourceType.ZIGBEE_CONNECTIVITY, item.getType());
684 ZigbeeStatus zigbeeStatus = item.getZigbeeStatus();
685 assertNotNull(zigbeeStatus);
686 assertEquals("Connected", zigbeeStatus.toString());
690 void testZoneGroup() {
691 String json = load(ResourceType.ZONE.name().toLowerCase());
692 Resources resources = GSON.fromJson(json, Resources.class);
693 assertNotNull(resources);
694 List<Resource> list = resources.getResources();
696 assertEquals(7, list.size());
697 Resource item = list.get(0);
698 assertEquals(ResourceType.ZONE, item.getType());
699 List<ResourceReference> children = item.getChildren();
700 assertEquals(1, children.size());
701 ResourceReference child = children.get(0);
702 assertNotNull(child);
703 assertEquals("bcad47a0-3f1f-498c-a8aa-3cf389965219", child.getId());
704 assertEquals(ResourceType.LIGHT, child.getType());
705 List<ResourceReference> services = item.getServiceReferences();
706 assertEquals(1, services.size());
707 ResourceReference service = services.get(0);
708 assertNotNull(service);
709 assertEquals("db4fd630-3798-40de-b642-c1ef464bf770", service.getId());
710 assertEquals(ResourceType.GROUPED_LIGHT, service.getType());
714 void testSecurityContact() {
715 String json = load(ResourceType.CONTACT.name().toLowerCase());
716 Resources resources = GSON.fromJson(json, Resources.class);
717 assertNotNull(resources);
718 List<Resource> list = resources.getResources();
720 assertEquals(1, list.size());
721 Resource resource = list.get(0);
722 assertEquals(ResourceType.CONTACT, resource.getType());
724 assertEquals(OpenClosedType.CLOSED, resource.getContactState());
725 assertEquals(new DateTimeType("2023-10-10T19:10:55.919Z"),
726 resource.getContactLastUpdatedState(ZoneId.of("UTC")));
728 resource.setContactReport(new ContactReport().setLastChanged(Instant.now()).setContactState("no_contact"));
729 assertEquals(OpenClosedType.OPEN, resource.getContactState());
730 assertTrue(resource.getContactLastUpdatedState(ZoneId.of("UTC")) instanceof DateTimeType);
734 void testSecurityTamper() {
735 String json = load(ResourceType.TAMPER.name().toLowerCase());
736 Resources resources = GSON.fromJson(json, Resources.class);
737 assertNotNull(resources);
738 List<Resource> list = resources.getResources();
740 assertEquals(1, list.size());
741 Resource resource = list.get(0);
742 assertEquals(ResourceType.TAMPER, resource.getType());
744 assertEquals(OpenClosedType.CLOSED, resource.getTamperState());
745 assertEquals(new DateTimeType("2023-01-01T00:00:00.001Z"),
746 resource.getTamperLastUpdatedState(ZoneId.of("UTC")));
748 Instant start = Instant.now();
749 List<TamperReport> tamperReports;
752 tamperReports = new ArrayList<>();
753 tamperReports.add(new TamperReport().setTamperState("not_tampered").setLastChanged(start));
754 resource.setTamperReports(tamperReports);
755 assertEquals(OpenClosedType.CLOSED, resource.getTamperState());
756 state = resource.getTamperLastUpdatedState(ZoneId.of("UTC"));
757 assertTrue(state instanceof DateTimeType);
758 assertEquals(start, ((DateTimeType) state).getInstant());
760 tamperReports = new ArrayList<>();
761 tamperReports.add(new TamperReport().setTamperState("not_tampered").setLastChanged(start));
762 tamperReports.add(new TamperReport().setTamperState("tampered").setLastChanged(start.plusSeconds(1)));
763 resource.setTamperReports(tamperReports);
764 assertEquals(OpenClosedType.OPEN, resource.getTamperState());
765 state = resource.getTamperLastUpdatedState(ZoneId.of("UTC"));
766 assertTrue(state instanceof DateTimeType);
767 assertEquals(start.plusSeconds(1), ((DateTimeType) state).getInstant());
769 tamperReports = new ArrayList<>();
770 tamperReports.add(new TamperReport().setTamperState("not_tampered").setLastChanged(start));
771 tamperReports.add(new TamperReport().setTamperState("tampered").setLastChanged(start.plusSeconds(1)));
772 tamperReports.add(new TamperReport().setTamperState("not_tampered").setLastChanged(start.plusSeconds(2)));
773 resource.setTamperReports(tamperReports);
774 assertEquals(OpenClosedType.CLOSED, resource.getTamperState());
775 state = resource.getTamperLastUpdatedState(ZoneId.of("UTC"));
776 assertTrue(state instanceof DateTimeType);
777 assertEquals(start.plusSeconds(2), ((DateTimeType) state).getInstant());
781 void testCameraMotion() {
782 String json = load(ResourceType.CAMERA_MOTION.name().toLowerCase());
783 Resources resources = GSON.fromJson(json, Resources.class);
784 assertNotNull(resources);
785 List<Resource> list = resources.getResources();
787 assertEquals(1, list.size());
788 Resource resource = list.get(0);
789 assertEquals(ResourceType.CAMERA_MOTION, resource.getType());
791 Boolean enabled = resource.getEnabled();
792 assertNotNull(enabled);
794 assertEquals(OnOffType.ON, resource.getMotionState());
795 assertEquals(new DateTimeType("2020-04-01T20:04:30.395Z"),
796 resource.getMotionLastUpdatedState(ZoneId.of("UTC")));
799 void testFixedEffectSetter() {
802 Effects resultEffect;
805 source = new Resource(ResourceType.LIGHT);
806 target = new Resource(ResourceType.LIGHT);
807 Setters.setResource(target, source);
808 assertNull(target.getFixedEffects());
810 // valid source fixed effects
811 source = new Resource(ResourceType.LIGHT).setFixedEffects(
812 new Effects().setStatusValues(List.of("NO_EFFECT", "SPARKLE", "CANDLE")).setEffect(EffectType.SPARKLE));
813 target = new Resource(ResourceType.LIGHT);
814 Setters.setResource(target, source);
815 resultEffect = target.getFixedEffects();
816 assertNotNull(resultEffect);
817 assertEquals(EffectType.SPARKLE, resultEffect.getEffect());
818 assertEquals(3, resultEffect.getStatusValues().size());
820 // valid but different source and target fixed effects
821 source = new Resource(ResourceType.LIGHT).setFixedEffects(
822 new Effects().setStatusValues(List.of("NO_EFFECT", "SPARKLE", "CANDLE")).setEffect(EffectType.SPARKLE));
823 target = new Resource(ResourceType.LIGHT).setFixedEffects(
824 new Effects().setStatusValues(List.of("NO_EFFECT", "FIRE")).setEffect(EffectType.FIRE));
825 Setters.setResource(target, source);
826 resultEffect = target.getFixedEffects();
827 assertNotNull(resultEffect);
828 assertNotEquals(EffectType.SPARKLE, resultEffect.getEffect());
829 assertEquals(3, resultEffect.getStatusValues().size());
831 // partly valid source fixed effects
832 source = new Resource(ResourceType.LIGHT).setFixedEffects(new Effects().setStatusValues(List.of("SPARKLE"))
833 .setEffect(EffectType.SPARKLE).setStatusValues(List.of()));
834 target = new Resource(ResourceType.LIGHT);
835 Setters.setResource(target, source);
836 resultEffect = target.getFixedEffects();
837 assertNotNull(resultEffect);
838 assertEquals(EffectType.SPARKLE, resultEffect.getEffect());
839 assertEquals(0, resultEffect.getStatusValues().size());
840 assertFalse(resultEffect.allows(EffectType.SPARKLE));
841 assertFalse(resultEffect.allows(EffectType.NO_EFFECT));
845 void testTimedEffectSetter() {
848 Effects resultEffect;
851 source = new Resource(ResourceType.LIGHT);
852 target = new Resource(ResourceType.LIGHT);
853 Setters.setResource(target, source);
854 assertNull(target.getTimedEffects());
856 // valid source timed effects
857 source = new Resource(ResourceType.LIGHT).setTimedEffects((TimedEffects) new TimedEffects()
858 .setStatusValues(List.of("NO_EFFECT", "SUNRISE")).setEffect(EffectType.NO_EFFECT));
859 target = new Resource(ResourceType.LIGHT);
860 Setters.setResource(target, source);
861 resultEffect = target.getTimedEffects();
862 assertNotNull(resultEffect);
863 assertEquals(EffectType.NO_EFFECT, resultEffect.getEffect());
864 assertEquals(2, resultEffect.getStatusValues().size());
866 // valid but different source and target timed effects
867 source = new Resource(ResourceType.LIGHT)
868 .setTimedEffects((TimedEffects) new TimedEffects().setDuration(Duration.ofMinutes(11))
869 .setStatusValues(List.of("NO_EFFECT", "SPARKLE", "CANDLE")).setEffect(EffectType.SPARKLE));
870 target = new Resource(ResourceType.LIGHT).setTimedEffects((TimedEffects) new TimedEffects()
871 .setStatusValues(List.of("NO_EFFECT", "FIRE")).setEffect(EffectType.FIRE));
872 Setters.setResource(target, source);
873 resultEffect = target.getTimedEffects();
874 assertNotNull(resultEffect);
875 assertNotEquals(EffectType.SPARKLE, resultEffect.getEffect());
876 assertEquals(3, resultEffect.getStatusValues().size());
877 assertTrue(resultEffect instanceof TimedEffects);
878 assertEquals(Duration.ofMinutes(11), ((TimedEffects) resultEffect).getDuration());
880 // partly valid source timed effects
881 source = new Resource(ResourceType.LIGHT).setTimedEffects((TimedEffects) new TimedEffects()
882 .setStatusValues(List.of("SUNRISE")).setEffect(EffectType.SUNRISE).setStatusValues(List.of()));
883 target = new Resource(ResourceType.LIGHT);
884 Setters.setResource(target, source);
885 resultEffect = target.getTimedEffects();
886 assertNotNull(resultEffect);
887 assertEquals(EffectType.SUNRISE, resultEffect.getEffect());
888 assertEquals(0, resultEffect.getStatusValues().size());
889 assertFalse(resultEffect.allows(EffectType.SPARKLE));
890 assertFalse(resultEffect.allows(EffectType.NO_EFFECT));
891 assertTrue(resultEffect instanceof TimedEffects);
892 assertNull(((TimedEffects) resultEffect).getDuration());
894 target.setTimedEffectsDuration(Duration.ofSeconds(22));
895 assertEquals(Duration.ofSeconds(22), ((TimedEffects) resultEffect).getDuration());
897 // source timed effect with duration
898 source = new Resource(ResourceType.LIGHT)
899 .setTimedEffects((TimedEffects) new TimedEffects().setDuration(Duration.ofMillis(44))
900 .setStatusValues(List.of("SUNRISE")).setEffect(EffectType.SUNRISE).setStatusValues(List.of()));
901 target = new Resource(ResourceType.LIGHT);
902 Setters.setResource(target, source);
903 resultEffect = target.getTimedEffects();
904 assertNotNull(resultEffect);
905 assertTrue(resultEffect instanceof TimedEffects);
906 assertEquals(Duration.ofMillis(44), ((TimedEffects) resultEffect).getDuration());