2 * Copyright (c) 2010-2022 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.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;
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");
61 * load a test JSON string from a file.
63 private String loadJson(String fileName) {
65 return Files.readAllLines(Paths.get(String.format("src/test/resources/%s.json", fileName))).stream()
66 .collect(Collectors.joining());
67 } catch (IOException e) {
74 * Run a series of ONLINE tests on the communication with a hub.
76 * @param hubIPAddress must be a valid hub IP address to run the
77 * tests on; or an INVALID IP address to
79 * @param allowShadeMovementCommands set to true if you accept that the tests
80 * shall physically move the shades
83 public void testOnlineCommunication() {
85 * NOTE: in order to actually run these tests you must have a hub physically
86 * available, and its IP address must be correctly configured in the
87 * "hubIPAddress" string constant e.g. "192.168.1.123"
89 String hubIPAddress = "192.168.1.xxx";
92 * NOTE: set allowShadeMovementCommands = true if you accept physically moving
93 * the shades during these tests
95 boolean allowShadeMovementCommands = false;
97 if (VALID_IP_V4_ADDRESS.matcher(hubIPAddress).matches()) {
98 // ==== initialize stuff ====
99 HttpClient client = new HttpClient();
100 assertNotNull(client);
102 // ==== start the client ====
105 assertTrue(client.isStarted());
106 } catch (Exception e) {
107 fail(e.getMessage());
110 HDPowerViewWebTargets webTargets = new HDPowerViewWebTargets(client, hubIPAddress);
111 assertNotNull(webTargets);
114 ShadePosition shadePos = null;
115 Shades shadesX = null;
117 // ==== get all shades ====
119 shadesX = webTargets.getShades();
120 assertNotNull(shadesX);
121 List<ShadeData> shadesData = shadesX.shadeData;
122 assertNotNull(shadesData);
124 if (shadesData != null) {
125 assertTrue(!shadesData.isEmpty());
127 shadeData = shadesData.get(0);
128 assertNotNull(shadeData);
129 assertTrue(shadeData.getName().length() > 0);
130 shadePos = shadeData.positions;
131 assertNotNull(shadePos);
132 ShadeData shadeZero = shadesData.get(0);
133 assertNotNull(shadeZero);
134 shadeId = shadeZero.id;
135 assertNotEquals(0, shadeId);
137 for (ShadeData shadexData : shadesData) {
138 String shadeName = shadexData.getName();
139 assertNotNull(shadeName);
142 } catch (HubException e) {
143 fail(e.getMessage());
146 // ==== get all scenes ====
149 Scenes scenes = webTargets.getScenes();
150 assertNotNull(scenes);
152 List<Scene> scenesData = scenes.sceneData;
153 assertNotNull(scenesData);
155 if (scenesData != null) {
156 assertTrue(!scenesData.isEmpty());
157 Scene sceneZero = scenesData.get(0);
158 assertNotNull(sceneZero);
159 sceneId = sceneZero.id;
160 assertTrue(sceneId > 0);
162 for (Scene scene : scenesData) {
163 String sceneName = scene.getName();
164 assertNotNull(sceneName);
167 } catch (HubException e) {
168 fail(e.getMessage());
171 // ==== refresh a specific shade ====
172 ShadeData shadeData = null;
174 assertNotEquals(0, shadeId);
175 shadeData = webTargets.refreshShadePosition(shadeId);
176 } catch (HubException e) {
177 fail(e.getMessage());
180 // ==== move a specific shade ====
182 assertNotEquals(0, shadeId);
184 if (shadeData != null) {
185 ShadePosition positions = shadeData.positions;
186 assertNotNull(positions);
187 Integer capabilitiesValue = shadeData.capabilities;
188 assertNotNull(capabilitiesValue);
190 if (positions != null && capabilitiesValue != null) {
191 Capabilities capabilities = new ShadeCapabilitiesDatabase()
192 .getCapabilities(capabilitiesValue.intValue());
194 State pos = positions.getState(capabilities, PRIMARY_POSITION);
195 assertEquals(PercentType.class, pos.getClass());
197 int position = ((PercentType) pos).intValue();
198 position = position + ((position <= 10) ? 5 : -5);
200 ShadePosition targetPosition = new ShadePosition().setPosition(capabilities, PRIMARY_POSITION,
202 assertNotNull(targetPosition);
204 if (allowShadeMovementCommands) {
205 webTargets.moveShade(shadeId, targetPosition);
207 ShadeData newData = webTargets.getShade(shadeId);
208 ShadePosition actualPosition = newData.positions;
209 assertNotNull(actualPosition);
210 if (actualPosition != null) {
211 assertEquals(targetPosition.getState(capabilities, PRIMARY_POSITION),
212 actualPosition.getState(capabilities, PRIMARY_POSITION));
217 } catch (HubException e) {
218 fail(e.getMessage());
221 // ==== activate a specific scene ====
222 if (allowShadeMovementCommands) {
224 assertNotNull(sceneId);
225 webTargets.activateScene(sceneId);
226 } catch (HubProcessingException | HubMaintenanceException e) {
227 fail(e.getMessage());
231 // ==== test stop command ====
232 if (allowShadeMovementCommands) {
234 assertNotNull(sceneId);
235 webTargets.stopShade(shadeId);
236 } catch (HubException e) {
237 fail(e.getMessage());
241 // ==== stop the client ====
242 if (client.isRunning()) {
245 } catch (Exception e) {
246 fail(e.getMessage());
253 * Test generic JSON shades response.
256 public void shadeResponseIsParsedCorrectly() throws JsonParseException {
257 final Gson gson = new Gson();
259 String json = loadJson("shades");
260 assertNotEquals("", json);
261 shades = gson.fromJson(json, Shades.class);
262 assertNotNull(shades);
266 * Test generic JSON scene response.
269 public void sceneResponseIsParsedCorrectly() throws JsonParseException {
270 final Gson gson = new Gson();
271 String json = loadJson("scenes");
272 assertNotEquals("", json);
274 Scenes scenes = gson.fromJson(json, Scenes.class);
275 assertNotNull(scenes);
276 if (scenes != null) {
277 List<Scene> sceneData = scenes.sceneData;
278 assertNotNull(sceneData);
279 if (sceneData != null) {
280 assertEquals(4, sceneData.size());
281 Scene scene = sceneData.get(0);
282 assertEquals("Door Open", scene.getName());
283 assertEquals(18097, scene.id);
289 * Test generic JSON scene collection response.
292 public void sceneCollectionResponseIsParsedCorrectly() throws JsonParseException {
293 final Gson gson = new Gson();
294 String json = loadJson("sceneCollections");
295 assertNotEquals("", json);
297 SceneCollections sceneCollections = gson.fromJson(json, SceneCollections.class);
298 assertNotNull(sceneCollections);
300 if (sceneCollections != null) {
301 List<SceneCollection> sceneCollectionData = sceneCollections.sceneCollectionData;
302 assertNotNull(sceneCollectionData);
303 if (sceneCollectionData != null) {
304 assertEquals(1, sceneCollectionData.size());
306 SceneCollection sceneCollection = sceneCollectionData.get(0);
307 assertEquals("Børn op", sceneCollection.getName());
308 assertEquals(27119, sceneCollection.id);
314 * Test the JSON parsing for a duette top down bottom up shade.
317 public void duetteTopDownBottomUpShadeIsParsedCorrectly() throws JsonParseException {
318 final Gson gson = new Gson();
319 String json = loadJson("duette");
320 assertNotEquals("", json);
322 Shades shades = gson.fromJson(json, Shades.class);
323 assertNotNull(shades);
324 if (shades != null) {
325 List<ShadeData> shadesData = shades.shadeData;
326 assertNotNull(shadesData);
328 if (shadesData != null) {
329 assertEquals(1, shadesData.size());
330 ShadeData shadeData = shadesData.get(0);
331 assertNotNull(shadeData);
333 assertEquals("Gardin 1", shadeData.getName());
334 assertEquals(63778, shadeData.id);
336 ShadePosition shadePos = shadeData.positions;
337 assertNotNull(shadePos);
339 if (shadePos != null) {
340 Integer capabilitiesValue = shadeData.capabilities;
341 assertNotNull(capabilitiesValue);
342 if (capabilitiesValue != null) {
343 assertEquals(7, capabilitiesValue.intValue());
344 ShadeCapabilitiesDatabase db = new ShadeCapabilitiesDatabase();
345 Capabilities capabilities = db.getCapabilities(capabilitiesValue);
347 State pos = shadePos.getState(capabilities, PRIMARY_POSITION);
348 assertEquals(PercentType.class, pos.getClass());
349 assertEquals(59, ((PercentType) pos).intValue());
351 pos = shadePos.getState(capabilities, SECONDARY_POSITION);
352 assertEquals(PercentType.class, pos.getClass());
353 assertEquals(35, ((PercentType) pos).intValue());
355 pos = shadePos.getState(capabilities, VANE_TILT_POSITION);
356 assertEquals(UnDefType.class, pos.getClass());
358 assertEquals(3, shadeData.batteryStatus);
360 assertEquals(4, shadeData.signalStrength);
362 assertEquals(8, shadeData.type);
364 assertTrue(db.isTypeInDatabase(shadeData.type));
365 assertTrue(db.isCapabilitiesInDatabase(capabilitiesValue.intValue()));
367 assertEquals(db.getType(shadeData.type).getCapabilities(), capabilitiesValue.intValue());
369 assertTrue(db.getCapabilities(capabilitiesValue.intValue()).supportsSecondary());
370 assertNotEquals(db.getType(shadeData.type).getCapabilities(), capabilitiesValue.intValue() + 1);
372 // ==== when changing position1, position2 value is not changed (vice-versa) ====
373 ShadePosition shadePosition = shadeData.positions;
374 assertNotNull(shadePosition);
375 if (shadePosition != null) {
376 // ==== position2 ====
377 State position2Old = shadePosition.getState(capabilities, SECONDARY_POSITION);
378 shadePosition.setPosition(capabilities, PRIMARY_POSITION, 99);
379 State position2New = shadePosition.getState(capabilities, SECONDARY_POSITION);
380 assertEquals(PercentType.class, position2Old.getClass());
381 assertEquals(PercentType.class, position2New.getClass());
382 assertEquals(((PercentType) position2Old).intValue(),
383 ((PercentType) position2New).intValue());
385 // ==== position2 ====
386 State position1Old = shadePosition.getState(capabilities, PRIMARY_POSITION);
387 shadePosition.setPosition(capabilities, SECONDARY_POSITION, 99);
388 State position1New = shadePosition.getState(capabilities, PRIMARY_POSITION);
389 assertEquals(PercentType.class, position1Old.getClass());
390 assertEquals(PercentType.class, position1New.getClass());
391 assertEquals(((PercentType) position1Old).intValue(),
392 ((PercentType) position1New).intValue());