]> git.basschouten.com Git - openhab-addons.git/blob
6efce926347c3028dc35fe3aa95aac25e5449310
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 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.velux.test;
14
15 import static org.junit.jupiter.api.Assertions.*;
16
17 import org.eclipse.jdt.annotation.NonNullByDefault;
18 import org.junit.jupiter.api.MethodOrderer;
19 import org.junit.jupiter.api.Order;
20 import org.junit.jupiter.api.Test;
21 import org.junit.jupiter.api.TestMethodOrder;
22 import org.openhab.binding.velux.internal.bridge.slip.FunctionalParameters;
23 import org.openhab.binding.velux.internal.bridge.slip.SCgetHouseStatus;
24 import org.openhab.binding.velux.internal.bridge.slip.SCgetProduct;
25 import org.openhab.binding.velux.internal.bridge.slip.SCgetProductStatus;
26 import org.openhab.binding.velux.internal.bridge.slip.SCrunProductCommand;
27 import org.openhab.binding.velux.internal.things.VeluxExistingProducts;
28 import org.openhab.binding.velux.internal.things.VeluxKLFAPI;
29 import org.openhab.binding.velux.internal.things.VeluxKLFAPI.Command;
30 import org.openhab.binding.velux.internal.things.VeluxProduct;
31 import org.openhab.binding.velux.internal.things.VeluxProduct.ProductBridgeIndex;
32 import org.openhab.binding.velux.internal.things.VeluxProduct.ProductState;
33 import org.openhab.binding.velux.internal.things.VeluxProductName;
34 import org.openhab.binding.velux.internal.things.VeluxProductPosition;
35 import org.openhab.binding.velux.internal.things.VeluxProductPosition.PositionType;
36 import org.openhab.binding.velux.internal.things.VeluxProductType.ActuatorType;
37 import org.openhab.core.library.types.PercentType;
38
39 /**
40  * JUnit test suite to check the proper parsing of actuator notification packets, and to confirm that the existing
41  * products database is working correctly.
42  *
43  * @author Andrew Fiddian-Green - Initial contribution.
44  */
45 @NonNullByDefault
46 @TestMethodOrder(MethodOrderer.OrderAnnotation.class)
47 public class TestNotificationsAndDatabase {
48     // validation parameters
49     private static final byte PRODUCT_INDEX_A = 6;
50     private static final byte PRODUCT_INDEX_B = 0;
51     private static final int MAIN_POSITION_A = 0xC800;
52     private static final int MAIN_POSITION_B = 0x4600;
53     private static final int VANE_POSITION_A = 0x634f;
54     private static final int TARGET_POSITION = 0xB800;
55     private static final int STATE_SOMFY = 0x2D;
56
57     private static final int UNKNOWN_POSITION = VeluxProductPosition.VPP_VELUX_UNKNOWN;
58     private static final int IGNORE_POSITION = VeluxProductPosition.VPP_VELUX_IGNORE;
59     private static final int STATE_DONE = VeluxProduct.ProductState.DONE.value;
60
61     private static final ActuatorType ACTUATOR_TYPE_SOMFY = ActuatorType.BLIND_17;
62     private static final ActuatorType ACTUATOR_TYPE_VELUX = ActuatorType.WINDOW_4_1;
63     private static final ActuatorType ACTUATOR_TYPE_UNDEF = ActuatorType.UNDEFTYPE;
64
65     // existing products database
66     private static final VeluxExistingProducts EXISTING_PRODUCTS = new VeluxExistingProducts();
67
68     private static byte[] toByteArray(String input) {
69         String[] data = input.split(" ");
70         byte[] result = new byte[data.length];
71         for (int i = 0; i < data.length; i++) {
72             result[i] = Integer.decode("0x" + data[i]).byteValue();
73         }
74         return result;
75     }
76
77     private VeluxExistingProducts getExistingProducts() {
78         return EXISTING_PRODUCTS;
79     }
80
81     /**
82      * Confirm the existing products database is initialised.
83      */
84     @Test
85     @Order(1)
86     public void testInitialized() {
87         assertEquals(0, getExistingProducts().getNoMembers());
88     }
89
90     /**
91      * Test the 'supportsVanePosition()' method for two types of products.
92      */
93     @Test
94     @Order(2)
95     public void testSupportsVanePosition() {
96         VeluxProduct product = new VeluxProduct();
97         assertFalse(product.supportsVanePosition());
98         product.setActuatorType(ACTUATOR_TYPE_SOMFY);
99         assertTrue(product.supportsVanePosition());
100     }
101
102     /**
103      * Test the SCgetProduct command by checking for the correct parsing of a 'GW_GET_NODE_INFORMATION_NTF' notification
104      * packet. Note: this packet is from a Somfy roller shutter with main and vane position.
105      */
106     @Test
107     @Order(3)
108     public void testSCgetProduct() {
109         // initialise the test parameters
110         final String packet = "06 00 06 00 48 6F 62 62 79 6B 61 6D 65 72 00 00 00 00 00 00 00 00 00 00 00 00 00 00"
111                 + " 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00"
112                 + " 00 00 00 00 00 01 04 40 00 00 00 00 00 00 00 00 00 00 00 00 00 2D C8 00 C8 00 F7 FF F7 FF 00 00 F7 FF 00"
113                 + " 00 4F 00 4A EA 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00";
114         final Command command = VeluxKLFAPI.Command.GW_GET_NODE_INFORMATION_NTF;
115
116         // initialise the BCP
117         SCgetProduct bcp = new SCgetProduct();
118         bcp.setProductId(PRODUCT_INDEX_A);
119
120         // set the packet response
121         bcp.setResponse(command.getShort(), toByteArray(packet), false);
122
123         // check BCP status
124         assertTrue(bcp.isCommunicationSuccessful());
125         assertTrue(bcp.isCommunicationFinished());
126
127         // initialise the product
128         VeluxProduct product = bcp.getProduct();
129
130         // check positive assertions
131         assertEquals(STATE_SOMFY, product.getState());
132         assertEquals(MAIN_POSITION_A, product.getCurrentPosition());
133         assertEquals(MAIN_POSITION_A, product.getTarget());
134         assertEquals(PRODUCT_INDEX_A, product.getBridgeProductIndex().toInt());
135         assertEquals(ACTUATOR_TYPE_SOMFY, product.getActuatorType());
136         assertEquals(UNKNOWN_POSITION, product.getVanePosition());
137         assertNull(product.getFunctionalParameters());
138         assertTrue(product.supportsVanePosition());
139         assertTrue(product.isSomfyProduct());
140         assertEquals(ProductState.DONE, product.getProductState());
141
142         // check negative assertions
143         assertNotEquals(VANE_POSITION_A, product.getVanePosition());
144
145         // register in existing products database
146         VeluxExistingProducts existingProducts = getExistingProducts();
147         assertTrue(existingProducts.register(product));
148         assertTrue(existingProducts.isRegistered(product));
149         assertTrue(existingProducts.isRegistered(product.getBridgeProductIndex()));
150         assertEquals(1, existingProducts.getNoMembers());
151
152         // confirm that a dummy product is NOT in the database
153         assertFalse(existingProducts.isRegistered(new ProductBridgeIndex(99)));
154
155         // check dirty flag
156         assertTrue(existingProducts.isDirty());
157         existingProducts.resetDirtyFlag();
158         assertFalse(existingProducts.isDirty());
159
160         // re-registering the same product should return false
161         assertFalse(existingProducts.register(product));
162
163         // updating again with the same data should NOT set the dirty flag
164         assertTrue(existingProducts.update(product));
165         assertFalse(existingProducts.isDirty());
166
167         // check that the product in the database is indeed the one just created
168         VeluxProduct existing = existingProducts.get(new ProductBridgeIndex(PRODUCT_INDEX_A));
169         assertEquals(product, existing);
170         assertEquals(1, existingProducts.getNoMembers());
171         assertTrue(existingProducts.isRegistered(product.getBridgeProductIndex()));
172     }
173
174     /**
175      * Confirm that the product in the existing database has the same values as the product created and added in test 3.
176      */
177     @Test
178     @Order(4)
179     public void testExistingUnknownVanePosition() {
180         VeluxExistingProducts existingProducts = getExistingProducts();
181         VeluxProduct product = existingProducts.get(new ProductBridgeIndex(PRODUCT_INDEX_A));
182
183         // confirm the product details
184         assertEquals(STATE_SOMFY, product.getState());
185         assertEquals(MAIN_POSITION_A, product.getCurrentPosition());
186         assertEquals(MAIN_POSITION_A, product.getTarget());
187         assertEquals(PRODUCT_INDEX_A, product.getBridgeProductIndex().toInt());
188         assertEquals(ACTUATOR_TYPE_SOMFY, product.getActuatorType());
189         assertEquals(UNKNOWN_POSITION, product.getVanePosition());
190         assertNull(product.getFunctionalParameters());
191     }
192
193     /**
194      * Test the SCgetProductStatus command by checking for the correct parsing of a 'GW_STATUS_REQUEST_NTF' notification
195      * packet. Note: this packet is from a Somfy roller shutter with main and vane position.
196      */
197     @Test
198     @Order(5)
199     public void testSCgetProductStatus() {
200         // initialise the test parameters
201         final String packet = "00 D8 01 06 00 01 01 02 00 C8 00 03 63 4F 00 00 00 00 00 00 00 00 00 00 00 00 00"
202                 + " 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00";
203         final Command command = VeluxKLFAPI.Command.GW_STATUS_REQUEST_NTF;
204
205         // initialise the BCP
206         SCgetProductStatus bcp = new SCgetProductStatus();
207         bcp.setProductId(PRODUCT_INDEX_A);
208
209         // set the packet response
210         bcp.setResponse(command.getShort(), toByteArray(packet), false);
211
212         // check BCP status
213         assertTrue(bcp.isCommunicationSuccessful());
214         assertTrue(bcp.isCommunicationFinished());
215
216         // initialise the product
217         VeluxProduct product = bcp.getProduct();
218
219         // change actuator type
220         assertEquals(ACTUATOR_TYPE_UNDEF, product.getActuatorType());
221         product.setActuatorType(ACTUATOR_TYPE_SOMFY);
222
223         // check positive assertions
224         assertEquals(STATE_DONE, product.getState());
225         assertEquals(MAIN_POSITION_A, product.getCurrentPosition());
226         assertEquals(IGNORE_POSITION, product.getTarget());
227         assertEquals(PRODUCT_INDEX_A, product.getBridgeProductIndex().toInt());
228         assertEquals(ACTUATOR_TYPE_SOMFY, product.getActuatorType());
229         assertEquals(VANE_POSITION_A, product.getVanePosition());
230         assertNotNull(product.getFunctionalParameters());
231         assertEquals(ProductState.DONE, product.getProductState());
232
233         // test updating the existing product in the database
234         VeluxExistingProducts existingProducts = getExistingProducts();
235         assertTrue(existingProducts.update(product));
236         assertTrue(existingProducts.isDirty());
237
238         // updating again with the same data should NOT set the dirty flag
239         existingProducts.resetDirtyFlag();
240         assertTrue(existingProducts.update(product));
241         assertFalse(existingProducts.isDirty());
242     }
243
244     /**
245      * Confirm that the product in the existing database has the same values as the product created and updated to the
246      * database in test 5.
247      */
248     @Test
249     @Order(6)
250     public void testExistingValidVanePosition() {
251         VeluxExistingProducts existingProducts = getExistingProducts();
252         VeluxProduct product = existingProducts.get(new ProductBridgeIndex(PRODUCT_INDEX_A));
253
254         // confirm the product details
255         assertEquals(STATE_DONE, product.getState());
256         assertEquals(MAIN_POSITION_A, product.getCurrentPosition());
257         assertEquals(MAIN_POSITION_A, product.getTarget());
258         assertEquals(PRODUCT_INDEX_A, product.getBridgeProductIndex().toInt());
259         assertEquals(ACTUATOR_TYPE_SOMFY, product.getActuatorType());
260         assertEquals(VANE_POSITION_A, product.getVanePosition());
261         assertNotNull(product.getFunctionalParameters());
262         assertEquals(ProductState.DONE, product.getProductState());
263     }
264
265     /**
266      * Test the SCgetHouseStatus command by checking for the correct parsing of a 'GW_NODE_STATE_POSITION_CHANGED_NTF'
267      * notification packet. Note: this packet is from a Somfy roller shutter with main and vane position.
268      */
269     @Test
270     @Order(7)
271     public void testSCgetHouseStatus() {
272         // initialise the test parameters
273         final String packet = "06 2D C8 00 B8 00 F7 FF F7 FF 00 00 F7 FF 00 00 4A E5 00 00";
274         final short command = VeluxKLFAPI.Command.GW_NODE_STATE_POSITION_CHANGED_NTF.getShort();
275
276         // initialise the BCP
277         SCgetHouseStatus bcp = new SCgetHouseStatus();
278
279         // set the packet response
280         bcp.setResponse(command, toByteArray(packet), false);
281
282         // check BCP status
283         assertTrue(bcp.isCommunicationSuccessful());
284         assertTrue(bcp.isCommunicationFinished());
285
286         // initialise the product
287         VeluxProduct product = bcp.getProduct();
288
289         // change actuator type
290         assertEquals(ACTUATOR_TYPE_UNDEF, product.getActuatorType());
291         product.setActuatorType(ACTUATOR_TYPE_SOMFY);
292
293         // check positive assertions
294         assertEquals(STATE_SOMFY, product.getState());
295         assertEquals(MAIN_POSITION_A, product.getCurrentPosition());
296         assertEquals(TARGET_POSITION, product.getTarget());
297         assertEquals(PRODUCT_INDEX_A, product.getBridgeProductIndex().toInt());
298         assertEquals(ACTUATOR_TYPE_SOMFY, product.getActuatorType());
299         assertEquals(UNKNOWN_POSITION, product.getVanePosition());
300         assertNull(product.getFunctionalParameters());
301
302         // check negative assertions
303         assertNotEquals(VANE_POSITION_A, product.getVanePosition());
304
305         VeluxExistingProducts existingProducts = getExistingProducts();
306         existingProducts.update(product);
307     }
308
309     /**
310      * Confirm that the product in the existing database has the same values as the product created and updated to the
311      * database in test 7.
312      */
313     @Test
314     @Order(8)
315     public void testExistingValidVanePositionWithNewTargetValue() {
316         VeluxExistingProducts existingProducts = getExistingProducts();
317         VeluxProduct product = existingProducts.get(new ProductBridgeIndex(PRODUCT_INDEX_A));
318
319         // confirm the product details
320         assertEquals(STATE_SOMFY, product.getState());
321         assertEquals(MAIN_POSITION_A, product.getCurrentPosition());
322         assertEquals(TARGET_POSITION, product.getTarget());
323         assertEquals(PRODUCT_INDEX_A, product.getBridgeProductIndex().toInt());
324         assertEquals(ACTUATOR_TYPE_SOMFY, product.getActuatorType());
325         assertEquals(VANE_POSITION_A, product.getVanePosition());
326         assertNotNull(product.getFunctionalParameters());
327     }
328
329     /**
330      * Test the SCgetProduct by checking for the correct parsing of a 'GW_GET_NODE_INFORMATION_NTF' notification packet.
331      * Note: this packet is from a Velux roof window without vane position.
332      */
333     @Test
334     @Order(9)
335     public void testSCgetProductOnVelux() {
336         // initialise the test parameters
337         final String packet = "00 00 00 00 53 68 65 64 20 57 69 6E 64 6F 77 00 00 00 00 00 00 00 00 00 00 00 00 00"
338                 + " 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00"
339                 + " 00 00 00 00 00 00 00 00 01 01 01 03 07 00 01 16 56 24 5C 26 14 19 00 FC 05 46 00 46 00 F7 FF F7"
340                 + " FF F7 FF F7 FF 00 00 4F 05 B3 5F 01 D8 03 B2 1C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00";
341         final short command = VeluxKLFAPI.Command.GW_GET_NODE_INFORMATION_NTF.getShort();
342
343         // initialise the BCP
344         SCgetProduct bcp = new SCgetProduct();
345         bcp.setProductId(PRODUCT_INDEX_B);
346
347         // set the packet response
348         bcp.setResponse(command, toByteArray(packet), false);
349
350         // check BCP status
351         assertTrue(bcp.isCommunicationSuccessful());
352         assertTrue(bcp.isCommunicationFinished());
353
354         // initialise the product
355         VeluxProduct product = bcp.getProduct();
356
357         // check positive assertions
358         assertEquals(STATE_DONE, product.getState());
359         assertEquals(MAIN_POSITION_B, product.getCurrentPosition());
360         assertEquals(MAIN_POSITION_B, product.getTarget());
361         assertEquals(PRODUCT_INDEX_B, product.getBridgeProductIndex().toInt());
362         assertEquals(ACTUATOR_TYPE_VELUX, product.getActuatorType());
363         assertEquals(UNKNOWN_POSITION, product.getVanePosition());
364         assertNull(product.getFunctionalParameters());
365         assertFalse(product.isSomfyProduct());
366
367         // check negative assertions
368         assertFalse(product.supportsVanePosition());
369         assertNotEquals(VANE_POSITION_A, product.getVanePosition());
370
371         // register in existing products database
372         VeluxExistingProducts existingProducts = getExistingProducts();
373         assertTrue(existingProducts.register(product));
374         assertTrue(existingProducts.isRegistered(product));
375         assertTrue(existingProducts.isRegistered(product.getBridgeProductIndex()));
376         assertEquals(2, existingProducts.getNoMembers());
377
378         // check that the product in the database is indeed the one just created
379         VeluxProduct existing = existingProducts.get(new ProductBridgeIndex(PRODUCT_INDEX_B));
380         assertEquals(product, existing);
381         assertTrue(existingProducts.isRegistered(product.getBridgeProductIndex()));
382     }
383
384     /**
385      * Confirm that the modified products list is functioning.
386      */
387     @Test
388     @Order(10)
389     public void testModifiedList() {
390         VeluxExistingProducts existingProducts = getExistingProducts();
391         VeluxProduct[] modified = existingProducts.valuesOfModified();
392
393         // confirm that the list contains two entries
394         assertEquals(2, modified.length);
395
396         // confirm the product details for the Somfy product
397         VeluxProduct product = modified[0];
398         assertEquals(STATE_SOMFY, product.getState());
399         assertEquals(MAIN_POSITION_A, product.getCurrentPosition());
400         assertEquals(TARGET_POSITION, product.getTarget());
401         assertEquals(PRODUCT_INDEX_A, product.getBridgeProductIndex().toInt());
402         assertEquals(ACTUATOR_TYPE_SOMFY, product.getActuatorType());
403         assertEquals(VANE_POSITION_A, product.getVanePosition());
404         assertTrue(product.isSomfyProduct());
405         assertNotNull(product.getFunctionalParameters());
406
407         // confirm the product details for the Velux product
408         product = modified[1];
409         assertEquals(STATE_DONE, product.getState());
410         assertEquals(MAIN_POSITION_B, product.getCurrentPosition());
411         assertEquals(MAIN_POSITION_B, product.getTarget());
412         assertEquals(PRODUCT_INDEX_B, product.getBridgeProductIndex().toInt());
413         assertEquals(ACTUATOR_TYPE_VELUX, product.getActuatorType());
414         assertEquals(UNKNOWN_POSITION, product.getVanePosition());
415         assertNull(product.getFunctionalParameters());
416         assertFalse(product.isSomfyProduct());
417
418         // reset the dirty flag
419         existingProducts.resetDirtyFlag();
420         assertFalse(existingProducts.isDirty());
421
422         // confirm modified list is now empty again
423         modified = existingProducts.valuesOfModified();
424         assertEquals(0, modified.length);
425     }
426
427     /**
428      * Test actuator type setting.
429      */
430     @Test
431     @Order(11)
432     public void testActuatorTypeSetting() {
433         VeluxProduct product = new VeluxProduct();
434         assertEquals(ACTUATOR_TYPE_UNDEF, product.getActuatorType());
435
436         // set actuator type
437         product.setActuatorType(ACTUATOR_TYPE_SOMFY);
438         assertEquals(ACTUATOR_TYPE_SOMFY, product.getActuatorType());
439
440         // try to set it again
441         product.setActuatorType(ACTUATOR_TYPE_VELUX);
442         assertNotEquals(ACTUATOR_TYPE_VELUX, product.getActuatorType());
443
444         // try with a clean product
445         product = new VeluxProduct();
446         product.setActuatorType(ACTUATOR_TYPE_VELUX);
447         assertEquals(ACTUATOR_TYPE_VELUX, product.getActuatorType());
448     }
449
450     /**
451      * Test the SCrunProduct command by creating packet for a given main position and vane position, and checking the
452      * created packet is as expected.
453      */
454     @Test
455     @Order(12)
456     public void testSCrunProductA() {
457         final String expectedString = "02 1C 08 05 00 20 00 90 00 00 00 00 00 A0 00 00 00 00 00 00 00 00 00 00 00"
458                 + " 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 06 00 00 00 00 00 00 00 00 00 00 00 00 00 00"
459                 + " 00 00 00 00 00 00 00 00 00";
460         final byte[] expectedPacket = toByteArray(expectedString);
461         final int targetMainPosition = 0x9000;
462         final int targetVanePosition = 0xA000;
463
464         // initialise the product to be commanded
465         VeluxProduct product = new VeluxProduct(VeluxProductName.UNKNOWN, new ProductBridgeIndex(PRODUCT_INDEX_A), 0, 0,
466                 0, null, Command.UNDEFTYPE);
467         product.setActuatorType(ACTUATOR_TYPE_SOMFY);
468         product.setCurrentPosition(targetMainPosition);
469         product.setVanePosition(targetVanePosition);
470
471         // create the run product command, and initialise it from the test product's state values
472         SCrunProductCommand bcp = new SCrunProductCommand();
473         bcp.setNodeIdAndParameters(product.getBridgeProductIndex().toInt(),
474                 new VeluxProductPosition(product.getCurrentPosition()), product.getFunctionalParameters());
475
476         // get the resulting data packet
477         byte[] actualPacket = bcp.getRequestDataAsArrayOfBytes();
478
479         // check the packet lengths are the same
480         assertEquals(expectedPacket.length, actualPacket.length);
481
482         // check the packet contents are identical (note start at i = 2 because session id won't match)
483         boolean identical = true;
484         for (int i = 2; i < expectedPacket.length; i++) {
485             if (actualPacket[i] != expectedPacket[i]) {
486                 identical = false;
487             }
488         }
489         assertTrue(identical);
490
491         // check the resulting updater product state is 'executing' with the new values
492         product = bcp.getProduct();
493         product.setActuatorType(ACTUATOR_TYPE_SOMFY);
494         assertEquals(ProductState.EXECUTING, product.getProductState());
495         assertEquals(targetMainPosition, product.getCurrentPosition());
496         assertEquals(targetMainPosition, product.getTarget());
497         assertEquals(targetMainPosition, product.getDisplayPosition());
498         assertEquals(targetVanePosition, product.getVanePosition());
499         assertEquals(targetVanePosition, product.getVaneDisplayPosition());
500     }
501
502     /**
503      * Test the SCrunProduct command by creating a packet with some bad values, and checking the product is as expected.
504      */
505     @Test
506     @Order(12)
507     public void testSCrunProductB() {
508         SCrunProductCommand bcp = new SCrunProductCommand();
509
510         final int errorMainPosition = 0xffff;
511         VeluxProduct product = new VeluxProduct(VeluxProductName.UNKNOWN, new ProductBridgeIndex(PRODUCT_INDEX_A), 0, 0,
512                 0, null, Command.UNDEFTYPE);
513         product.setActuatorType(ACTUATOR_TYPE_SOMFY);
514         product.setCurrentPosition(errorMainPosition);
515
516         bcp.setNodeIdAndParameters(product.getBridgeProductIndex().toInt(),
517                 new VeluxProductPosition(product.getCurrentPosition()), product.getFunctionalParameters());
518         product = bcp.getProduct();
519         product.setActuatorType(ACTUATOR_TYPE_SOMFY);
520
521         assertEquals(ProductState.EXECUTING, product.getProductState());
522         assertEquals(IGNORE_POSITION, product.getCurrentPosition());
523         assertEquals(IGNORE_POSITION, product.getTarget());
524         assertEquals(UNKNOWN_POSITION, product.getDisplayPosition());
525         assertEquals(UNKNOWN_POSITION, product.getVanePosition());
526         assertEquals(UNKNOWN_POSITION, product.getVaneDisplayPosition());
527     }
528
529     /**
530      * Test the actuator state.
531      */
532     @Test
533     @Order(14)
534     public void testActuatorState() {
535         VeluxProduct product = new VeluxProduct(VeluxProductName.UNKNOWN, new ProductBridgeIndex(PRODUCT_INDEX_A), 0, 0,
536                 0, null, Command.UNDEFTYPE);
537         int[] inputStates = { 0, 1, 2, 3, 4, 5, 0x2c, 0x2d, 0xff, 0x80 };
538         ProductState[] expected = { ProductState.NON_EXECUTING, ProductState.ERROR, ProductState.NOT_USED,
539                 ProductState.WAITING_FOR_POWER, ProductState.EXECUTING, ProductState.DONE, ProductState.EXECUTING,
540                 ProductState.DONE, ProductState.UNKNOWN, ProductState.MANUAL };
541         for (int i = 0; i < inputStates.length; i++) {
542             product.setState(inputStates[i]);
543             assertEquals(expected[i], product.getProductState());
544         }
545     }
546
547     /**
548      * Test actuator positions and display positions.
549      */
550     @Test
551     @Order(15)
552     public void testActuatorPositions() {
553         VeluxProduct product = new VeluxProduct(VeluxProductName.UNKNOWN, new ProductBridgeIndex(PRODUCT_INDEX_A), 0, 0,
554                 0, null, Command.UNDEFTYPE);
555
556         product.setCurrentPosition(MAIN_POSITION_A);
557         product.setTarget(TARGET_POSITION);
558         product.setActuatorType(ACTUATOR_TYPE_SOMFY);
559         product.setVanePosition(VANE_POSITION_A);
560
561         // state uninitialised
562         assertEquals(MAIN_POSITION_A, product.getCurrentPosition());
563         assertEquals(MAIN_POSITION_A, product.getDisplayPosition());
564         assertEquals(TARGET_POSITION, product.getTarget());
565         assertEquals(VANE_POSITION_A, product.getVaneDisplayPosition());
566         assertEquals(VANE_POSITION_A, product.getVanePosition());
567
568         // state = done
569         product.setState(ProductState.DONE.value);
570         assertEquals(MAIN_POSITION_A, product.getDisplayPosition());
571         assertEquals(MAIN_POSITION_A, product.getCurrentPosition());
572         assertEquals(TARGET_POSITION, product.getTarget());
573         assertEquals(VANE_POSITION_A, product.getVaneDisplayPosition());
574         assertEquals(VANE_POSITION_A, product.getVanePosition());
575
576         // state = not used
577         product.setState(ProductState.NOT_USED.value);
578         assertEquals(MAIN_POSITION_A, product.getDisplayPosition());
579         assertEquals(MAIN_POSITION_A, product.getCurrentPosition());
580         assertEquals(TARGET_POSITION, product.getTarget());
581         assertEquals(VANE_POSITION_A, product.getVaneDisplayPosition());
582         assertEquals(VANE_POSITION_A, product.getVanePosition());
583
584         // state = executing
585         product.setState(ProductState.EXECUTING.value);
586         assertEquals(TARGET_POSITION, product.getDisplayPosition());
587         assertEquals(MAIN_POSITION_A, product.getCurrentPosition());
588         assertEquals(TARGET_POSITION, product.getTarget());
589         assertEquals(VANE_POSITION_A, product.getVaneDisplayPosition());
590         assertEquals(VANE_POSITION_A, product.getVanePosition());
591
592         // state = manual + excuting
593         product.setState(ProductState.MANUAL.value + ProductState.EXECUTING.value);
594         assertEquals(UNKNOWN_POSITION, product.getDisplayPosition());
595         assertEquals(MAIN_POSITION_A, product.getCurrentPosition());
596         assertEquals(TARGET_POSITION, product.getTarget());
597         assertEquals(UNKNOWN_POSITION, product.getVaneDisplayPosition());
598         assertEquals(VANE_POSITION_A, product.getVanePosition());
599
600         // state = error
601         product.setState(ProductState.ERROR.value);
602         assertEquals(UNKNOWN_POSITION, product.getDisplayPosition());
603         assertEquals(MAIN_POSITION_A, product.getCurrentPosition());
604         assertEquals(TARGET_POSITION, product.getTarget());
605         assertEquals(UNKNOWN_POSITION, product.getVaneDisplayPosition());
606         assertEquals(VANE_POSITION_A, product.getVanePosition());
607     }
608
609     /**
610      * Test the SCgetHouseStatus command by checking for the correct parsing of a 'GW_NODE_STATE_POSITION_CHANGED_NTF'
611      * notification packet. Note: this packet is from a Velux roller shutter with main and vane position.
612      */
613     @Test
614     @Order(16)
615     public void testSCgetHouseStatusOnVelux() {
616         // initialise the test parameters
617         final String packet = "00 2D C8 00 B8 00 F7 FF F7 FF 00 00 F7 FF 00 00 4A E5 00 00";
618         final short command = VeluxKLFAPI.Command.GW_NODE_STATE_POSITION_CHANGED_NTF.getShort();
619
620         // initialise the BCP
621         SCgetHouseStatus bcp = new SCgetHouseStatus();
622
623         // set the packet response
624         bcp.setResponse(command, toByteArray(packet), false);
625
626         // check BCP status
627         assertTrue(bcp.isCommunicationSuccessful());
628         assertTrue(bcp.isCommunicationFinished());
629
630         // initialise the product
631         VeluxProduct product = bcp.getProduct();
632
633         // change actuator type
634         assertEquals(ACTUATOR_TYPE_UNDEF, product.getActuatorType());
635         product.setActuatorType(ACTUATOR_TYPE_SOMFY);
636
637         // check negative assertions
638         assertNotEquals(VANE_POSITION_A, product.getVanePosition());
639
640         // test updating the existing product in the database
641         VeluxExistingProducts existingProducts = getExistingProducts();
642
643         // process this as a receive only command for a Velux product => update IS applied
644         existingProducts.resetDirtyFlag();
645         assertTrue(existingProducts.update(product));
646         assertTrue(existingProducts.isDirty());
647
648         // process this as an information request command for a Velux product => update IS applied
649         existingProducts.resetDirtyFlag();
650         product.setCurrentPosition(MAIN_POSITION_B);
651         assertTrue(existingProducts.update(product));
652         assertTrue(existingProducts.isDirty());
653     }
654
655     /**
656      * Test updating logic with various states applied.
657      */
658     @Test
659     @Order(17)
660     public void testUpdatingLogic() {
661         VeluxExistingProducts existingProducts = getExistingProducts();
662         ProductBridgeIndex index = new ProductBridgeIndex(PRODUCT_INDEX_A);
663         VeluxProduct product = existingProducts.get(index).clone();
664
665         assertEquals(ProductState.DONE, product.getProductState());
666
667         // state = done
668         assertEquals(MAIN_POSITION_A, product.getDisplayPosition());
669         assertEquals(MAIN_POSITION_A, product.getCurrentPosition());
670         assertEquals(TARGET_POSITION, product.getTarget());
671         assertEquals(VANE_POSITION_A, product.getVaneDisplayPosition());
672         assertEquals(VANE_POSITION_A, product.getVanePosition());
673
674         // state = not used
675         product.setState(ProductState.NOT_USED.value);
676         product.setCurrentPosition(MAIN_POSITION_A - 1);
677         product.setTarget(TARGET_POSITION - 1);
678         product.setVanePosition(VANE_POSITION_A - 1);
679         existingProducts.update(product);
680         product = existingProducts.get(index).clone();
681         assertEquals(MAIN_POSITION_A, product.getDisplayPosition());
682         assertEquals(MAIN_POSITION_A, product.getCurrentPosition());
683         assertEquals(TARGET_POSITION, product.getTarget());
684         assertEquals(VANE_POSITION_A, product.getVaneDisplayPosition());
685         assertEquals(VANE_POSITION_A, product.getVanePosition());
686
687         // state = manual + excuting
688         product.setState(ProductState.MANUAL.value + ProductState.EXECUTING.value);
689         product.setCurrentPosition(MAIN_POSITION_A - 1);
690         product.setTarget(TARGET_POSITION - 1);
691         product.setVanePosition(VANE_POSITION_A - 1);
692         existingProducts.update(product);
693         product = existingProducts.get(index).clone();
694         assertEquals(UNKNOWN_POSITION, product.getDisplayPosition());
695         assertEquals(MAIN_POSITION_A, product.getCurrentPosition());
696         assertEquals(TARGET_POSITION, product.getTarget());
697         assertEquals(UNKNOWN_POSITION, product.getVaneDisplayPosition());
698         assertEquals(VANE_POSITION_A, product.getVanePosition());
699
700         // state = error
701         product.setState(ProductState.ERROR.value);
702         product.setCurrentPosition(MAIN_POSITION_A - 1);
703         product.setTarget(TARGET_POSITION - 1);
704         product.setVanePosition(VANE_POSITION_A - 1);
705         existingProducts.update(product);
706         product = existingProducts.get(index).clone();
707         assertEquals(UNKNOWN_POSITION, product.getDisplayPosition());
708         assertEquals(MAIN_POSITION_A, product.getCurrentPosition());
709         assertEquals(TARGET_POSITION, product.getTarget());
710         assertEquals(UNKNOWN_POSITION, product.getVaneDisplayPosition());
711         assertEquals(VANE_POSITION_A, product.getVanePosition());
712
713         // state = executing
714         product.setState(ProductState.EXECUTING.value);
715         product.setCurrentPosition(MAIN_POSITION_A - 1);
716         product.setTarget(TARGET_POSITION - 1);
717         product.setVanePosition(VANE_POSITION_A - 1);
718         existingProducts.update(product);
719         product = existingProducts.get(index).clone();
720         assertNotEquals(TARGET_POSITION, product.getDisplayPosition());
721         assertNotEquals(MAIN_POSITION_A, product.getCurrentPosition());
722         assertNotEquals(TARGET_POSITION, product.getTarget());
723         assertNotEquals(VANE_POSITION_A, product.getVaneDisplayPosition());
724         assertNotEquals(VANE_POSITION_A, product.getVanePosition());
725
726         // state = done
727         product.setState(ProductState.EXECUTING.value);
728         product.setCurrentPosition(MAIN_POSITION_A);
729         product.setTarget(TARGET_POSITION);
730         product.setVanePosition(VANE_POSITION_A);
731         existingProducts.update(product);
732         product = existingProducts.get(index).clone();
733         assertEquals(TARGET_POSITION, product.getDisplayPosition());
734         assertEquals(MAIN_POSITION_A, product.getCurrentPosition());
735         assertEquals(TARGET_POSITION, product.getTarget());
736         assertEquals(VANE_POSITION_A, product.getVaneDisplayPosition());
737         assertEquals(VANE_POSITION_A, product.getVanePosition());
738     }
739
740     /**
741      * Test updating the existing product in the database with special exceptions.
742      */
743     @Test
744     @Order(18)
745     public void testSpecialExceptions() {
746         VeluxExistingProducts existingProducts = getExistingProducts();
747         VeluxProduct existing = existingProducts.get(new ProductBridgeIndex(PRODUCT_INDEX_A));
748
749         VeluxProduct product;
750
751         // process this as a receive only command for a Somfy product => update IS applied
752         product = new VeluxProduct(existing.getProductName(), existing.getBridgeProductIndex(), existing.getState(),
753                 existing.getCurrentPosition(), existing.getTarget(), existing.getFunctionalParameters(),
754                 Command.GW_OPENHAB_RECEIVEONLY);
755         existingProducts.resetDirtyFlag();
756         product.setState(ProductState.DONE.value);
757         product.setCurrentPosition(MAIN_POSITION_B);
758         assertTrue(existingProducts.update(product));
759         assertTrue(existingProducts.isDirty());
760
761         // process this as an information request command for a Somfy product => update IS applied
762         product = new VeluxProduct(existing.getProductName(), existing.getBridgeProductIndex(), existing.getState(),
763                 existing.getCurrentPosition(), existing.getTarget(), existing.getFunctionalParameters(),
764                 Command.GW_GET_NODE_INFORMATION_REQ);
765         existingProducts.resetDirtyFlag();
766         product.setCurrentPosition(MAIN_POSITION_A);
767         assertTrue(existingProducts.update(product));
768         assertTrue(existingProducts.isDirty());
769
770         // process this as a receive only command for a Somfy product with bad data => update NOT applied
771         product = new VeluxProduct(existing.getProductName(), existing.getBridgeProductIndex(), existing.getState(),
772                 existing.getCurrentPosition(), existing.getTarget(), existing.getFunctionalParameters(),
773                 Command.GW_OPENHAB_RECEIVEONLY);
774         existingProducts.resetDirtyFlag();
775         product.setCurrentPosition(UNKNOWN_POSITION);
776         product.setTarget(UNKNOWN_POSITION);
777         assertTrue(existingProducts.update(product));
778         assertFalse(existingProducts.isDirty());
779     }
780
781     /**
782      * Test VeluxProductPosition
783      */
784     @Test
785     @Order(19)
786     public void testVeluxProductPosition() {
787         VeluxProductPosition position;
788         int target;
789
790         // on and inside range limits
791         assertTrue(new VeluxProductPosition(VeluxProductPosition.VPP_VELUX_MIN).isValid());
792         assertTrue(new VeluxProductPosition(VeluxProductPosition.VPP_VELUX_MAX).isValid());
793         assertTrue(new VeluxProductPosition(VeluxProductPosition.VPP_VELUX_MAX - 1).isValid());
794         assertTrue(new VeluxProductPosition(VeluxProductPosition.VPP_VELUX_MIN + 1).isValid());
795
796         // outside range limits
797         assertFalse(new VeluxProductPosition(VeluxProductPosition.VPP_VELUX_MIN - 1).isValid());
798         assertFalse(new VeluxProductPosition(VeluxProductPosition.VPP_VELUX_MAX + 1).isValid());
799         assertFalse(new VeluxProductPosition(VeluxProductPosition.VPP_VELUX_IGNORE).isValid());
800         assertFalse(new VeluxProductPosition(VeluxProductPosition.VPP_VELUX_DEFAULT).isValid());
801         assertFalse(new VeluxProductPosition(VeluxProductPosition.VPP_VELUX_STOP).isValid());
802         assertFalse(new VeluxProductPosition(VeluxProductPosition.VPP_VELUX_UNKNOWN).isValid());
803
804         // 80% absolute position
805         position = new VeluxProductPosition(new PercentType(80));
806         assertEquals(0xA000, position.getPositionAsVeluxType());
807         assertTrue(position.isValid());
808
809         // 80% absolute position
810         position = new VeluxProductPosition(new PercentType(80), false);
811         assertEquals(0xA000, position.getPositionAsVeluxType());
812         assertTrue(position.isValid());
813
814         // 80% inverted absolute position (i.e. 20%)
815         position = new VeluxProductPosition(new PercentType(80), true);
816         assertEquals(0x2800, position.getPositionAsVeluxType());
817         assertTrue(position.isValid());
818
819         // 80% positive relative position
820         target = VeluxProductPosition.VPP_VELUX_RELATIVE_ORIGIN
821                 + (VeluxProductPosition.VPP_VELUX_RELATIVE_RANGE * 8 / 10);
822         position = new VeluxProductPosition(new PercentType(80)).overridePositionType(PositionType.OFFSET_POSITIVE);
823         assertTrue(position.isValid());
824         assertEquals(target, position.getPositionAsVeluxType());
825
826         // 80% negative relative position
827         target = VeluxProductPosition.VPP_VELUX_RELATIVE_ORIGIN
828                 - (VeluxProductPosition.VPP_VELUX_RELATIVE_RANGE * 8 / 10);
829         position = new VeluxProductPosition(new PercentType(80)).overridePositionType(PositionType.OFFSET_NEGATIVE);
830         assertTrue(position.isValid());
831         assertEquals(target, position.getPositionAsVeluxType());
832     }
833
834     /**
835      * Test SCrunProductResult results
836      */
837     @Test
838     @Order(20)
839     public void testSCrunProductResults() {
840         SCrunProductCommand bcp = new SCrunProductCommand();
841
842         // create a dummy product to get some functional parameters from
843         VeluxProduct product = new VeluxProduct(VeluxProductName.UNKNOWN, new ProductBridgeIndex(PRODUCT_INDEX_A), 0, 0,
844                 0, null, Command.UNDEFTYPE);
845         product.setActuatorType(ACTUATOR_TYPE_SOMFY);
846         product.setVanePosition(VANE_POSITION_A);
847         final FunctionalParameters functionalParameters = product.getFunctionalParameters();
848
849         boolean ok;
850
851         // test setting both main and vane position
852         ok = bcp.setNodeIdAndParameters(PRODUCT_INDEX_A, new VeluxProductPosition(MAIN_POSITION_A),
853                 functionalParameters);
854         assertTrue(ok);
855         product = bcp.getProduct();
856         product.setActuatorType(ACTUATOR_TYPE_SOMFY);
857         assertEquals(MAIN_POSITION_A, product.getCurrentPosition());
858         assertEquals(VANE_POSITION_A, product.getVanePosition());
859
860         // test setting vane position only
861         ok = bcp.setNodeIdAndParameters(PRODUCT_INDEX_A, null, functionalParameters);
862         assertTrue(ok);
863         product = bcp.getProduct();
864         product.setActuatorType(ACTUATOR_TYPE_SOMFY);
865         assertEquals(IGNORE_POSITION, product.getCurrentPosition());
866         assertEquals(VANE_POSITION_A, product.getVanePosition());
867
868         // test setting main position only
869         ok = bcp.setNodeIdAndParameters(PRODUCT_INDEX_A, new VeluxProductPosition(MAIN_POSITION_A), null);
870         assertTrue(ok);
871         product = bcp.getProduct();
872         product.setActuatorType(ACTUATOR_TYPE_SOMFY);
873         assertEquals(MAIN_POSITION_A, product.getCurrentPosition());
874         assertEquals(UNKNOWN_POSITION, product.getVanePosition());
875
876         // test setting neither
877         ok = bcp.setNodeIdAndParameters(PRODUCT_INDEX_A, null, null);
878         assertFalse(ok);
879     }
880
881     /**
882      * Test SCgetProductStatus exceptional error state processing.
883      */
884     @Test
885     @Order(21)
886     public void testErrorStateMapping() {
887         // initialise the test parameters
888         final String packet = "0F A3 01 06 01 00 01 02 00 9A 36 03 00 00 00 00 00 00 00 00 00 00 00 00 00"
889                 + " 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00"
890                 + " 00 00 00 00 00";
891         final Command command = VeluxKLFAPI.Command.GW_STATUS_REQUEST_NTF;
892
893         // initialise the BCP
894         SCgetProductStatus bcp = new SCgetProductStatus();
895         bcp.setProductId(PRODUCT_INDEX_A);
896
897         // set the packet response
898         bcp.setResponse(command.getShort(), toByteArray(packet), false);
899
900         // check BCP status
901         assertTrue(bcp.isCommunicationSuccessful());
902         assertTrue(bcp.isCommunicationFinished());
903
904         // check the product state
905         assertEquals(ProductState.UNKNOWN.value, bcp.getProduct().getState());
906     }
907 }