]> git.basschouten.com Git - openhab-addons.git/blob
059a06ba0459a76384c84df47a20fe4d310de3e1
[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.List;
25 import java.util.Map;
26 import java.util.Optional;
27 import java.util.Set;
28
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;
72
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;
79
80 /**
81  * JUnit test for CLIP 2 DTOs.
82  *
83  * @author Andrew Fiddian-Green - Initial contribution
84  */
85 @NonNullByDefault
86 class Clip2DtoTest {
87
88     private static final Gson GSON = new GsonBuilder().registerTypeAdapter(Instant.class, new InstantDeserializer())
89             .create();
90     private static final Double MINIMUM_DIMMING_LEVEL = Double.valueOf(12.34f);
91
92     /**
93      * Load the test JSON payload string from a file
94      */
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();
99             String line;
100             while ((line = reader.readLine()) != null) {
101                 builder.append(line).append("\n");
102             }
103             return builder.toString();
104         } catch (IOException e) {
105             fail(e.getMessage());
106         }
107         return "";
108     }
109
110     @Test
111     void testButton() {
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();
116         assertNotNull(list);
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")));
126     }
127
128     @Test
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();
134         assertNotNull(list);
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());
141     }
142
143     @Test
144     void testDevice() {
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();
149         assertNotNull(list);
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) {
157                 itemFound = true;
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());
164                 break;
165             }
166         }
167         assertTrue(itemFound);
168     }
169
170     @Test
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();
176         assertNotNull(list);
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());
184     }
185
186     @Test
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();
192         assertNotNull(list);
193         assertEquals(15, list.size());
194         int itemsFound = 0;
195         for (Resource item : list) {
196             assertEquals(ResourceType.GROUPED_LIGHT, item.getType());
197             Alerts alert;
198             switch (item.getId()) {
199                 case "db4fd630-3798-40de-b642-c1ef464bf770":
200                     itemsFound++;
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);
207                     }
208                     break;
209                 case "9228d710-3c54-4ae4-8c88-bfe57d8fd220":
210                     itemsFound++;
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);
217                     }
218                     break;
219                 default:
220             }
221         }
222         assertEquals(2, itemsFound);
223     }
224
225     @Test
226     void testLight() {
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();
231         assertNotNull(list);
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();
239             assertNotNull(name);
240             State state;
241             if (name.contains("Bay Window Lamp")) {
242                 itemFoundCount++;
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);
263                 }
264             }
265             if (name.contains("Table Lamp A")) {
266                 itemFoundCount++;
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());
280
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);
288
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);
299
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);
306
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);
313
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());
320
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);
326                 }
327             }
328         }
329         assertEquals(2, itemFoundCount);
330     }
331
332     @Test
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();
338         assertNotNull(list);
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);
344         assertTrue(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")));
348     }
349
350     @Test
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();
356         assertNotNull(list);
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);
362         assertTrue(enabled);
363         LightLevel lightLevel = item.getLightLevel();
364         assertNotNull(lightLevel);
365         assertEquals(12725, lightLevel.getLightLevel());
366         assertTrue(lightLevel.isLightLevelValid());
367     }
368
369     @Test
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();
375         assertNotNull(list);
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());
391     }
392
393     @Test
394     void testResourceMerging() {
395         // create resource one
396         Resource one = new Resource(ResourceType.LIGHT).setId("AARDVARK");
397         assertNotNull(one);
398         // preset the minimum dimming level
399         try {
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) {
405             fail();
406         }
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));
412
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);
418
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);
424
425         // null its Dimming field
426         try {
427             Field dimming = one.getClass().getDeclaredField("dimming");
428             dimming.setAccessible(true);
429             dimming.set(one, null);
430         } catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) {
431             fail();
432         }
433
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));
439
440         PercentType testBrightness = new PercentType(42);
441
442         // create resource two
443         Resource two = new Resource(ResourceType.DEVICE).setId("ALLIGATOR");
444         assertNotNull(two);
445         Setters.setDimming(two, testBrightness, null);
446         assertEquals(UnDefType.NULL, two.getColorState());
447         assertEquals(testBrightness, two.getBrightnessState());
448
449         // merge two => one
450         Setters.setResource(one, two);
451
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));
459     }
460
461     @Test
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();
467         assertNotNull(list);
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());
483     }
484
485     @Test
486     void testScene() {
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();
491         assertNotNull(list);
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());
502     }
503
504     @Test
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();
510         assertNotNull(list);
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();
519         assertNotNull(type);
520         assertEquals(ResourceType.ROOM, type);
521         Optional<Boolean> state = item.getSmartSceneActive();
522         assertTrue(state.isPresent());
523         assertFalse(state.get());
524     }
525
526     @Test
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();
532         assertNotNull(list);
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);
538         assertTrue(enabled);
539         assertEquals(OnOffType.ON, item.getMotionState());
540         assertEquals(new DateTimeType("2023-09-04T20:04:30.395+0000"),
541                 item.getMotionLastUpdatedState(ZoneId.of("UTC")));
542     }
543
544     @Test
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();
550         assertNotNull(list);
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);
556         assertTrue(enabled);
557         Motion motion = item.getMotion();
558         assertNotNull(motion);
559         assertTrue(motion.isMotion());
560         assertTrue(motion.isMotionValid());
561     }
562
563     @Test
564     void testSetGetPureColors() {
565         Resource resource = new Resource(ResourceType.LIGHT);
566         assertNotNull(resource);
567
568         HSBType cyan = new HSBType("180,100,100");
569         HSBType yellow = new HSBType("60,100,100");
570         HSBType magenta = new HSBType("300,100,100");
571
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));
577         }
578     }
579
580     @Test
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);
592         }
593     }
594
595     @Test
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());
615     }
616
617     @Test
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();
623         assertNotNull(list);
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);
629         assertTrue(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")));
633     }
634
635     @Test
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();
641         assertNotNull(list);
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);
647         assertTrue(enabled);
648         Temperature temperature = item.getTemperature();
649         assertNotNull(temperature);
650         assertEquals(17.2, temperature.getTemperature(), 0.1);
651         assertTrue(temperature.isTemperatureValid());
652     }
653
654     @Test
655     void testValidJson() {
656         for (ResourceType res : ResourceType.values()) {
657             if (!ResourceType.SSE_TYPES.contains(res)) {
658                 try {
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) {
664                     fail(res.name());
665                 }
666             }
667         }
668     }
669
670     @Test
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();
676         assertNotNull(list);
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());
683     }
684
685     @Test
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();
691         assertNotNull(list);
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());
707     }
708
709     @Test
710     void testFixedEffectSetter() {
711         Resource source;
712         Resource target;
713         Effects resultEffect;
714
715         // no source effects
716         source = new Resource(ResourceType.LIGHT);
717         target = new Resource(ResourceType.LIGHT);
718         Setters.setResource(target, source);
719         assertNull(target.getFixedEffects());
720
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());
730
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());
741
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));
753     }
754
755     @Test
756     void testTimedEffectSetter() {
757         Resource source;
758         Resource target;
759         Effects resultEffect;
760
761         // no source effects
762         source = new Resource(ResourceType.LIGHT);
763         target = new Resource(ResourceType.LIGHT);
764         Setters.setResource(target, source);
765         assertNull(target.getTimedEffects());
766
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());
776
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());
790
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());
804
805         target.setTimedEffectsDuration(Duration.ofSeconds(22));
806         assertEquals(Duration.ofSeconds(22), ((TimedEffects) resultEffect).getDuration());
807
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());
818     }
819 }