2 * Copyright (c) 2010-2021 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.hdpowerview;
15 import static org.junit.jupiter.api.Assertions.*;
16 import static org.openhab.binding.hdpowerview.internal.api.CoordinateSystem.*;
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;
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;
45 import com.google.gson.Gson;
46 import com.google.gson.JsonParseException;
49 * Unit tests for HD PowerView binding.
51 * @author Andrew Fiddian-Green - Initial contribution
52 * @author Jacob Laursen - Add support for scene groups
55 public class HDPowerViewJUnitTests {
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");
60 private final ShadeCapabilitiesDatabase db = new ShadeCapabilitiesDatabase();
63 * load a test JSON string from a file.
65 private String loadJson(String fileName) {
67 return Files.readAllLines(Paths.get(String.format("src/test/resources/%s.json", fileName))).stream()
68 .collect(Collectors.joining());
69 } catch (IOException e) {
76 * Run a series of ONLINE tests on the communication with a hub.
78 * @param hubIPAddress must be a valid hub IP address to run the
79 * tests on; or an INVALID IP address to
81 * @param allowShadeMovementCommands set to true if you accept that the tests
82 * shall physically move the shades
85 public void testOnlineCommunication() {
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"
91 String hubIPAddress = "192.168.1.xxx";
94 * NOTE: set allowShadeMovementCommands = true if you accept physically moving
95 * the shades during these tests
97 boolean allowShadeMovementCommands = false;
99 if (VALID_IP_V4_ADDRESS.matcher(hubIPAddress).matches()) {
100 // ==== initialize stuff ====
101 HttpClient client = new HttpClient();
102 assertNotNull(client);
104 // ==== start the client ====
107 assertTrue(client.isStarted());
108 } catch (Exception e) {
109 fail(e.getMessage());
112 HDPowerViewWebTargets webTargets = new HDPowerViewWebTargets(client, hubIPAddress);
113 assertNotNull(webTargets);
116 ShadePosition shadePos = null;
117 Shades shadesX = null;
119 // ==== get all shades ====
121 shadesX = webTargets.getShades();
122 assertNotNull(shadesX);
123 if (shadesX != null) {
124 List<ShadeData> shadesData = shadesX.shadeData;
125 assertNotNull(shadesData);
127 if (shadesData != null) {
128 assertTrue(!shadesData.isEmpty());
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);
140 for (ShadeData shadexData : shadesData) {
141 String shadeName = shadexData.getName();
142 assertNotNull(shadeName);
146 } catch (JsonParseException | HubProcessingException | HubMaintenanceException e) {
147 fail(e.getMessage());
150 // ==== get all scenes ====
153 Scenes scenes = webTargets.getScenes();
154 assertNotNull(scenes);
156 if (scenes != null) {
157 List<Scene> scenesData = scenes.sceneData;
158 assertNotNull(scenesData);
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);
167 for (Scene scene : scenesData) {
168 String sceneName = scene.getName();
169 assertNotNull(sceneName);
173 } catch (JsonParseException | HubProcessingException | HubMaintenanceException e) {
174 fail(e.getMessage());
177 // ==== refresh a specific shade ====
180 assertNotEquals(0, shadeId);
181 shade = webTargets.refreshShadePosition(shadeId);
182 assertNotNull(shade);
183 } catch (HubProcessingException | HubMaintenanceException e) {
184 fail(e.getMessage());
187 // ==== move a specific shade ====
189 assertNotEquals(0, shadeId);
190 assertNotNull(shade);
192 ShadeData shadeData = shade.shade;
193 assertNotNull(shadeData);
195 if (shadeData != null) {
196 ShadePosition positions = shadeData.positions;
197 assertNotNull(positions);
199 if (positions != null) {
200 Integer capabilitiesValue = shadeData.capabilities;
201 assertNotNull(capabilitiesValue);
203 if (capabilitiesValue != null) {
204 Capabilities capabilities = db.getCapabilities(capabilitiesValue.intValue());
206 State pos = positions.getState(capabilities, PRIMARY_ZERO_IS_CLOSED);
207 assertEquals(PercentType.class, pos.getClass());
209 int position = ((PercentType) pos).intValue();
210 position = position + ((position <= 10) ? 5 : -5);
212 ShadePosition targetPosition = new ShadePosition().setPosition(capabilities,
213 PRIMARY_ZERO_IS_CLOSED, position);
214 assertNotNull(targetPosition);
216 if (allowShadeMovementCommands) {
217 webTargets.moveShade(shadeId, targetPosition);
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) {
229 targetPosition.getState(capabilities, PRIMARY_ZERO_IS_CLOSED),
230 actualPosition.getState(capabilities, PRIMARY_ZERO_IS_CLOSED));
239 } catch (HubProcessingException | HubMaintenanceException e) {
240 fail(e.getMessage());
243 // ==== activate a specific scene ====
244 if (allowShadeMovementCommands) {
246 assertNotNull(sceneId);
247 webTargets.activateScene(sceneId);
248 } catch (HubProcessingException | HubMaintenanceException e) {
249 fail(e.getMessage());
253 // ==== test stop command ====
254 if (allowShadeMovementCommands) {
256 assertNotNull(sceneId);
257 webTargets.stopShade(shadeId);
258 } catch (HubProcessingException | HubMaintenanceException e) {
259 fail(e.getMessage());
263 // ==== stop the client ====
264 if (client.isRunning()) {
267 } catch (Exception e) {
268 fail(e.getMessage());
275 * Test parsing of ShadePosition (shade fully up).
279 public void testShadePositionParsingFullyUp() {
280 Capabilities capabilities = db.getCapabilities(0);
281 ShadePosition test = new ShadePosition().setPosition(capabilities, PRIMARY_ZERO_IS_CLOSED, 0);
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));
291 * Test parsing of ShadePosition (shade fully down (method 1)).
295 public void testShadePositionParsingShadeFullyDown1() {
296 Capabilities capabilities = db.getCapabilities(0);
297 ShadePosition test = new ShadePosition().setPosition(capabilities, PRIMARY_ZERO_IS_CLOSED, 100);
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());
308 * Test parsing of ShadePosition (shade fully down (method 2)).
312 public void testShadePositionParsingShadeFullyDown2() {
313 Capabilities capabilities = db.getCapabilities(0);
314 ShadePosition test = new ShadePosition().setPosition(capabilities, VANE_TILT_COORDS, 0);
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());
325 * Test parsing of ShadePosition (shade fully down (method 2) and vane fully open).
329 public void testShadePositionParsingShadeFullyDownVaneOpen() {
330 Capabilities capabilities = db.getCapabilities(0);
331 ShadePosition test = new ShadePosition().setPosition(capabilities, VANE_TILT_COORDS, 100);
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());
342 * Test generic JSON shades response.
345 public void shadeResponseIsParsedCorrectly() throws JsonParseException {
346 final Gson gson = new Gson();
348 String json = loadJson("shades");
349 assertNotEquals("", json);
350 shades = gson.fromJson(json, Shades.class);
351 assertNotNull(shades);
355 * Test generic JSON scene response.
358 public void sceneResponseIsParsedCorrectly() throws JsonParseException {
359 final Gson gson = new Gson();
360 String json = loadJson("scenes");
361 assertNotEquals("", json);
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);
378 * Test generic JSON scene collection response.
381 public void sceneCollectionResponseIsParsedCorrectly() throws JsonParseException {
382 final Gson gson = new Gson();
383 String json = loadJson("sceneCollections");
384 assertNotEquals("", json);
386 SceneCollections sceneCollections = gson.fromJson(json, SceneCollections.class);
387 assertNotNull(sceneCollections);
389 if (sceneCollections != null) {
390 List<SceneCollection> sceneCollectionData = sceneCollections.sceneCollectionData;
391 assertNotNull(sceneCollectionData);
392 if (sceneCollectionData != null) {
393 assertEquals(1, sceneCollectionData.size());
395 SceneCollection sceneCollection = sceneCollectionData.get(0);
396 assertEquals("Børn op", sceneCollection.getName());
397 assertEquals(27119, sceneCollection.id);
403 * Test the JSON parsing for a duette top down bottom up shade.
406 public void duetteTopDownBottomUpShadeIsParsedCorrectly() throws JsonParseException {
407 final Gson gson = new Gson();
408 String json = loadJson("duette");
409 assertNotEquals("", json);
411 Shades shades = gson.fromJson(json, Shades.class);
412 assertNotNull(shades);
413 if (shades != null) {
414 List<ShadeData> shadesData = shades.shadeData;
415 assertNotNull(shadesData);
417 if (shadesData != null) {
418 assertEquals(1, shadesData.size());
419 ShadeData shadeData = shadesData.get(0);
420 assertNotNull(shadeData);
422 assertEquals("Gardin 1", shadeData.getName());
423 assertEquals(63778, shadeData.id);
425 ShadePosition shadePos = shadeData.positions;
426 assertNotNull(shadePos);
428 if (shadePos != null) {
429 Integer capabilitiesValue = shadeData.capabilities;
430 assertNotNull(capabilitiesValue);
431 if (capabilitiesValue != null) {
432 assertEquals(7, capabilitiesValue.intValue());
434 Capabilities capabilities = db.getCapabilities(capabilitiesValue);
436 State pos = shadePos.getState(capabilities, PRIMARY_ZERO_IS_CLOSED);
437 assertEquals(PercentType.class, pos.getClass());
438 assertEquals(59, ((PercentType) pos).intValue());
440 pos = shadePos.getState(capabilities, SECONDARY_ZERO_IS_OPEN);
441 assertEquals(PercentType.class, pos.getClass());
442 assertEquals(35, ((PercentType) pos).intValue());
444 pos = shadePos.getState(capabilities, VANE_TILT_COORDS);
445 assertEquals(UnDefType.class, pos.getClass());
447 assertEquals(3, shadeData.batteryStatus);
449 assertEquals(4, shadeData.signalStrength);
451 assertEquals(8, shadeData.type);
453 assertTrue(db.isTypeInDatabase(shadeData.type));
454 assertTrue(db.isCapabilitiesInDatabase(capabilitiesValue.intValue()));
456 assertEquals(db.getType(shadeData.type).getCapabilities(), capabilitiesValue.intValue());
458 assertTrue(db.getCapabilities(capabilitiesValue.intValue()).supportsSecondary());
459 assertNotEquals(db.getType(shadeData.type).getCapabilities(), capabilitiesValue.intValue() + 1);
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());
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());
490 * General tests of the database of known types.
493 public void testKnownTypesDatabase() {
494 assertTrue(db.isTypeInDatabase(4));
495 assertTrue(db.isCapabilitiesInDatabase(0));
497 assertTrue(db.getCapabilities(6).isPrimaryStateInverted());
498 assertTrue(db.getCapabilities(7).supportsSecondary());
500 assertEquals(db.getType(4).getCapabilities(), 0);
501 assertEquals(db.getType(-1).getCapabilities(), -1);
503 assertFalse(db.isTypeInDatabase(99));
504 assertFalse(db.isCapabilitiesInDatabase(99));
506 assertFalse(db.getCapabilities(0).isPrimaryStateInverted());
507 assertFalse(db.getCapabilities(-1).isPrimaryStateInverted());
508 assertFalse(db.getCapabilities(99).isPrimaryStateInverted());
510 assertFalse(db.getCapabilities(0).supportsSecondary());
511 assertFalse(db.getCapabilities(-1).supportsSecondary());
512 assertFalse(db.getCapabilities(99).supportsSecondary());
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
521 public void testDualRailConstraints() {
522 ShadePosition shade = new ShadePosition();
523 Capabilities caps = db.getCapabilities(7);
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));
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));
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));
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));
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));
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));
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));