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.ActuatorClass.*;
17 import static org.openhab.binding.hdpowerview.internal.api.CoordinateSystem.*;
19 import java.io.BufferedReader;
20 import java.io.FileReader;
21 import java.io.IOException;
22 import java.util.List;
23 import java.util.regex.Pattern;
25 import org.eclipse.jdt.annotation.NonNullByDefault;
26 import org.eclipse.jdt.annotation.Nullable;
27 import org.eclipse.jetty.client.HttpClient;
28 import org.junit.jupiter.api.Test;
29 import org.openhab.binding.hdpowerview.internal.HDPowerViewWebTargets;
30 import org.openhab.binding.hdpowerview.internal.HubMaintenanceException;
31 import org.openhab.binding.hdpowerview.internal.HubProcessingException;
32 import org.openhab.binding.hdpowerview.internal.api.CoordinateSystem;
33 import org.openhab.binding.hdpowerview.internal.api.ShadePosition;
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.core.library.types.PercentType;
40 import org.openhab.core.types.State;
41 import org.openhab.core.types.UnDefType;
43 import com.google.gson.Gson;
44 import com.google.gson.JsonParseException;
47 * Unit tests for HD PowerView binding
49 * @author Andrew Fiddian-Green - Initial contribution
52 public class HDPowerViewJUnitTests {
54 private static final Pattern VALID_IP_V4_ADDRESS = Pattern
55 .compile("\\b((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\\.|$)){4}\\b");
58 * load a test JSON string from a file
60 private String loadJson(String fileName) {
61 try (FileReader file = new FileReader(String.format("src/test/resources/%s.json", fileName));
62 BufferedReader reader = new BufferedReader(file)) {
63 StringBuilder builder = new StringBuilder();
65 while ((line = reader.readLine()) != null) {
66 builder.append(line).append("\n");
68 return builder.toString();
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);
115 // ==== exercise some code ====
120 test = ShadePosition.create(ZERO_IS_CLOSED, 0);
122 pos = test.getState(PRIMARY_ACTUATOR, ZERO_IS_CLOSED);
123 assertEquals(PercentType.class, pos.getClass());
124 assertEquals(0, ((PercentType) pos).intValue());
125 pos = test.getState(PRIMARY_ACTUATOR, VANE_COORDS);
126 assertTrue(UnDefType.UNDEF.equals(pos));
128 // shade fully down (method 1)
129 test = ShadePosition.create(ZERO_IS_CLOSED, 100);
131 pos = test.getState(PRIMARY_ACTUATOR, ZERO_IS_CLOSED);
132 assertEquals(PercentType.class, pos.getClass());
133 assertEquals(100, ((PercentType) pos).intValue());
134 pos = test.getState(PRIMARY_ACTUATOR, VANE_COORDS);
135 assertEquals(PercentType.class, pos.getClass());
136 assertEquals(0, ((PercentType) pos).intValue());
138 // shade fully down (method 2)
139 test = ShadePosition.create(VANE_COORDS, 0);
141 pos = test.getState(PRIMARY_ACTUATOR, ZERO_IS_CLOSED);
142 assertEquals(PercentType.class, pos.getClass());
143 assertEquals(100, ((PercentType) pos).intValue());
144 pos = test.getState(PRIMARY_ACTUATOR, VANE_COORDS);
145 assertEquals(PercentType.class, pos.getClass());
146 assertEquals(0, ((PercentType) pos).intValue());
148 // shade fully down (method 2) and vane fully open
149 test = ShadePosition.create(VANE_COORDS, 100);
151 pos = test.getState(PRIMARY_ACTUATOR, ZERO_IS_CLOSED);
152 assertEquals(PercentType.class, pos.getClass());
153 assertEquals(100, ((PercentType) pos).intValue());
154 pos = test.getState(PRIMARY_ACTUATOR, VANE_COORDS);
155 assertEquals(PercentType.class, pos.getClass());
156 assertEquals(100, ((PercentType) pos).intValue());
160 ShadePosition shadePos = null;
162 Shades shadesX = null;
164 // ==== get all shades ====
166 shadesX = webTargets.getShades();
167 assertNotNull(shadesX);
169 List<ShadeData> shadesData = shadesX.shadeData;
170 assertNotNull(shadesData);
171 assertTrue(!shadesData.isEmpty());
174 shadeData = shadesData.get(0);
175 assertNotNull(shadeData);
176 assertTrue(shadeData.getName().length() > 0);
177 shadePos = shadeData.positions;
178 assertNotNull(shadePos);
180 ShadeData shadeZero = shadesData.get(0);
181 assertNotNull(shadeZero);
182 shadeId = shadeZero.id;
183 assertNotEquals(0, shadeId);
185 for (ShadeData shadexData : shadesData) {
186 String shadeName = shadexData.getName();
187 assertNotNull(shadeName);
189 } catch (JsonParseException | HubProcessingException | HubMaintenanceException e) {
190 fail(e.getMessage());
193 // ==== get all scenes ====
196 Scenes scenes = webTargets.getScenes();
197 assertNotNull(scenes);
199 List<Scene> scenesData = scenes.sceneData;
200 assertNotNull(scenesData);
201 assertTrue(!scenesData.isEmpty());
203 Scene sceneZero = scenesData.get(0);
204 assertNotNull(sceneZero);
205 sceneId = sceneZero.id;
206 assertTrue(sceneId > 0);
208 for (Scene scene : scenesData) {
209 String sceneName = scene.getName();
210 assertNotNull(sceneName);
212 } catch (JsonParseException | HubProcessingException | HubMaintenanceException e) {
213 fail(e.getMessage());
216 // ==== refresh a specific shade ====
220 assertNotEquals(0, shadeId);
221 shade = webTargets.refreshShadePosition(shadeId);
222 assertNotNull(shade);
223 } catch (HubProcessingException | HubMaintenanceException e) {
224 fail(e.getMessage());
227 // ==== move a specific shade ====
229 assertNotEquals(0, shadeId);
230 assertNotNull(shade);
232 ShadeData shadeData = shade.shade;
233 assertNotNull(shadeData);
234 ShadePosition positions = shadeData.positions;
235 assertNotNull(positions);
236 CoordinateSystem coordSys = positions.getCoordinateSystem(PRIMARY_ACTUATOR);
237 assertNotNull(coordSys);
239 pos = positions.getState(PRIMARY_ACTUATOR, coordSys);
240 assertEquals(PercentType.class, pos.getClass());
242 pos = positions.getState(PRIMARY_ACTUATOR, ZERO_IS_CLOSED);
243 assertEquals(PercentType.class, pos.getClass());
245 int position = ((PercentType) pos).intValue();
246 position = position + ((position <= 10) ? 5 : -5);
248 ShadePosition newPos = ShadePosition.create(ZERO_IS_CLOSED, position);
249 assertNotNull(newPos);
251 if (allowShadeMovementCommands) {
252 webTargets.moveShade(shadeId, newPos);
254 } catch (HubProcessingException | HubMaintenanceException e) {
255 fail(e.getMessage());
258 // ==== activate a specific scene ====
259 if (allowShadeMovementCommands) {
261 assertNotNull(sceneId);
262 webTargets.activateScene(sceneId);
263 } catch (HubProcessingException | HubMaintenanceException e) {
264 fail(e.getMessage());
268 // ==== test stop command ====
269 if (allowShadeMovementCommands) {
271 assertNotNull(sceneId);
272 webTargets.stopShade(shadeId);
273 } catch (HubProcessingException | HubMaintenanceException e) {
274 fail(e.getMessage());
278 // ==== stop the client ====
279 if (client.isRunning()) {
282 } catch (Exception e) {
283 fail(e.getMessage());
290 * Run a series of OFFLINE tests on the JSON parsing machinery
293 public void testOfflineJsonParsing() {
294 final Gson gson = new Gson();
298 // test generic JSON shades response
301 String json = loadJson("shades");
303 assertNotEquals("", json);
304 shades = gson.fromJson(json, Shades.class);
305 assertNotNull(shades);
306 } catch (JsonParseException e) {
307 fail(e.getMessage());
310 // test generic JSON scenes response
313 String json = loadJson("scenes");
315 assertNotEquals("", json);
317 Scenes scenes = gson.fromJson(json, Scenes.class);
318 assertNotNull(scenes);
319 } catch (JsonParseException e) {
320 fail(e.getMessage());
323 // test the JSON parsing for a duette top down bottom up shade
326 ShadeData shadeData = null;
327 String json = loadJson("duette");
329 assertNotEquals("", json);
331 shades = gson.fromJson(json, Shades.class);
332 assertNotNull(shades);
334 List<ShadeData> shadesData = shades.shadeData;
335 assertNotNull(shadesData);
337 assertEquals(1, shadesData.size());
338 shadeData = shadesData.get(0);
339 assertNotNull(shadeData);
341 assertEquals("Gardin 1", shadeData.getName());
342 assertEquals(63778, shadeData.id);
344 ShadePosition shadePos = shadeData.positions;
345 assertNotNull(shadePos);
346 assertEquals(ZERO_IS_CLOSED, shadePos.getCoordinateSystem(PRIMARY_ACTUATOR));
348 State pos = shadePos.getState(PRIMARY_ACTUATOR, ZERO_IS_CLOSED);
349 assertEquals(PercentType.class, pos.getClass());
350 assertEquals(59, ((PercentType) pos).intValue());
352 pos = shadePos.getState(SECONDARY_ACTUATOR, ZERO_IS_OPEN);
353 assertEquals(PercentType.class, pos.getClass());
354 assertEquals(65, ((PercentType) pos).intValue());
356 pos = shadePos.getState(PRIMARY_ACTUATOR, VANE_COORDS);
357 assertEquals(UnDefType.class, pos.getClass());
359 assertEquals(3, shadeData.batteryStatus);
361 assertEquals(4, shadeData.signalStrength);
362 } catch (JsonParseException e) {
363 fail(e.getMessage());