]> git.basschouten.com Git - openhab-addons.git/blob
516b850e4b9403e99fbfc92529dc27d908ef4f44
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2021 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.hdpowerview;
14
15 import static org.junit.jupiter.api.Assertions.*;
16 import static org.openhab.binding.hdpowerview.internal.api.CoordinateSystem.*;
17
18 import java.io.IOException;
19 import java.nio.file.Files;
20 import java.nio.file.Paths;
21 import java.util.List;
22 import java.util.regex.Pattern;
23 import java.util.stream.Collectors;
24
25 import org.eclipse.jdt.annotation.NonNullByDefault;
26 import org.eclipse.jetty.client.HttpClient;
27 import org.junit.jupiter.api.Test;
28 import org.openhab.binding.hdpowerview.internal.HDPowerViewWebTargets;
29 import org.openhab.binding.hdpowerview.internal.HubMaintenanceException;
30 import org.openhab.binding.hdpowerview.internal.HubProcessingException;
31 import org.openhab.binding.hdpowerview.internal.api.ShadePosition;
32 import org.openhab.binding.hdpowerview.internal.api.responses.SceneCollections;
33 import org.openhab.binding.hdpowerview.internal.api.responses.SceneCollections.SceneCollection;
34 import org.openhab.binding.hdpowerview.internal.api.responses.Scenes;
35 import org.openhab.binding.hdpowerview.internal.api.responses.Scenes.Scene;
36 import org.openhab.binding.hdpowerview.internal.api.responses.Shade;
37 import org.openhab.binding.hdpowerview.internal.api.responses.Shades;
38 import org.openhab.binding.hdpowerview.internal.api.responses.Shades.ShadeData;
39 import org.openhab.binding.hdpowerview.internal.database.ShadeCapabilitiesDatabase;
40 import org.openhab.binding.hdpowerview.internal.database.ShadeCapabilitiesDatabase.Capabilities;
41 import org.openhab.core.library.types.PercentType;
42 import org.openhab.core.types.State;
43 import org.openhab.core.types.UnDefType;
44
45 import com.google.gson.Gson;
46 import com.google.gson.JsonParseException;
47
48 /**
49  * Unit tests for HD PowerView binding.
50  *
51  * @author Andrew Fiddian-Green - Initial contribution
52  * @author Jacob Laursen - Add support for scene groups
53  */
54 @NonNullByDefault
55 public class HDPowerViewJUnitTests {
56
57     private static final Pattern VALID_IP_V4_ADDRESS = Pattern
58             .compile("\\b((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\\.|$)){4}\\b");
59
60     private final ShadeCapabilitiesDatabase db = new ShadeCapabilitiesDatabase();
61
62     /*
63      * load a test JSON string from a file.
64      */
65     private String loadJson(String fileName) {
66         try {
67             return Files.readAllLines(Paths.get(String.format("src/test/resources/%s.json", fileName))).stream()
68                     .collect(Collectors.joining());
69         } catch (IOException e) {
70             fail(e.getMessage());
71         }
72         return "";
73     }
74
75     /**
76      * Run a series of ONLINE tests on the communication with a hub.
77      *
78      * @param hubIPAddress must be a valid hub IP address to run the
79      *            tests on; or an INVALID IP address to
80      *            suppress the tests
81      * @param allowShadeMovementCommands set to true if you accept that the tests
82      *            shall physically move the shades
83      */
84     @Test
85     public void testOnlineCommunication() {
86         /*
87          * NOTE: in order to actually run these tests you must have a hub physically
88          * available, and its IP address must be correctly configured in the
89          * "hubIPAddress" string constant e.g. "192.168.1.123"
90          */
91         String hubIPAddress = "192.168.1.xxx";
92
93         /*
94          * NOTE: set allowShadeMovementCommands = true if you accept physically moving
95          * the shades during these tests
96          */
97         boolean allowShadeMovementCommands = false;
98
99         if (VALID_IP_V4_ADDRESS.matcher(hubIPAddress).matches()) {
100             // ==== initialize stuff ====
101             HttpClient client = new HttpClient();
102             assertNotNull(client);
103
104             // ==== start the client ====
105             try {
106                 client.start();
107                 assertTrue(client.isStarted());
108             } catch (Exception e) {
109                 fail(e.getMessage());
110             }
111
112             HDPowerViewWebTargets webTargets = new HDPowerViewWebTargets(client, hubIPAddress);
113             assertNotNull(webTargets);
114
115             int shadeId = 0;
116             ShadePosition shadePos = null;
117             Shades shadesX = null;
118
119             // ==== get all shades ====
120             try {
121                 shadesX = webTargets.getShades();
122                 assertNotNull(shadesX);
123                 if (shadesX != null) {
124                     List<ShadeData> shadesData = shadesX.shadeData;
125                     assertNotNull(shadesData);
126
127                     if (shadesData != null) {
128                         assertTrue(!shadesData.isEmpty());
129                         ShadeData shadeData;
130                         shadeData = shadesData.get(0);
131                         assertNotNull(shadeData);
132                         assertTrue(shadeData.getName().length() > 0);
133                         shadePos = shadeData.positions;
134                         assertNotNull(shadePos);
135                         ShadeData shadeZero = shadesData.get(0);
136                         assertNotNull(shadeZero);
137                         shadeId = shadeZero.id;
138                         assertNotEquals(0, shadeId);
139
140                         for (ShadeData shadexData : shadesData) {
141                             String shadeName = shadexData.getName();
142                             assertNotNull(shadeName);
143                         }
144                     }
145                 }
146             } catch (JsonParseException | HubProcessingException | HubMaintenanceException e) {
147                 fail(e.getMessage());
148             }
149
150             // ==== get all scenes ====
151             int sceneId = 0;
152             try {
153                 Scenes scenes = webTargets.getScenes();
154                 assertNotNull(scenes);
155
156                 if (scenes != null) {
157                     List<Scene> scenesData = scenes.sceneData;
158                     assertNotNull(scenesData);
159
160                     if (scenesData != null) {
161                         assertTrue(!scenesData.isEmpty());
162                         Scene sceneZero = scenesData.get(0);
163                         assertNotNull(sceneZero);
164                         sceneId = sceneZero.id;
165                         assertTrue(sceneId > 0);
166
167                         for (Scene scene : scenesData) {
168                             String sceneName = scene.getName();
169                             assertNotNull(sceneName);
170                         }
171                     }
172                 }
173             } catch (JsonParseException | HubProcessingException | HubMaintenanceException e) {
174                 fail(e.getMessage());
175             }
176
177             // ==== refresh a specific shade ====
178             Shade shade = null;
179             try {
180                 assertNotEquals(0, shadeId);
181                 shade = webTargets.refreshShadePosition(shadeId);
182                 assertNotNull(shade);
183             } catch (HubProcessingException | HubMaintenanceException e) {
184                 fail(e.getMessage());
185             }
186
187             // ==== move a specific shade ====
188             try {
189                 assertNotEquals(0, shadeId);
190                 assertNotNull(shade);
191                 if (shade != null) {
192                     ShadeData shadeData = shade.shade;
193                     assertNotNull(shadeData);
194
195                     if (shadeData != null) {
196                         ShadePosition positions = shadeData.positions;
197                         assertNotNull(positions);
198
199                         if (positions != null) {
200                             Integer capabilitiesValue = shadeData.capabilities;
201                             assertNotNull(capabilitiesValue);
202
203                             if (capabilitiesValue != null) {
204                                 Capabilities capabilities = db.getCapabilities(capabilitiesValue.intValue());
205
206                                 State pos = positions.getState(capabilities, PRIMARY_ZERO_IS_CLOSED);
207                                 assertEquals(PercentType.class, pos.getClass());
208
209                                 int position = ((PercentType) pos).intValue();
210                                 position = position + ((position <= 10) ? 5 : -5);
211
212                                 ShadePosition targetPosition = new ShadePosition().setPosition(capabilities,
213                                         PRIMARY_ZERO_IS_CLOSED, position);
214                                 assertNotNull(targetPosition);
215
216                                 if (allowShadeMovementCommands) {
217                                     webTargets.moveShade(shadeId, targetPosition);
218
219                                     Shade newShade = webTargets.getShade(shadeId);
220                                     assertNotNull(newShade);
221                                     if (newShade != null) {
222                                         ShadeData newData = newShade.shade;
223                                         assertNotNull(newData);
224                                         if (newData != null) {
225                                             ShadePosition actualPosition = newData.positions;
226                                             assertNotNull(actualPosition);
227                                             if (actualPosition != null) {
228                                                 assertEquals(
229                                                         targetPosition.getState(capabilities, PRIMARY_ZERO_IS_CLOSED),
230                                                         actualPosition.getState(capabilities, PRIMARY_ZERO_IS_CLOSED));
231                                             }
232                                         }
233                                     }
234                                 }
235                             }
236                         }
237                     }
238                 }
239             } catch (HubProcessingException | HubMaintenanceException e) {
240                 fail(e.getMessage());
241             }
242
243             // ==== activate a specific scene ====
244             if (allowShadeMovementCommands) {
245                 try {
246                     assertNotNull(sceneId);
247                     webTargets.activateScene(sceneId);
248                 } catch (HubProcessingException | HubMaintenanceException e) {
249                     fail(e.getMessage());
250                 }
251             }
252
253             // ==== test stop command ====
254             if (allowShadeMovementCommands) {
255                 try {
256                     assertNotNull(sceneId);
257                     webTargets.stopShade(shadeId);
258                 } catch (HubProcessingException | HubMaintenanceException e) {
259                     fail(e.getMessage());
260                 }
261             }
262
263             // ==== stop the client ====
264             if (client.isRunning()) {
265                 try {
266                     client.stop();
267                 } catch (Exception e) {
268                     fail(e.getMessage());
269                 }
270             }
271         }
272     }
273
274     /**
275      * Test parsing of ShadePosition (shade fully up).
276      *
277      */
278     @Test
279     public void testShadePositionParsingFullyUp() {
280         Capabilities capabilities = db.getCapabilities(0);
281         ShadePosition test = new ShadePosition().setPosition(capabilities, PRIMARY_ZERO_IS_CLOSED, 0);
282         assertNotNull(test);
283         State pos = test.getState(capabilities, PRIMARY_ZERO_IS_CLOSED);
284         assertEquals(PercentType.class, pos.getClass());
285         assertEquals(0, ((PercentType) pos).intValue());
286         pos = test.getState(capabilities, VANE_TILT_COORDS);
287         assertTrue(UnDefType.UNDEF.equals(pos));
288     }
289
290     /**
291      * Test parsing of ShadePosition (shade fully down (method 1)).
292      *
293      */
294     @Test
295     public void testShadePositionParsingShadeFullyDown1() {
296         Capabilities capabilities = db.getCapabilities(0);
297         ShadePosition test = new ShadePosition().setPosition(capabilities, PRIMARY_ZERO_IS_CLOSED, 100);
298         assertNotNull(test);
299         State pos = test.getState(capabilities, PRIMARY_ZERO_IS_CLOSED);
300         assertEquals(PercentType.class, pos.getClass());
301         assertEquals(100, ((PercentType) pos).intValue());
302         pos = test.getState(capabilities, VANE_TILT_COORDS);
303         assertEquals(PercentType.class, pos.getClass());
304         assertEquals(0, ((PercentType) pos).intValue());
305     }
306
307     /**
308      * Test parsing of ShadePosition (shade fully down (method 2)).
309      *
310      */
311     @Test
312     public void testShadePositionParsingShadeFullyDown2() {
313         Capabilities capabilities = db.getCapabilities(0);
314         ShadePosition test = new ShadePosition().setPosition(capabilities, VANE_TILT_COORDS, 0);
315         assertNotNull(test);
316         State pos = test.getState(capabilities, PRIMARY_ZERO_IS_CLOSED);
317         assertEquals(PercentType.class, pos.getClass());
318         assertEquals(100, ((PercentType) pos).intValue());
319         pos = test.getState(capabilities, VANE_TILT_COORDS);
320         assertEquals(PercentType.class, pos.getClass());
321         assertEquals(0, ((PercentType) pos).intValue());
322     }
323
324     /**
325      * Test parsing of ShadePosition (shade fully down (method 2) and vane fully open).
326      *
327      */
328     @Test
329     public void testShadePositionParsingShadeFullyDownVaneOpen() {
330         Capabilities capabilities = db.getCapabilities(0);
331         ShadePosition test = new ShadePosition().setPosition(capabilities, VANE_TILT_COORDS, 100);
332         assertNotNull(test);
333         State pos = test.getState(capabilities, PRIMARY_ZERO_IS_CLOSED);
334         assertEquals(PercentType.class, pos.getClass());
335         assertEquals(100, ((PercentType) pos).intValue());
336         pos = test.getState(capabilities, VANE_TILT_COORDS);
337         assertEquals(PercentType.class, pos.getClass());
338         assertEquals(100, ((PercentType) pos).intValue());
339     }
340
341     /**
342      * Test generic JSON shades response.
343      */
344     @Test
345     public void shadeResponseIsParsedCorrectly() throws JsonParseException {
346         final Gson gson = new Gson();
347         Shades shades;
348         String json = loadJson("shades");
349         assertNotEquals("", json);
350         shades = gson.fromJson(json, Shades.class);
351         assertNotNull(shades);
352     }
353
354     /**
355      * Test generic JSON scene response.
356      */
357     @Test
358     public void sceneResponseIsParsedCorrectly() throws JsonParseException {
359         final Gson gson = new Gson();
360         String json = loadJson("scenes");
361         assertNotEquals("", json);
362
363         Scenes scenes = gson.fromJson(json, Scenes.class);
364         assertNotNull(scenes);
365         if (scenes != null) {
366             List<Scene> sceneData = scenes.sceneData;
367             assertNotNull(sceneData);
368             if (sceneData != null) {
369                 assertEquals(4, sceneData.size());
370                 Scene scene = sceneData.get(0);
371                 assertEquals("Door Open", scene.getName());
372                 assertEquals(18097, scene.id);
373             }
374         }
375     }
376
377     /**
378      * Test generic JSON scene collection response.
379      */
380     @Test
381     public void sceneCollectionResponseIsParsedCorrectly() throws JsonParseException {
382         final Gson gson = new Gson();
383         String json = loadJson("sceneCollections");
384         assertNotEquals("", json);
385
386         SceneCollections sceneCollections = gson.fromJson(json, SceneCollections.class);
387         assertNotNull(sceneCollections);
388
389         if (sceneCollections != null) {
390             List<SceneCollection> sceneCollectionData = sceneCollections.sceneCollectionData;
391             assertNotNull(sceneCollectionData);
392             if (sceneCollectionData != null) {
393                 assertEquals(1, sceneCollectionData.size());
394
395                 SceneCollection sceneCollection = sceneCollectionData.get(0);
396                 assertEquals("Børn op", sceneCollection.getName());
397                 assertEquals(27119, sceneCollection.id);
398             }
399         }
400     }
401
402     /**
403      * Test the JSON parsing for a duette top down bottom up shade.
404      */
405     @Test
406     public void duetteTopDownBottomUpShadeIsParsedCorrectly() throws JsonParseException {
407         final Gson gson = new Gson();
408         String json = loadJson("duette");
409         assertNotEquals("", json);
410
411         Shades shades = gson.fromJson(json, Shades.class);
412         assertNotNull(shades);
413         if (shades != null) {
414             List<ShadeData> shadesData = shades.shadeData;
415             assertNotNull(shadesData);
416
417             if (shadesData != null) {
418                 assertEquals(1, shadesData.size());
419                 ShadeData shadeData = shadesData.get(0);
420                 assertNotNull(shadeData);
421
422                 assertEquals("Gardin 1", shadeData.getName());
423                 assertEquals(63778, shadeData.id);
424
425                 ShadePosition shadePos = shadeData.positions;
426                 assertNotNull(shadePos);
427
428                 if (shadePos != null) {
429                     Integer capabilitiesValue = shadeData.capabilities;
430                     assertNotNull(capabilitiesValue);
431                     if (capabilitiesValue != null) {
432                         assertEquals(7, capabilitiesValue.intValue());
433
434                         Capabilities capabilities = db.getCapabilities(capabilitiesValue);
435
436                         State pos = shadePos.getState(capabilities, PRIMARY_ZERO_IS_CLOSED);
437                         assertEquals(PercentType.class, pos.getClass());
438                         assertEquals(59, ((PercentType) pos).intValue());
439
440                         pos = shadePos.getState(capabilities, SECONDARY_ZERO_IS_OPEN);
441                         assertEquals(PercentType.class, pos.getClass());
442                         assertEquals(35, ((PercentType) pos).intValue());
443
444                         pos = shadePos.getState(capabilities, VANE_TILT_COORDS);
445                         assertEquals(UnDefType.class, pos.getClass());
446
447                         assertEquals(3, shadeData.batteryStatus);
448
449                         assertEquals(4, shadeData.signalStrength);
450
451                         assertEquals(8, shadeData.type);
452
453                         assertTrue(db.isTypeInDatabase(shadeData.type));
454                         assertTrue(db.isCapabilitiesInDatabase(capabilitiesValue.intValue()));
455
456                         assertEquals(db.getType(shadeData.type).getCapabilities(), capabilitiesValue.intValue());
457
458                         assertTrue(db.getCapabilities(capabilitiesValue.intValue()).supportsSecondary());
459                         assertNotEquals(db.getType(shadeData.type).getCapabilities(), capabilitiesValue.intValue() + 1);
460
461                         // ==== when changing position1, position2 value is not changed (vice-versa) ====
462                         ShadePosition shadePosition = shadeData.positions;
463                         assertNotNull(shadePosition);
464                         if (shadePosition != null) {
465                             // ==== position2 ====
466                             State position2Old = shadePosition.getState(capabilities, SECONDARY_ZERO_IS_OPEN);
467                             shadePosition.setPosition(capabilities, PRIMARY_ZERO_IS_CLOSED, 99);
468                             State position2New = shadePosition.getState(capabilities, SECONDARY_ZERO_IS_OPEN);
469                             assertEquals(PercentType.class, position2Old.getClass());
470                             assertEquals(PercentType.class, position2New.getClass());
471                             assertEquals(((PercentType) position2Old).intValue(),
472                                     ((PercentType) position2New).intValue());
473
474                             // ==== position2 ====
475                             State position1Old = shadePosition.getState(capabilities, PRIMARY_ZERO_IS_CLOSED);
476                             shadePosition.setPosition(capabilities, SECONDARY_ZERO_IS_OPEN, 99);
477                             State position1New = shadePosition.getState(capabilities, PRIMARY_ZERO_IS_CLOSED);
478                             assertEquals(PercentType.class, position1Old.getClass());
479                             assertEquals(PercentType.class, position1New.getClass());
480                             assertEquals(((PercentType) position1Old).intValue(),
481                                     ((PercentType) position1New).intValue());
482                         }
483                     }
484                 }
485             }
486         }
487     }
488
489     /**
490      * General tests of the database of known types.
491      */
492     @Test
493     public void testKnownTypesDatabase() {
494         assertTrue(db.isTypeInDatabase(4));
495         assertTrue(db.isCapabilitiesInDatabase(0));
496
497         assertTrue(db.getCapabilities(6).isPrimaryStateInverted());
498         assertTrue(db.getCapabilities(7).supportsSecondary());
499
500         assertEquals(db.getType(4).getCapabilities(), 0);
501         assertEquals(db.getType(-1).getCapabilities(), -1);
502
503         assertFalse(db.isTypeInDatabase(99));
504         assertFalse(db.isCapabilitiesInDatabase(99));
505
506         assertFalse(db.getCapabilities(0).isPrimaryStateInverted());
507         assertFalse(db.getCapabilities(-1).isPrimaryStateInverted());
508         assertFalse(db.getCapabilities(99).isPrimaryStateInverted());
509
510         assertFalse(db.getCapabilities(0).supportsSecondary());
511         assertFalse(db.getCapabilities(-1).supportsSecondary());
512         assertFalse(db.getCapabilities(99).supportsSecondary());
513     }
514
515     /**
516      * On dual rail shades, it should not be possible to drive the upper rail below the lower rail, or vice-versa. So
517      * the binding code applies constraints on setting such positions. This test checks that the constraint code is
518      * working.
519      */
520     @Test
521     public void testDualRailConstraints() {
522         ShadePosition shade = new ShadePosition();
523         Capabilities caps = db.getCapabilities(7);
524
525         // ==== OK !! primary at bottom, secondary at top ====
526         shade.setPosition(caps, PRIMARY_ZERO_IS_CLOSED, 100).setPosition(caps, SECONDARY_ZERO_IS_OPEN, 0);
527         assertEquals(PercentType.HUNDRED, shade.getState(caps, PRIMARY_ZERO_IS_CLOSED));
528         assertEquals(PercentType.ZERO, shade.getState(caps, SECONDARY_ZERO_IS_OPEN));
529
530         // ==== OK !! primary at middle, secondary at top ====
531         shade.setPosition(caps, PRIMARY_ZERO_IS_CLOSED, 50).setPosition(caps, SECONDARY_ZERO_IS_OPEN, 0);
532         assertEquals(new PercentType(50), shade.getState(caps, PRIMARY_ZERO_IS_CLOSED));
533         assertEquals(PercentType.ZERO, shade.getState(caps, SECONDARY_ZERO_IS_OPEN));
534
535         // ==== OK !! primary at middle, secondary at middle ====
536         shade.setPosition(caps, PRIMARY_ZERO_IS_CLOSED, 50).setPosition(caps, SECONDARY_ZERO_IS_OPEN, 50);
537         assertEquals(new PercentType(50), shade.getState(caps, PRIMARY_ZERO_IS_CLOSED));
538         assertEquals(new PercentType(50), shade.getState(caps, SECONDARY_ZERO_IS_OPEN));
539
540         // ==== IMPOSSIBLE !! secondary at middle, primary above => test the constraining code ====
541         shade.setPosition(caps, SECONDARY_ZERO_IS_OPEN, 0).setPosition(caps, PRIMARY_ZERO_IS_CLOSED, 100);
542         shade.setPosition(caps, SECONDARY_ZERO_IS_OPEN, 40).setPosition(caps, PRIMARY_ZERO_IS_CLOSED, 25);
543         assertEquals(new PercentType(40), shade.getState(caps, SECONDARY_ZERO_IS_OPEN));
544         assertEquals(new PercentType(40), shade.getState(caps, PRIMARY_ZERO_IS_CLOSED));
545
546         // ==== OK !! secondary at middle, primary below ====
547         shade.setPosition(caps, SECONDARY_ZERO_IS_OPEN, 0).setPosition(caps, PRIMARY_ZERO_IS_CLOSED, 100);
548         shade.setPosition(caps, SECONDARY_ZERO_IS_OPEN, 50).setPosition(caps, PRIMARY_ZERO_IS_CLOSED, 75);
549         assertEquals(new PercentType(50), shade.getState(caps, SECONDARY_ZERO_IS_OPEN));
550         assertEquals(new PercentType(75), shade.getState(caps, PRIMARY_ZERO_IS_CLOSED));
551
552         // ==== IMPOSSIBLE !! primary at middle, secondary below => test the constraining code ====
553         shade.setPosition(caps, SECONDARY_ZERO_IS_OPEN, 0).setPosition(caps, PRIMARY_ZERO_IS_CLOSED, 100);
554         shade.setPosition(caps, PRIMARY_ZERO_IS_CLOSED, 60).setPosition(caps, SECONDARY_ZERO_IS_OPEN, 75);
555         assertEquals(new PercentType(60), shade.getState(caps, PRIMARY_ZERO_IS_CLOSED));
556         assertEquals(new PercentType(60), shade.getState(caps, SECONDARY_ZERO_IS_OPEN));
557
558         // ==== OK !! primary at middle, secondary above ====
559         shade.setPosition(caps, SECONDARY_ZERO_IS_OPEN, 0).setPosition(caps, PRIMARY_ZERO_IS_CLOSED, 100);
560         shade.setPosition(caps, PRIMARY_ZERO_IS_CLOSED, 60).setPosition(caps, SECONDARY_ZERO_IS_OPEN, 25);
561         assertEquals(new PercentType(60), shade.getState(caps, PRIMARY_ZERO_IS_CLOSED));
562         assertEquals(new PercentType(25), shade.getState(caps, SECONDARY_ZERO_IS_OPEN));
563     }
564 }