]> git.basschouten.com Git - openhab-addons.git/blob
79b32776d512db13f77000ba01586839614618a6
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
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
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.hue.internal.clip2;
14
15 import static org.junit.jupiter.api.Assertions.*;
16
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;
26 import java.util.Map;
27 import java.util.Optional;
28 import java.util.Set;
29
30 import org.eclipse.jdt.annotation.NonNullByDefault;
31 import org.junit.jupiter.api.Test;
32 import org.openhab.binding.hue.internal.dto.clip2.ActionEntry;
33 import org.openhab.binding.hue.internal.dto.clip2.Alerts;
34 import org.openhab.binding.hue.internal.dto.clip2.Button;
35 import org.openhab.binding.hue.internal.dto.clip2.ContactReport;
36 import org.openhab.binding.hue.internal.dto.clip2.Dimming;
37 import org.openhab.binding.hue.internal.dto.clip2.Effects;
38 import org.openhab.binding.hue.internal.dto.clip2.Event;
39 import org.openhab.binding.hue.internal.dto.clip2.LightLevel;
40 import org.openhab.binding.hue.internal.dto.clip2.MetaData;
41 import org.openhab.binding.hue.internal.dto.clip2.MirekSchema;
42 import org.openhab.binding.hue.internal.dto.clip2.Motion;
43 import org.openhab.binding.hue.internal.dto.clip2.Power;
44 import org.openhab.binding.hue.internal.dto.clip2.ProductData;
45 import org.openhab.binding.hue.internal.dto.clip2.RelativeRotary;
46 import org.openhab.binding.hue.internal.dto.clip2.Resource;
47 import org.openhab.binding.hue.internal.dto.clip2.ResourceReference;
48 import org.openhab.binding.hue.internal.dto.clip2.Resources;
49 import org.openhab.binding.hue.internal.dto.clip2.Rotation;
50 import org.openhab.binding.hue.internal.dto.clip2.RotationEvent;
51 import org.openhab.binding.hue.internal.dto.clip2.TamperReport;
52 import org.openhab.binding.hue.internal.dto.clip2.Temperature;
53 import org.openhab.binding.hue.internal.dto.clip2.TimedEffects;
54 import org.openhab.binding.hue.internal.dto.clip2.enums.ActionType;
55 import org.openhab.binding.hue.internal.dto.clip2.enums.Archetype;
56 import org.openhab.binding.hue.internal.dto.clip2.enums.BatteryStateType;
57 import org.openhab.binding.hue.internal.dto.clip2.enums.ButtonEventType;
58 import org.openhab.binding.hue.internal.dto.clip2.enums.DirectionType;
59 import org.openhab.binding.hue.internal.dto.clip2.enums.EffectType;
60 import org.openhab.binding.hue.internal.dto.clip2.enums.ResourceType;
61 import org.openhab.binding.hue.internal.dto.clip2.enums.RotationEventType;
62 import org.openhab.binding.hue.internal.dto.clip2.enums.ZigbeeStatus;
63 import org.openhab.binding.hue.internal.dto.clip2.helper.Setters;
64 import org.openhab.binding.hue.internal.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;
76
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;
83
84 /**
85  * JUnit test for CLIP 2 DTOs.
86  *
87  * @author Andrew Fiddian-Green - Initial contribution
88  */
89 @NonNullByDefault
90 class Clip2DtoTest {
91
92     private static final Gson GSON = new GsonBuilder().registerTypeAdapter(Instant.class, new InstantDeserializer())
93             .create();
94     private static final Double MINIMUM_DIMMING_LEVEL = Double.valueOf(12.34f);
95
96     /**
97      * Load the test JSON payload string from a file
98      */
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();
103             String line;
104             while ((line = reader.readLine()) != null) {
105                 builder.append(line).append("\n");
106             }
107             return builder.toString();
108         } catch (IOException e) {
109             fail(e.getMessage());
110         }
111         return "";
112     }
113
114     @Test
115     void testButton() {
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();
120         assertNotNull(list);
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")));
130     }
131
132     @Test
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();
138         assertNotNull(list);
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());
145     }
146
147     @Test
148     void testDevice() {
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();
153         assertNotNull(list);
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) {
161                 itemFound = true;
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());
168                 break;
169             }
170         }
171         assertTrue(itemFound);
172     }
173
174     @Test
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();
180         assertNotNull(list);
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());
188     }
189
190     @Test
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();
196         assertNotNull(list);
197         assertEquals(15, list.size());
198         int itemsFound = 0;
199         for (Resource item : list) {
200             assertEquals(ResourceType.GROUPED_LIGHT, item.getType());
201             Alerts alert;
202             switch (item.getId()) {
203                 case "db4fd630-3798-40de-b642-c1ef464bf770":
204                     itemsFound++;
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);
211                     }
212                     break;
213                 case "9228d710-3c54-4ae4-8c88-bfe57d8fd220":
214                     itemsFound++;
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);
221                     }
222                     break;
223                 default:
224             }
225         }
226         assertEquals(2, itemsFound);
227     }
228
229     @Test
230     void testLight() {
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();
235         assertNotNull(list);
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();
243             assertNotNull(name);
244             State state;
245             if (name.contains("Bay Window Lamp")) {
246                 itemFoundCount++;
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);
267                 }
268             }
269             if (name.contains("Table Lamp A")) {
270                 itemFoundCount++;
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());
284
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);
292
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);
303
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);
310
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);
317
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());
324
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);
330                 }
331             }
332         }
333         assertEquals(2, itemFoundCount);
334     }
335
336     @Test
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();
342         assertNotNull(list);
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);
348         assertTrue(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")));
352     }
353
354     @Test
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();
360         assertNotNull(list);
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);
366         assertTrue(enabled);
367         LightLevel lightLevel = item.getLightLevel();
368         assertNotNull(lightLevel);
369         assertEquals(12725, lightLevel.getLightLevel());
370         assertTrue(lightLevel.isLightLevelValid());
371     }
372
373     @Test
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();
379         assertNotNull(list);
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());
395     }
396
397     @Test
398     void testResourceMerging() {
399         // create resource one
400         Resource one = new Resource(ResourceType.LIGHT).setId("AARDVARK");
401         assertNotNull(one);
402         // preset the minimum dimming level
403         try {
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) {
409             fail();
410         }
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));
416
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);
422
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);
428
429         // null its Dimming field
430         try {
431             Field dimming = one.getClass().getDeclaredField("dimming");
432             dimming.setAccessible(true);
433             dimming.set(one, null);
434         } catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) {
435             fail();
436         }
437
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));
443
444         PercentType testBrightness = new PercentType(42);
445
446         // create resource two
447         Resource two = new Resource(ResourceType.DEVICE).setId("ALLIGATOR");
448         assertNotNull(two);
449         Setters.setDimming(two, testBrightness, null);
450         assertEquals(UnDefType.NULL, two.getColorState());
451         assertEquals(testBrightness, two.getBrightnessState());
452
453         // merge two => one
454         Setters.setResource(one, two);
455
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));
463     }
464
465     @Test
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();
471         assertNotNull(list);
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());
487     }
488
489     @Test
490     void testScene() {
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();
495         assertNotNull(list);
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());
506     }
507
508     @Test
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();
514         assertNotNull(list);
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();
523         assertNotNull(type);
524         assertEquals(ResourceType.ROOM, type);
525         Optional<Boolean> state = item.getSmartSceneActive();
526         assertTrue(state.isPresent());
527         assertFalse(state.get());
528     }
529
530     @Test
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();
536         assertNotNull(list);
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);
542         assertTrue(enabled);
543         assertEquals(OnOffType.ON, item.getMotionState());
544         assertEquals(new DateTimeType("2023-09-04T20:04:30.395+0000"),
545                 item.getMotionLastUpdatedState(ZoneId.of("UTC")));
546     }
547
548     @Test
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();
554         assertNotNull(list);
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);
560         assertTrue(enabled);
561         Motion motion = item.getMotion();
562         assertNotNull(motion);
563         assertTrue(motion.isMotion());
564         assertTrue(motion.isMotionValid());
565     }
566
567     @Test
568     void testSetGetPureColors() {
569         Resource resource = new Resource(ResourceType.LIGHT);
570         assertNotNull(resource);
571
572         HSBType cyan = new HSBType("180,100,100");
573         HSBType yellow = new HSBType("60,100,100");
574         HSBType magenta = new HSBType("300,100,100");
575
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));
581         }
582     }
583
584     @Test
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);
596         }
597     }
598
599     @Test
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());
619     }
620
621     @Test
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();
627         assertNotNull(list);
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);
633         assertTrue(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")));
637     }
638
639     @Test
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();
645         assertNotNull(list);
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);
651         assertTrue(enabled);
652         Temperature temperature = item.getTemperature();
653         assertNotNull(temperature);
654         assertEquals(17.2, temperature.getTemperature(), 0.1);
655         assertTrue(temperature.isTemperatureValid());
656     }
657
658     @Test
659     void testValidJson() {
660         for (ResourceType res : ResourceType.values()) {
661             if (!ResourceType.SSE_TYPES.contains(res)) {
662                 try {
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) {
668                     fail(res.name());
669                 }
670             }
671         }
672     }
673
674     @Test
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();
680         assertNotNull(list);
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());
687     }
688
689     @Test
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();
695         assertNotNull(list);
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());
711     }
712
713     @Test
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();
719         assertNotNull(list);
720         assertEquals(1, list.size());
721         Resource resource = list.get(0);
722         assertEquals(ResourceType.CONTACT, resource.getType());
723
724         assertEquals(OpenClosedType.CLOSED, resource.getContactState());
725         assertEquals(new DateTimeType("2023-10-10T19:10:55.919Z"),
726                 resource.getContactLastUpdatedState(ZoneId.of("UTC")));
727
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);
731     }
732
733     @Test
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();
739         assertNotNull(list);
740         assertEquals(1, list.size());
741         Resource resource = list.get(0);
742         assertEquals(ResourceType.TAMPER, resource.getType());
743
744         assertEquals(OpenClosedType.CLOSED, resource.getTamperState());
745         assertEquals(new DateTimeType("2023-01-01T00:00:00.001Z"),
746                 resource.getTamperLastUpdatedState(ZoneId.of("UTC")));
747
748         Instant start = Instant.now();
749         List<TamperReport> tamperReports;
750         State state;
751
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());
759
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());
768
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());
778     }
779
780     @Test
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();
786         assertNotNull(list);
787         assertEquals(1, list.size());
788         Resource resource = list.get(0);
789         assertEquals(ResourceType.CAMERA_MOTION, resource.getType());
790
791         Boolean enabled = resource.getEnabled();
792         assertNotNull(enabled);
793         assertTrue(enabled);
794         assertEquals(OnOffType.ON, resource.getMotionState());
795         assertEquals(new DateTimeType("2020-04-01T20:04:30.395Z"),
796                 resource.getMotionLastUpdatedState(ZoneId.of("UTC")));
797     }
798
799     void testFixedEffectSetter() {
800         Resource source;
801         Resource target;
802         Effects resultEffect;
803
804         // no source effects
805         source = new Resource(ResourceType.LIGHT);
806         target = new Resource(ResourceType.LIGHT);
807         Setters.setResource(target, source);
808         assertNull(target.getFixedEffects());
809
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());
819
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());
830
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));
842     }
843
844     @Test
845     void testTimedEffectSetter() {
846         Resource source;
847         Resource target;
848         Effects resultEffect;
849
850         // no source effects
851         source = new Resource(ResourceType.LIGHT);
852         target = new Resource(ResourceType.LIGHT);
853         Setters.setResource(target, source);
854         assertNull(target.getTimedEffects());
855
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());
865
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());
879
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());
893
894         target.setTimedEffectsDuration(Duration.ofSeconds(22));
895         assertEquals(Duration.ofSeconds(22), ((TimedEffects) resultEffect).getDuration());
896
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());
907     }
908 }