]> git.basschouten.com Git - openhab-addons.git/blob
229468ecb53d89e71eeebbedbe46d1668e74bc35
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2022 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.api.ShadePosition;
30 import org.openhab.binding.hdpowerview.internal.api.responses.SceneCollections;
31 import org.openhab.binding.hdpowerview.internal.api.responses.SceneCollections.SceneCollection;
32 import org.openhab.binding.hdpowerview.internal.api.responses.Scenes;
33 import org.openhab.binding.hdpowerview.internal.api.responses.Scenes.Scene;
34 import org.openhab.binding.hdpowerview.internal.api.responses.Shades;
35 import org.openhab.binding.hdpowerview.internal.api.responses.Shades.ShadeData;
36 import org.openhab.binding.hdpowerview.internal.database.ShadeCapabilitiesDatabase;
37 import org.openhab.binding.hdpowerview.internal.database.ShadeCapabilitiesDatabase.Capabilities;
38 import org.openhab.binding.hdpowerview.internal.exceptions.HubException;
39 import org.openhab.binding.hdpowerview.internal.exceptions.HubMaintenanceException;
40 import org.openhab.binding.hdpowerview.internal.exceptions.HubProcessingException;
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                 List<ShadeData> shadesData = shadesX.shadeData;
124                 assertNotNull(shadesData);
125
126                 if (shadesData != null) {
127                     assertTrue(!shadesData.isEmpty());
128                     ShadeData shadeData;
129                     shadeData = shadesData.get(0);
130                     assertNotNull(shadeData);
131                     assertTrue(shadeData.getName().length() > 0);
132                     shadePos = shadeData.positions;
133                     assertNotNull(shadePos);
134                     ShadeData shadeZero = shadesData.get(0);
135                     assertNotNull(shadeZero);
136                     shadeId = shadeZero.id;
137                     assertNotEquals(0, shadeId);
138
139                     for (ShadeData shadexData : shadesData) {
140                         String shadeName = shadexData.getName();
141                         assertNotNull(shadeName);
142                     }
143                 }
144             } catch (HubException e) {
145                 fail(e.getMessage());
146             }
147
148             // ==== get all scenes ====
149             int sceneId = 0;
150             try {
151                 Scenes scenes = webTargets.getScenes();
152                 assertNotNull(scenes);
153
154                 List<Scene> scenesData = scenes.sceneData;
155                 assertNotNull(scenesData);
156
157                 if (scenesData != null) {
158                     assertTrue(!scenesData.isEmpty());
159                     Scene sceneZero = scenesData.get(0);
160                     assertNotNull(sceneZero);
161                     sceneId = sceneZero.id;
162                     assertTrue(sceneId > 0);
163
164                     for (Scene scene : scenesData) {
165                         String sceneName = scene.getName();
166                         assertNotNull(sceneName);
167                     }
168                 }
169             } catch (HubException e) {
170                 fail(e.getMessage());
171             }
172
173             // ==== refresh a specific shade ====
174             ShadeData shadeData = null;
175             try {
176                 assertNotEquals(0, shadeId);
177                 shadeData = webTargets.refreshShadePosition(shadeId);
178             } catch (HubException e) {
179                 fail(e.getMessage());
180             }
181
182             // ==== move a specific shade ====
183             try {
184                 assertNotEquals(0, shadeId);
185
186                 if (shadeData != null) {
187                     ShadePosition positions = shadeData.positions;
188                     assertNotNull(positions);
189                     Integer capabilitiesValue = shadeData.capabilities;
190                     assertNotNull(capabilitiesValue);
191
192                     if (positions != null && capabilitiesValue != null) {
193                         Capabilities capabilities = db.getCapabilities(capabilitiesValue.intValue());
194
195                         State pos = positions.getState(capabilities, PRIMARY_ZERO_IS_CLOSED);
196                         assertEquals(PercentType.class, pos.getClass());
197
198                         int position = ((PercentType) pos).intValue();
199                         position = position + ((position <= 10) ? 5 : -5);
200
201                         ShadePosition targetPosition = new ShadePosition().setPosition(capabilities,
202                                 PRIMARY_ZERO_IS_CLOSED, position);
203                         assertNotNull(targetPosition);
204
205                         if (allowShadeMovementCommands) {
206                             webTargets.moveShade(shadeId, targetPosition);
207
208                             ShadeData newData = webTargets.getShade(shadeId);
209                             ShadePosition actualPosition = newData.positions;
210                             assertNotNull(actualPosition);
211                             if (actualPosition != null) {
212                                 assertEquals(targetPosition.getState(capabilities, PRIMARY_ZERO_IS_CLOSED),
213                                         actualPosition.getState(capabilities, PRIMARY_ZERO_IS_CLOSED));
214                             }
215                         }
216                     }
217                 }
218             } catch (HubException e) {
219                 fail(e.getMessage());
220             }
221
222             // ==== activate a specific scene ====
223             if (allowShadeMovementCommands) {
224                 try {
225                     assertNotNull(sceneId);
226                     webTargets.activateScene(sceneId);
227                 } catch (HubProcessingException | HubMaintenanceException e) {
228                     fail(e.getMessage());
229                 }
230             }
231
232             // ==== test stop command ====
233             if (allowShadeMovementCommands) {
234                 try {
235                     assertNotNull(sceneId);
236                     webTargets.stopShade(shadeId);
237                 } catch (HubException e) {
238                     fail(e.getMessage());
239                 }
240             }
241
242             // ==== stop the client ====
243             if (client.isRunning()) {
244                 try {
245                     client.stop();
246                 } catch (Exception e) {
247                     fail(e.getMessage());
248                 }
249             }
250         }
251     }
252
253     /**
254      * Test parsing of ShadePosition (shade fully up).
255      *
256      */
257     @Test
258     public void testShadePositionParsingFullyUp() {
259         Capabilities capabilities = db.getCapabilities(0);
260         ShadePosition test = new ShadePosition().setPosition(capabilities, PRIMARY_ZERO_IS_CLOSED, 0);
261         assertNotNull(test);
262         State pos = test.getState(capabilities, PRIMARY_ZERO_IS_CLOSED);
263         assertEquals(PercentType.class, pos.getClass());
264         assertEquals(0, ((PercentType) pos).intValue());
265         pos = test.getState(capabilities, VANE_TILT_COORDS);
266         assertTrue(UnDefType.UNDEF.equals(pos));
267     }
268
269     /**
270      * Test parsing of ShadePosition (shade fully down (method 1)).
271      *
272      */
273     @Test
274     public void testShadePositionParsingShadeFullyDown1() {
275         Capabilities capabilities = db.getCapabilities(0);
276         ShadePosition test = new ShadePosition().setPosition(capabilities, PRIMARY_ZERO_IS_CLOSED, 100);
277         assertNotNull(test);
278         State pos = test.getState(capabilities, PRIMARY_ZERO_IS_CLOSED);
279         assertEquals(PercentType.class, pos.getClass());
280         assertEquals(100, ((PercentType) pos).intValue());
281         pos = test.getState(capabilities, VANE_TILT_COORDS);
282         assertEquals(PercentType.class, pos.getClass());
283         assertEquals(0, ((PercentType) pos).intValue());
284     }
285
286     /**
287      * Test parsing of ShadePosition (shade fully down (method 2)).
288      *
289      */
290     @Test
291     public void testShadePositionParsingShadeFullyDown2() {
292         Capabilities capabilities = db.getCapabilities(0);
293         ShadePosition test = new ShadePosition().setPosition(capabilities, VANE_TILT_COORDS, 0);
294         assertNotNull(test);
295         State pos = test.getState(capabilities, PRIMARY_ZERO_IS_CLOSED);
296         assertEquals(PercentType.class, pos.getClass());
297         assertEquals(100, ((PercentType) pos).intValue());
298         pos = test.getState(capabilities, VANE_TILT_COORDS);
299         assertEquals(PercentType.class, pos.getClass());
300         assertEquals(0, ((PercentType) pos).intValue());
301     }
302
303     /**
304      * Test parsing of ShadePosition (shade fully down (method 2) and vane fully open).
305      *
306      */
307     @Test
308     public void testShadePositionParsingShadeFullyDownVaneOpen() {
309         Capabilities capabilities = db.getCapabilities(0);
310         ShadePosition test = new ShadePosition().setPosition(capabilities, VANE_TILT_COORDS, 100);
311         assertNotNull(test);
312         State pos = test.getState(capabilities, PRIMARY_ZERO_IS_CLOSED);
313         assertEquals(PercentType.class, pos.getClass());
314         assertEquals(100, ((PercentType) pos).intValue());
315         pos = test.getState(capabilities, VANE_TILT_COORDS);
316         assertEquals(PercentType.class, pos.getClass());
317         assertEquals(100, ((PercentType) pos).intValue());
318     }
319
320     /**
321      * Test generic JSON shades response.
322      */
323     @Test
324     public void shadeResponseIsParsedCorrectly() throws JsonParseException {
325         final Gson gson = new Gson();
326         Shades shades;
327         String json = loadJson("shades");
328         assertNotEquals("", json);
329         shades = gson.fromJson(json, Shades.class);
330         assertNotNull(shades);
331     }
332
333     /**
334      * Test generic JSON scene response.
335      */
336     @Test
337     public void sceneResponseIsParsedCorrectly() throws JsonParseException {
338         final Gson gson = new Gson();
339         String json = loadJson("scenes");
340         assertNotEquals("", json);
341
342         Scenes scenes = gson.fromJson(json, Scenes.class);
343         assertNotNull(scenes);
344         if (scenes != null) {
345             List<Scene> sceneData = scenes.sceneData;
346             assertNotNull(sceneData);
347             if (sceneData != null) {
348                 assertEquals(4, sceneData.size());
349                 Scene scene = sceneData.get(0);
350                 assertEquals("Door Open", scene.getName());
351                 assertEquals(18097, scene.id);
352             }
353         }
354     }
355
356     /**
357      * Test generic JSON scene collection response.
358      */
359     @Test
360     public void sceneCollectionResponseIsParsedCorrectly() throws JsonParseException {
361         final Gson gson = new Gson();
362         String json = loadJson("sceneCollections");
363         assertNotEquals("", json);
364
365         SceneCollections sceneCollections = gson.fromJson(json, SceneCollections.class);
366         assertNotNull(sceneCollections);
367
368         if (sceneCollections != null) {
369             List<SceneCollection> sceneCollectionData = sceneCollections.sceneCollectionData;
370             assertNotNull(sceneCollectionData);
371             if (sceneCollectionData != null) {
372                 assertEquals(1, sceneCollectionData.size());
373
374                 SceneCollection sceneCollection = sceneCollectionData.get(0);
375                 assertEquals("Børn op", sceneCollection.getName());
376                 assertEquals(27119, sceneCollection.id);
377             }
378         }
379     }
380
381     /**
382      * Test the JSON parsing for a duette top down bottom up shade.
383      */
384     @Test
385     public void duetteTopDownBottomUpShadeIsParsedCorrectly() throws JsonParseException {
386         final Gson gson = new Gson();
387         String json = loadJson("duette");
388         assertNotEquals("", json);
389
390         Shades shades = gson.fromJson(json, Shades.class);
391         assertNotNull(shades);
392         if (shades != null) {
393             List<ShadeData> shadesData = shades.shadeData;
394             assertNotNull(shadesData);
395
396             if (shadesData != null) {
397                 assertEquals(1, shadesData.size());
398                 ShadeData shadeData = shadesData.get(0);
399                 assertNotNull(shadeData);
400
401                 assertEquals("Gardin 1", shadeData.getName());
402                 assertEquals(63778, shadeData.id);
403
404                 ShadePosition shadePos = shadeData.positions;
405                 assertNotNull(shadePos);
406
407                 if (shadePos != null) {
408                     Integer capabilitiesValue = shadeData.capabilities;
409                     assertNotNull(capabilitiesValue);
410                     if (capabilitiesValue != null) {
411                         assertEquals(7, capabilitiesValue.intValue());
412
413                         Capabilities capabilities = db.getCapabilities(capabilitiesValue);
414
415                         State pos = shadePos.getState(capabilities, PRIMARY_ZERO_IS_CLOSED);
416                         assertEquals(PercentType.class, pos.getClass());
417                         assertEquals(59, ((PercentType) pos).intValue());
418
419                         pos = shadePos.getState(capabilities, SECONDARY_ZERO_IS_OPEN);
420                         assertEquals(PercentType.class, pos.getClass());
421                         assertEquals(35, ((PercentType) pos).intValue());
422
423                         pos = shadePos.getState(capabilities, VANE_TILT_COORDS);
424                         assertEquals(UnDefType.class, pos.getClass());
425
426                         assertEquals(3, shadeData.batteryStatus);
427
428                         assertEquals(4, shadeData.signalStrength);
429
430                         assertEquals(8, shadeData.type);
431
432                         assertTrue(db.isTypeInDatabase(shadeData.type));
433                         assertTrue(db.isCapabilitiesInDatabase(capabilitiesValue.intValue()));
434
435                         assertEquals(db.getType(shadeData.type).getCapabilities(), capabilitiesValue.intValue());
436
437                         assertTrue(db.getCapabilities(capabilitiesValue.intValue()).supportsSecondary());
438                         assertNotEquals(db.getType(shadeData.type).getCapabilities(), capabilitiesValue.intValue() + 1);
439
440                         // ==== when changing position1, position2 value is not changed (vice-versa) ====
441                         ShadePosition shadePosition = shadeData.positions;
442                         assertNotNull(shadePosition);
443                         if (shadePosition != null) {
444                             // ==== position2 ====
445                             State position2Old = shadePosition.getState(capabilities, SECONDARY_ZERO_IS_OPEN);
446                             shadePosition.setPosition(capabilities, PRIMARY_ZERO_IS_CLOSED, 99);
447                             State position2New = shadePosition.getState(capabilities, SECONDARY_ZERO_IS_OPEN);
448                             assertEquals(PercentType.class, position2Old.getClass());
449                             assertEquals(PercentType.class, position2New.getClass());
450                             assertEquals(((PercentType) position2Old).intValue(),
451                                     ((PercentType) position2New).intValue());
452
453                             // ==== position2 ====
454                             State position1Old = shadePosition.getState(capabilities, PRIMARY_ZERO_IS_CLOSED);
455                             shadePosition.setPosition(capabilities, SECONDARY_ZERO_IS_OPEN, 99);
456                             State position1New = shadePosition.getState(capabilities, PRIMARY_ZERO_IS_CLOSED);
457                             assertEquals(PercentType.class, position1Old.getClass());
458                             assertEquals(PercentType.class, position1New.getClass());
459                             assertEquals(((PercentType) position1Old).intValue(),
460                                     ((PercentType) position1New).intValue());
461                         }
462                     }
463                 }
464             }
465         }
466     }
467
468     /**
469      * General tests of the database of known types.
470      */
471     @Test
472     public void testKnownTypesDatabase() {
473         assertTrue(db.isTypeInDatabase(4));
474         assertTrue(db.isCapabilitiesInDatabase(0));
475
476         assertTrue(db.getCapabilities(6).isPrimaryStateInverted());
477         assertTrue(db.getCapabilities(7).supportsSecondary());
478
479         assertEquals(db.getType(4).getCapabilities(), 0);
480         assertEquals(db.getType(-1).getCapabilities(), -1);
481
482         assertFalse(db.isTypeInDatabase(99));
483         assertFalse(db.isCapabilitiesInDatabase(99));
484
485         assertFalse(db.getCapabilities(0).isPrimaryStateInverted());
486         assertFalse(db.getCapabilities(-1).isPrimaryStateInverted());
487         assertFalse(db.getCapabilities(99).isPrimaryStateInverted());
488
489         assertFalse(db.getCapabilities(0).supportsSecondary());
490         assertFalse(db.getCapabilities(-1).supportsSecondary());
491         assertFalse(db.getCapabilities(99).supportsSecondary());
492     }
493
494     /**
495      * On dual rail shades, it should not be possible to drive the upper rail below the lower rail, or vice-versa. So
496      * the binding code applies constraints on setting such positions. This test checks that the constraint code is
497      * working.
498      */
499     @Test
500     public void testDualRailConstraints() {
501         ShadePosition shade = new ShadePosition();
502         Capabilities caps = db.getCapabilities(7);
503
504         // ==== OK !! primary at bottom, secondary at top ====
505         shade.setPosition(caps, PRIMARY_ZERO_IS_CLOSED, 100).setPosition(caps, SECONDARY_ZERO_IS_OPEN, 0);
506         assertEquals(PercentType.HUNDRED, shade.getState(caps, PRIMARY_ZERO_IS_CLOSED));
507         assertEquals(PercentType.ZERO, shade.getState(caps, SECONDARY_ZERO_IS_OPEN));
508
509         // ==== OK !! primary at middle, secondary at top ====
510         shade.setPosition(caps, PRIMARY_ZERO_IS_CLOSED, 50).setPosition(caps, SECONDARY_ZERO_IS_OPEN, 0);
511         assertEquals(new PercentType(50), shade.getState(caps, PRIMARY_ZERO_IS_CLOSED));
512         assertEquals(PercentType.ZERO, shade.getState(caps, SECONDARY_ZERO_IS_OPEN));
513
514         // ==== OK !! primary at middle, secondary at middle ====
515         shade.setPosition(caps, PRIMARY_ZERO_IS_CLOSED, 50).setPosition(caps, SECONDARY_ZERO_IS_OPEN, 50);
516         assertEquals(new PercentType(50), shade.getState(caps, PRIMARY_ZERO_IS_CLOSED));
517         assertEquals(new PercentType(50), shade.getState(caps, SECONDARY_ZERO_IS_OPEN));
518
519         // ==== IMPOSSIBLE !! secondary at middle, primary above => test the constraining code ====
520         shade.setPosition(caps, SECONDARY_ZERO_IS_OPEN, 0).setPosition(caps, PRIMARY_ZERO_IS_CLOSED, 100);
521         shade.setPosition(caps, SECONDARY_ZERO_IS_OPEN, 40).setPosition(caps, PRIMARY_ZERO_IS_CLOSED, 25);
522         assertEquals(new PercentType(40), shade.getState(caps, SECONDARY_ZERO_IS_OPEN));
523         assertEquals(new PercentType(40), shade.getState(caps, PRIMARY_ZERO_IS_CLOSED));
524
525         // ==== OK !! secondary at middle, primary below ====
526         shade.setPosition(caps, SECONDARY_ZERO_IS_OPEN, 0).setPosition(caps, PRIMARY_ZERO_IS_CLOSED, 100);
527         shade.setPosition(caps, SECONDARY_ZERO_IS_OPEN, 50).setPosition(caps, PRIMARY_ZERO_IS_CLOSED, 75);
528         assertEquals(new PercentType(50), shade.getState(caps, SECONDARY_ZERO_IS_OPEN));
529         assertEquals(new PercentType(75), shade.getState(caps, PRIMARY_ZERO_IS_CLOSED));
530
531         // ==== IMPOSSIBLE !! primary at middle, secondary below => test the constraining code ====
532         shade.setPosition(caps, SECONDARY_ZERO_IS_OPEN, 0).setPosition(caps, PRIMARY_ZERO_IS_CLOSED, 100);
533         shade.setPosition(caps, PRIMARY_ZERO_IS_CLOSED, 60).setPosition(caps, SECONDARY_ZERO_IS_OPEN, 75);
534         assertEquals(new PercentType(60), shade.getState(caps, PRIMARY_ZERO_IS_CLOSED));
535         assertEquals(new PercentType(60), shade.getState(caps, SECONDARY_ZERO_IS_OPEN));
536
537         // ==== OK !! primary at middle, secondary above ====
538         shade.setPosition(caps, SECONDARY_ZERO_IS_OPEN, 0).setPosition(caps, PRIMARY_ZERO_IS_CLOSED, 100);
539         shade.setPosition(caps, PRIMARY_ZERO_IS_CLOSED, 60).setPosition(caps, SECONDARY_ZERO_IS_OPEN, 25);
540         assertEquals(new PercentType(60), shade.getState(caps, PRIMARY_ZERO_IS_CLOSED));
541         assertEquals(new PercentType(25), shade.getState(caps, SECONDARY_ZERO_IS_OPEN));
542     }
543 }