]> git.basschouten.com Git - openhab-addons.git/commitdiff
[hdpowerview] Corrections to shade database and capabilities (#12902)
authorAndrew Fiddian-Green <software@whitebear.ch>
Fri, 10 Jun 2022 18:10:31 +0000 (20:10 +0200)
committerGitHub <noreply@github.com>
Fri, 10 Jun 2022 18:10:31 +0000 (20:10 +0200)
* [hdpowerview] add type 66 shutters to database
* [hdpowerview] shade database updates
* [hdpowerview] shade database additions and corrections
* [hdpowerview] enhance database features
* [hdpowerview] fix capabilities 8, 9 functionality
* [hdpowerview] adjust tests to match new capabilities
* [hdpowerview] correct method visibility
* [hdpowerview] test type 44
* [hdpowerview] remove comment
* [hdpowerview] name change
* [hdpowerview] remove comments attribute
* [hdpowerview] refactor capabilities code
* [hdpowerview] 'hard' properties now hidden
* [hdpowerview] adopt reviewer suggestion
* [hdpowerview] refactor constant names

Signed-off-by: Andrew Fiddian-Green <software@whitebear.ch>
bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/HDPowerViewBindingConstants.java
bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/api/ShadePosition.java
bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/database/ShadeCapabilitiesDatabase.java
bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/discovery/HDPowerViewDeviceDiscoveryService.java
bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/handler/HDPowerViewShadeHandler.java
bundles/org.openhab.binding.hdpowerview/src/test/java/org/openhab/binding/hdpowerview/ShadePositionTest.java

index 1b19597dfa77097fa292f41ece429e4db1a22f3f..be154bc2ec45f229dcc673191f9e1aa6e35a3d6a 100644 (file)
@@ -67,8 +67,6 @@ public class HDPowerViewBindingConstants {
     // Shade properties
     public static final String PROPERTY_SHADE_TYPE = "type";
     public static final String PROPERTY_SHADE_CAPABILITIES = "capabilities";
-    public static final String PROPERTY_SECONDARY_RAIL_DETECTED = "secondaryRailDetected";
-    public static final String PROPERTY_TILT_ANYWHERE_DETECTED = "tiltAnywhereDetected";
     public static final String PROPERTY_MOTOR_FIRMWARE_VERSION = "motorFirmwareVersion";
 
     public static final List<String> NETBIOS_NAMES = Arrays.asList("PDBU-Hub3.0", "PowerView-Hub");
index 00ca32976034ba193829f3fcff535aaf0aebbbdc..0eee7aff49ce8b8b45cc79aab84c744392b992bb 100644 (file)
@@ -158,7 +158,7 @@ public class ShadePosition {
                     }
                     return new PercentType((int) Math.round((double) position1 / MAX_SHADE * 100));
                 }
-                if (PRIMARY_POSITION.equals(posKind1) && shadeCapabilities.supportsSecondaryOverlapped()) {
+                if (!SECONDARY_POSITION.equals(posKind1) && shadeCapabilities.supportsSecondaryOverlapped()) {
                     return PercentType.ZERO;
                 }
                 break;
@@ -182,6 +182,10 @@ public class ShadePosition {
                 if (PRIMARY_POSITION.equals(posKind1) && shadeCapabilities.supportsTiltOnClosed()) {
                     return position1 != 0 ? UnDefType.UNDEF : PercentType.ZERO;
                 }
+                if (SECONDARY_POSITION.equals(posKind1) && shadeCapabilities.supportsSecondaryOverlapped()
+                        && shadeCapabilities.supportsTiltOnClosed()) {
+                    return PercentType.HUNDRED;
+                }
                 break;
 
             case ERROR_UNKNOWN:
index 47a3b1ed9425278c2a5024f39cba9eca3a00d53e..6b0bdd90568baec5f56c87302aba861b6ce0565c 100644 (file)
@@ -18,6 +18,7 @@ import java.util.function.Function;
 import java.util.stream.Collectors;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -40,16 +41,17 @@ public class ShadeCapabilitiesDatabase {
      */
     private static final Map<Integer, Capabilities> CAPABILITIES_DATABASE = Arrays.asList(
     // @formatter:off
-            new Capabilities(0).primary()        .tiltOnClosed()                                .text("Bottom Up"),
+            new Capabilities(0).primary()                                                       .text("Bottom Up"),
             new Capabilities(1).primary()        .tiltOnClosed()                                .text("Bottom Up Tilt 90°"),
             new Capabilities(2).primary()        .tiltAnywhere().tilt180()                      .text("Bottom Up Tilt 180°"),
-            new Capabilities(3).primary()        .tiltOnClosed()                                .text("Vertical"),
-            new Capabilities(4).primary()        .tiltAnywhere().tilt180()                      .text("Vertical Tilt 180°"),
+            new Capabilities(3).primary()        .tiltAnywhere().tilt180()                      .text("Vertical Tilt 180°"),
+            new Capabilities(4).primary()                                                       .text("Vertical"),
             new Capabilities(5)                  .tiltAnywhere().tilt180()                      .text("Tilt Only 180°"),
             new Capabilities(6).primaryInverted()                                               .text("Top Down"),
             new Capabilities(7).primary()                                 .secondary()          .text("Top Down Bottom Up"),
             new Capabilities(8).primary()                                 .secondaryOverlapped().text("Dual Overlapped"),
-            new Capabilities(9).primary()        .tiltAnywhere()          .secondaryOverlapped().text("Dual Overlapped Tilt 90°"),
+            // note: for the following capabilities entry the 'tiltOnClosed()' applies to the primary shade
+            new Capabilities(9).primary()        .tiltOnClosed()          .secondaryOverlapped().text("Dual Overlapped Tilt 90°"),
     // @formatter:on
             new Capabilities()).stream().collect(Collectors.toMap(Capabilities::getValue, Function.identity()));
 
@@ -58,18 +60,20 @@ public class ShadeCapabilitiesDatabase {
      */
     private static final Map<Integer, Type> TYPE_DATABASE = Arrays.asList(
     // @formatter:off
+            new Type( 1).capabilities(0).text("Roller / Solar"),
             new Type( 4).capabilities(0).text("Roman"),
             new Type( 5).capabilities(0).text("Bottom Up"),
             new Type( 6).capabilities(0).text("Duette"),
             new Type( 7).capabilities(6).text("Top Down"),
             new Type( 8).capabilities(7).text("Duette Top Down Bottom Up"),
             new Type( 9).capabilities(7).text("Duette DuoLite Top Down Bottom Up"),
-            new Type(18).capabilities(1).text("Silhouette"),
+            new Type(18).capabilities(1).text("Pirouette"),
             new Type(23).capabilities(1).text("Silhouette"),
             new Type(38).capabilities(9).text("Silhouette Duolite"),
             new Type(42).capabilities(0).text("M25T Roller Blind"),
             new Type(43).capabilities(1).text("Facette"),
-            new Type(44).capabilities(0).text("Twist"),
+            // note: the following shade type has the functionality of a capabilities 1 shade
+            new Type(44).capabilities(0).text("Twist").capabilitiesOverride(1),
             new Type(47).capabilities(7).text("Pleated Top Down Bottom Up"),
             new Type(49).capabilities(0).text("AC Roller"),
             new Type(51).capabilities(2).text("Venetian"),
@@ -79,9 +83,9 @@ public class ShadeCapabilitiesDatabase {
             new Type(62).capabilities(2).text("Venetian"),
             new Type(65).capabilities(8).text("Vignette Duolite"),
             new Type(66).capabilities(5).text("Shutter"),
-            new Type(69).capabilities(3).text("Curtain Left Stack"),
-            new Type(70).capabilities(3).text("Curtain Right Stack"),
-            new Type(71).capabilities(3).text("Curtain Split Stack"),
+            new Type(69).capabilities(4).text("Curtain Left Stack"),
+            new Type(70).capabilities(4).text("Curtain Right Stack"),
+            new Type(71).capabilities(4).text("Curtain Split Stack"),
             new Type(79).capabilities(8).text("Duolite Lift"),
     // @formatter:on
             new Type()).stream().collect(Collectors.toMap(Type::getValue, Function.identity()));
@@ -112,6 +116,7 @@ public class ShadeCapabilitiesDatabase {
      */
     public static class Type extends Base {
         private int capabilities = -1;
+        private int capabilitiesOverride = -1;
 
         protected Type() {
         }
@@ -130,6 +135,11 @@ public class ShadeCapabilitiesDatabase {
             return this;
         }
 
+        protected Type capabilitiesOverride(int capabilitiesOverride) {
+            this.capabilitiesOverride = capabilitiesOverride;
+            return this;
+        }
+
         /**
          * Get shade types's 'capabilities'.
          *
@@ -138,6 +148,15 @@ public class ShadeCapabilitiesDatabase {
         public int getCapabilities() {
             return capabilities;
         }
+
+        /**
+         * Get shade's type specific 'capabilities'.
+         *
+         * @return 'typeCapabilities'.
+         */
+        public int getCapabilitiesOverride() {
+            return capabilitiesOverride;
+        }
     }
 
     /**
@@ -301,13 +320,35 @@ public class ShadeCapabilitiesDatabase {
     }
 
     /**
-     * Return a Capabilities class instance that corresponds to the given 'capabilities' parameter.
+     * Return a Capabilities class instance that corresponds to the given 'capabilitiesId' parameter. If the
+     * 'capabilitiesId' parameter is for a valid capabilities entry in the database, then that respective Capabilities
+     * class instance is returned. Otherwise a blank Capabilities class instance is returned.
      *
-     * @param capabilities the shade 'capabilities' parameter.
-     * @return corresponding instance of Capabilities class.
+     * @param capabilitiesId the target capabilities Id.
+     * @return corresponding Capabilities class instance.
      */
-    public Capabilities getCapabilities(int capabilities) {
-        return CAPABILITIES_DATABASE.getOrDefault(capabilities, new Capabilities());
+    public Capabilities getCapabilities(@Nullable Integer capabilitiesId) {
+        return CAPABILITIES_DATABASE.getOrDefault(capabilitiesId != null ? capabilitiesId.intValue() : -1,
+                new Capabilities());
+    }
+
+    /**
+     * Return a Capabilities class instance that corresponds to the given 'typeId' parameter. If the 'typeId' parameter
+     * is a valid type in the database, and it has a 'capabilitiesOverride' value, then an instance of the respective
+     * overridden Capabilities class is returned. Otherwise if the 'capabilitiesId' parameter is for a valid
+     * capabilities entry in the database, then that respective Capabilities class instance is returned. Otherwise a
+     * blank Capabilities class instance is returned.
+     *
+     * @param typeId the target shade type Id (to check if it has a 'capabilitiesOverride' value).
+     * @param capabilitiesId the target capabilities value (when type Id does not have a 'capabilitiesOverride').
+     * @return corresponding Capabilities class instance.
+     */
+    public Capabilities getCapabilities(int typeId, @Nullable Integer capabilitiesId) {
+        int targetCapabilities = TYPE_DATABASE.getOrDefault(typeId, new Type()).getCapabilitiesOverride();
+        if (targetCapabilities < 0) {
+            targetCapabilities = capabilitiesId != null ? capabilitiesId.intValue() : -1;
+        }
+        return getCapabilities(targetCapabilities);
     }
 
     private static final String REQUEST_DEVELOPERS_TO_UPDATE = " => Please request developers to update the database!";
index f61be9f9a9b624095068ee510c2dc3a66fc4f6f3..d8f5d9862d01ffbed051b72412fa6f623fd36793 100644 (file)
@@ -115,8 +115,7 @@ public class HDPowerViewDeviceDiscoveryService extends AbstractDiscoveryService
             }
             String id = Integer.toString(shadeData.id);
             ThingUID thingUID = new ThingUID(HDPowerViewBindingConstants.THING_TYPE_SHADE, bridgeUid, id);
-            Integer caps = shadeData.capabilities;
-            Capabilities capabilities = db.getCapabilities((caps != null) ? caps.intValue() : -1);
+            Capabilities capabilities = db.getCapabilities(shadeData.capabilities);
 
             DiscoveryResultBuilder builder = DiscoveryResultBuilder.create(thingUID).withLabel(shadeData.getName())
                     .withBridge(bridgeUid).withProperty(HDPowerViewShadeConfiguration.ID, id)
index 2c072811c6451870a7d55d06e7862ff612ca3a80..5a3e203af6c61526caf155a23e87e400a6e36281 100644 (file)
@@ -15,6 +15,7 @@ package org.openhab.binding.hdpowerview.internal.handler;
 import static org.openhab.binding.hdpowerview.internal.HDPowerViewBindingConstants.*;
 import static org.openhab.binding.hdpowerview.internal.api.CoordinateSystem.*;
 
+import java.util.HashMap;
 import java.util.Map;
 import java.util.concurrent.ScheduledFuture;
 import java.util.concurrent.TimeUnit;
@@ -75,6 +76,10 @@ public class HDPowerViewShadeHandler extends AbstractHubbedThingHandler {
     private static final String COMMAND_CALIBRATE = "CALIBRATE";
     private static final String COMMAND_IDENTIFY = "IDENTIFY";
 
+    private static final String DETECTED_SECONDARY_RAIL = "secondaryRailDetected";
+    private static final String DETECTED_TILT_ANYWHERE = "tiltAnywhereDetected";
+    private final Map<String, String> detectedCapabilities = new HashMap<>();
+
     private final Logger logger = LoggerFactory.getLogger(HDPowerViewShadeHandler.class);
     private final ShadeCapabilitiesDatabase db = new ShadeCapabilitiesDatabase();
 
@@ -260,14 +265,13 @@ public class HDPowerViewShadeHandler extends AbstractHubbedThingHandler {
             // Already cached.
             return;
         }
-        Integer value = shade.capabilities;
-        if (value != null) {
-            int valueAsInt = value.intValue();
-            logger.debug("Caching capabilities {} for shade {}", valueAsInt, shade.id);
-            capabilities = db.getCapabilities(valueAsInt);
-        } else {
-            logger.debug("Capabilities not included in shade response");
+        Capabilities capabilities = db.getCapabilities(shade.type, shade.capabilities);
+        if (capabilities.getValue() < 0) {
+            logger.debug("Unable to set capabilities for shade {}", shade.id);
+            return;
         }
+        logger.debug("Caching capabilities {} for shade {}", capabilities.getValue(), shade.id);
+        this.capabilities = capabilities;
     }
 
     private Capabilities getCapabilitiesOrDefault() {
@@ -304,9 +308,8 @@ public class HDPowerViewShadeHandler extends AbstractHubbedThingHandler {
         }
 
         // update 'capabilities' property
-        final Integer temp = shadeData.capabilities;
-        final int capabilitiesVal = temp != null ? temp.intValue() : -1;
-        Capabilities capabilities = db.getCapabilities(capabilitiesVal);
+        Capabilities capabilities = db.getCapabilities(shadeData.capabilities);
+        final int capabilitiesVal = capabilities.getValue();
         propKey = HDPowerViewBindingConstants.PROPERTY_SHADE_CAPABILITIES;
         propOldVal = properties.getOrDefault(propKey, "");
         propNewVal = capabilities.toString();
@@ -338,7 +341,7 @@ public class HDPowerViewShadeHandler extends AbstractHubbedThingHandler {
     }
 
     /**
-     * After a hard refresh, update the Thing's properties based on the contents of the provided ShadeData.
+     * After a hard refresh, update the Thing's detected capabilities based on the contents of the provided ShadeData.
      *
      * Checks if the secondary support capabilities in the database of known Shade 'types' and 'capabilities' matches
      * that implied by the ShadeData and logs any incompatible values, so that developers can be kept updated about the
@@ -346,35 +349,34 @@ public class HDPowerViewShadeHandler extends AbstractHubbedThingHandler {
      *
      * @param shadeData
      */
-    private void updateHardProperties(ShadeData shadeData) {
+    private void updateDetectedCapabilities(ShadeData shadeData) {
         final ShadePosition positions = shadeData.positions;
         if (positions == null) {
             return;
         }
         Capabilities capabilities = getCapabilitiesOrDefault();
-        final Map<String, String> properties = getThing().getProperties();
 
-        // update 'secondary rail detected' property
-        String propKey = HDPowerViewBindingConstants.PROPERTY_SECONDARY_RAIL_DETECTED;
-        String propOldVal = properties.getOrDefault(propKey, "");
-        boolean propNewBool = positions.secondaryRailDetected();
-        String propNewVal = String.valueOf(propNewBool);
-        if (!propNewVal.equals(propOldVal)) {
-            getThing().setProperty(propKey, propNewVal);
-            if (propNewBool != capabilities.supportsSecondary()) {
-                db.logPropertyMismatch(propKey, shadeData.type, capabilities.getValue(), propNewBool);
+        // update 'secondary rail' detected capability
+        String capsKey = DETECTED_SECONDARY_RAIL;
+        String capsOldVal = detectedCapabilities.getOrDefault(capsKey, "");
+        boolean capsNewBool = positions.secondaryRailDetected();
+        String capsNewVal = String.valueOf(capsNewBool);
+        if (!capsNewVal.equals(capsOldVal)) {
+            detectedCapabilities.put(capsKey, capsNewVal);
+            if (capsNewBool != capabilities.supportsSecondary()) {
+                db.logPropertyMismatch(capsKey, shadeData.type, capabilities.getValue(), capsNewBool);
             }
         }
 
-        // update 'tilt anywhere detected' property
-        propKey = HDPowerViewBindingConstants.PROPERTY_TILT_ANYWHERE_DETECTED;
-        propOldVal = properties.getOrDefault(propKey, "");
-        propNewBool = positions.tiltAnywhereDetected();
-        propNewVal = String.valueOf(propNewBool);
-        if (!propNewVal.equals(propOldVal)) {
-            getThing().setProperty(propKey, propNewVal);
-            if (propNewBool != capabilities.supportsTiltAnywhere()) {
-                db.logPropertyMismatch(propKey, shadeData.type, capabilities.getValue(), propNewBool);
+        // update 'tilt anywhere' detected capability
+        capsKey = DETECTED_TILT_ANYWHERE;
+        capsOldVal = detectedCapabilities.getOrDefault(capsKey, "");
+        capsNewBool = positions.tiltAnywhereDetected();
+        capsNewVal = String.valueOf(capsNewBool);
+        if (!capsNewVal.equals(capsOldVal)) {
+            detectedCapabilities.put(capsKey, capsNewVal);
+            if (capsNewBool != capabilities.supportsTiltAnywhere()) {
+                db.logPropertyMismatch(capsKey, shadeData.type, capabilities.getValue(), capsNewBool);
             }
         }
     }
@@ -523,7 +525,7 @@ public class HDPowerViewShadeHandler extends AbstractHubbedThingHandler {
                 case POSITION:
                     shadeData = webTargets.refreshShadePosition(shadeId);
                     updateShadePositions(shadeData);
-                    updateHardProperties(shadeData);
+                    updateDetectedCapabilities(shadeData);
                     break;
                 case SURVEY:
                     Survey survey = webTargets.getShadeSurvey(shadeId);
index ce74424e50aace60e1d8b246f259dfabd8c8afb5..b944c27d56e3628a8acb3989886f64a973697762 100644 (file)
@@ -43,17 +43,18 @@ public class ShadePositionTest {
         assertTrue(db.isCapabilitiesInDatabase(0));
 
         assertTrue(db.getCapabilities(0).supportsPrimary());
-        assertTrue(db.getCapabilities(0).supportsTiltOnClosed());
         assertTrue(db.getCapabilities(1).supportsTiltOnClosed());
         assertTrue(db.getCapabilities(2).supportsTilt180());
-        assertTrue(db.getCapabilities(3).supportsTiltOnClosed());
-        assertTrue(db.getCapabilities(4).supportsTilt180());
+        assertTrue(db.getCapabilities(2).supportsTiltAnywhere());
+        assertTrue(db.getCapabilities(3).supportsTilt180());
+        assertTrue(db.getCapabilities(3).supportsTiltAnywhere());
         assertTrue(db.getCapabilities(5).supportsTilt180());
         assertFalse(db.getCapabilities(5).supportsPrimary());
         assertTrue(db.getCapabilities(6).isPrimaryInverted());
         assertTrue(db.getCapabilities(7).supportsSecondary());
         assertTrue(db.getCapabilities(8).supportsSecondaryOverlapped());
         assertTrue(db.getCapabilities(9).supportsSecondaryOverlapped());
+        assertTrue(db.getCapabilities(9).supportsTiltOnClosed());
 
         assertEquals(db.getType(4).getCapabilities(), 0);
         assertEquals(db.getType(-1).getCapabilities(), -1);
@@ -82,57 +83,59 @@ public class ShadePositionTest {
     }
 
     /**
-     * Test parsing of ShadePosition (shade fully up).
+     * Test parsing of Capabilities 1 ShadePosition (shade fully up).
      *
      */
     @Test
-    public void testShadePositionParsingFullyUp() {
-        Capabilities capabilities = db.getCapabilities(0);
+    public void testCaps1ShadePositionParsingFullyUp() {
+        Capabilities capabilities = db.getCapabilities(1);
         ShadePosition test = new ShadePosition().setPosition(capabilities, PRIMARY_POSITION, 0);
         assertNotNull(test);
-        State pos = test.getState(capabilities, PRIMARY_POSITION);
-        assertShadePosition(pos, 0);
-        pos = test.getState(capabilities, VANE_TILT_POSITION);
-        assertTrue(UnDefType.UNDEF.equals(pos));
+        assertShadePosition(test.getState(capabilities, PRIMARY_POSITION), 0);
+        assertShadePosition(test.getState(capabilities, SECONDARY_POSITION), UnDefType.UNDEF);
+        assertShadePosition(test.getState(capabilities, VANE_TILT_POSITION), UnDefType.UNDEF);
     }
 
     /**
-     * Test parsing of ShadePosition (shade fully down (method 1)).
+     * Test parsing of Capabilities 1 ShadePosition (shade fully down (method 1)).
      *
      */
     @Test
-    public void testShadePositionParsingShadeFullyDown1() {
-        Capabilities capabilities = db.getCapabilities(0);
+    public void testCaps1ShadePositionParsingShadeFullyDown1() {
+        Capabilities capabilities = db.getCapabilities(1);
         ShadePosition test = new ShadePosition().setPosition(capabilities, PRIMARY_POSITION, 100);
         assertNotNull(test);
         assertShadePosition(test.getState(capabilities, PRIMARY_POSITION), 100);
+        assertShadePosition(test.getState(capabilities, SECONDARY_POSITION), UnDefType.UNDEF);
         assertShadePosition(test.getState(capabilities, VANE_TILT_POSITION), 0);
     }
 
     /**
-     * Test parsing of ShadePosition (shade fully down (method 2)).
+     * Test parsing of Capabilities 1 ShadePosition (shade fully down (method 2)).
      *
      */
     @Test
-    public void testShadePositionParsingShadeFullyDown2() {
-        Capabilities capabilities = db.getCapabilities(0);
+    public void testCaps1ShadePositionParsingShadeFullyDown2() {
+        Capabilities capabilities = db.getCapabilities(1);
         ShadePosition test = new ShadePosition().setPosition(capabilities, VANE_TILT_POSITION, 0);
         assertNotNull(test);
         assertShadePosition(test.getState(capabilities, PRIMARY_POSITION), 100);
+        assertShadePosition(test.getState(capabilities, SECONDARY_POSITION), UnDefType.UNDEF);
         assertShadePosition(test.getState(capabilities, VANE_TILT_POSITION), 0);
     }
 
     /**
-     * Test parsing of ShadePosition (shade fully down (method 2) and vane fully open).
+     * Test parsing of Capabilities 1 ShadePosition (shade fully down (method 2) and vane fully open).
      *
      */
     @Test
-    public void testShadePositionParsingShadeFullyDownVaneOpen() {
-        Capabilities capabilities = db.getCapabilities(0);
-        ShadePosition test = new ShadePosition().setPosition(capabilities, VANE_TILT_POSITION, 100);
+    public void testCaps1ShadePositionParsingShadeFullyDownVaneOpen() {
+        Capabilities capabilities = db.getCapabilities(1);
+        ShadePosition test = new ShadePosition().setPosition(capabilities, VANE_TILT_POSITION, 88);
         assertNotNull(test);
         assertShadePosition(test.getState(capabilities, PRIMARY_POSITION), 100);
-        assertShadePosition(test.getState(capabilities, VANE_TILT_POSITION), 100);
+        assertShadePosition(test.getState(capabilities, SECONDARY_POSITION), UnDefType.UNDEF);
+        assertShadePosition(test.getState(capabilities, VANE_TILT_POSITION), 88);
     }
 
     /**
@@ -149,40 +152,47 @@ public class ShadePositionTest {
         test.setPosition(capabilities, PRIMARY_POSITION, 100).setPosition(capabilities, SECONDARY_POSITION, 0);
         assertShadePosition(test.getState(capabilities, PRIMARY_POSITION), 100);
         assertShadePosition(test.getState(capabilities, SECONDARY_POSITION), 0);
+        assertShadePosition(test.getState(capabilities, VANE_TILT_POSITION), UnDefType.UNDEF);
 
         // ==== OK !! primary at middle, secondary at top ====
         test.setPosition(capabilities, PRIMARY_POSITION, 50).setPosition(capabilities, SECONDARY_POSITION, 0);
         assertShadePosition(test.getState(capabilities, PRIMARY_POSITION), 50);
         assertShadePosition(test.getState(capabilities, SECONDARY_POSITION), 0);
+        assertShadePosition(test.getState(capabilities, VANE_TILT_POSITION), UnDefType.UNDEF);
 
         // ==== OK !! primary at middle, secondary at middle ====
         test.setPosition(capabilities, PRIMARY_POSITION, 50).setPosition(capabilities, SECONDARY_POSITION, 50);
         assertShadePosition(test.getState(capabilities, PRIMARY_POSITION), 50);
         assertShadePosition(test.getState(capabilities, SECONDARY_POSITION), 50);
+        assertShadePosition(test.getState(capabilities, VANE_TILT_POSITION), UnDefType.UNDEF);
 
         // ==== IMPOSSIBLE !! secondary at middle, primary above => test the constraining code ====
         test.setPosition(capabilities, SECONDARY_POSITION, 0).setPosition(capabilities, PRIMARY_POSITION, 100);
         test.setPosition(capabilities, SECONDARY_POSITION, 40).setPosition(capabilities, PRIMARY_POSITION, 25);
         assertShadePosition(test.getState(capabilities, PRIMARY_POSITION), 40);
         assertShadePosition(test.getState(capabilities, SECONDARY_POSITION), 40);
+        assertShadePosition(test.getState(capabilities, VANE_TILT_POSITION), UnDefType.UNDEF);
 
         // ==== OK !! secondary at middle, primary below ====
         test.setPosition(capabilities, SECONDARY_POSITION, 0).setPosition(capabilities, PRIMARY_POSITION, 100);
         test.setPosition(capabilities, SECONDARY_POSITION, 50).setPosition(capabilities, PRIMARY_POSITION, 75);
         assertShadePosition(test.getState(capabilities, PRIMARY_POSITION), 75);
         assertShadePosition(test.getState(capabilities, SECONDARY_POSITION), 50);
+        assertShadePosition(test.getState(capabilities, VANE_TILT_POSITION), UnDefType.UNDEF);
 
         // ==== IMPOSSIBLE !! primary at middle, secondary below => test the constraining code ====
         test.setPosition(capabilities, SECONDARY_POSITION, 0).setPosition(capabilities, PRIMARY_POSITION, 100);
         test.setPosition(capabilities, PRIMARY_POSITION, 60).setPosition(capabilities, SECONDARY_POSITION, 75);
         assertShadePosition(test.getState(capabilities, PRIMARY_POSITION), 60);
         assertShadePosition(test.getState(capabilities, SECONDARY_POSITION), 60);
+        assertShadePosition(test.getState(capabilities, VANE_TILT_POSITION), UnDefType.UNDEF);
 
         // ==== OK !! primary at middle, secondary above ====
         test.setPosition(capabilities, SECONDARY_POSITION, 0).setPosition(capabilities, PRIMARY_POSITION, 100);
         test.setPosition(capabilities, PRIMARY_POSITION, 60).setPosition(capabilities, SECONDARY_POSITION, 25);
         assertShadePosition(test.getState(capabilities, PRIMARY_POSITION), 60);
         assertShadePosition(test.getState(capabilities, SECONDARY_POSITION), 25);
+        assertShadePosition(test.getState(capabilities, VANE_TILT_POSITION), UnDefType.UNDEF);
     }
 
     /**
@@ -200,40 +210,46 @@ public class ShadePositionTest {
         assertNotNull(test);
         assertShadePosition(test.getState(capabilities, PRIMARY_POSITION), 0);
         assertShadePosition(test.getState(capabilities, SECONDARY_POSITION), 0);
+        assertShadePosition(test.getState(capabilities, VANE_TILT_POSITION), UnDefType.UNDEF);
 
         // front shade 50% down
         test = new ShadePosition().setPosition(capabilities, PRIMARY_POSITION, 50);
         assertNotNull(test);
         assertShadePosition(test.getState(capabilities, PRIMARY_POSITION), 50);
         assertShadePosition(test.getState(capabilities, SECONDARY_POSITION), 0);
+        assertShadePosition(test.getState(capabilities, VANE_TILT_POSITION), UnDefType.UNDEF);
 
         // front shade 100% down, back shade 0% down
         test = new ShadePosition().setPosition(capabilities, PRIMARY_POSITION, 100);
         assertNotNull(test);
         assertShadePosition(test.getState(capabilities, PRIMARY_POSITION), 100);
         assertShadePosition(test.getState(capabilities, SECONDARY_POSITION), 0);
+        assertShadePosition(test.getState(capabilities, VANE_TILT_POSITION), UnDefType.UNDEF);
 
         // front shade 100% down, back shade 0% down (ALTERNATE)
         test = new ShadePosition().setPosition(capabilities, SECONDARY_POSITION, 0);
         assertNotNull(test);
         assertShadePosition(test.getState(capabilities, PRIMARY_POSITION), 100);
         assertShadePosition(test.getState(capabilities, SECONDARY_POSITION), 0);
+        assertShadePosition(test.getState(capabilities, VANE_TILT_POSITION), UnDefType.UNDEF);
 
         // front shade 100% down, back shade 50% down
         test = new ShadePosition().setPosition(capabilities, SECONDARY_POSITION, 50);
         assertNotNull(test);
         assertShadePosition(test.getState(capabilities, PRIMARY_POSITION), 100);
         assertShadePosition(test.getState(capabilities, SECONDARY_POSITION), 50);
+        assertShadePosition(test.getState(capabilities, VANE_TILT_POSITION), UnDefType.UNDEF);
 
         // front shade 100% down, back shade 100% down
         test = new ShadePosition().setPosition(capabilities, SECONDARY_POSITION, 100);
         assertNotNull(test);
         assertShadePosition(test.getState(capabilities, PRIMARY_POSITION), 100);
         assertShadePosition(test.getState(capabilities, SECONDARY_POSITION), 100);
+        assertShadePosition(test.getState(capabilities, VANE_TILT_POSITION), UnDefType.UNDEF);
     }
 
     /**
-     * Test parsing of DuoLite shades having both a secondary blackout shade, and tilt anywhere functionality.
+     * Test parsing of DuoLite shades having both a secondary blackout shade, and tilt functionality.
      *
      */
     @Test
@@ -242,51 +258,149 @@ public class ShadePositionTest {
         Capabilities capabilities = db.getCapabilities(9);
         ShadePosition test;
 
-        // both shades up, tilt 0%
-        test = new ShadePosition().setPosition(capabilities, PRIMARY_POSITION, 0).setPosition(capabilities,
-                VANE_TILT_POSITION, 0);
+        // front shade up
+        test = new ShadePosition().setPosition(capabilities, PRIMARY_POSITION, 0);
         assertNotNull(test);
         assertShadePosition(test.getState(capabilities, PRIMARY_POSITION), 0);
         assertShadePosition(test.getState(capabilities, SECONDARY_POSITION), 0);
+        assertShadePosition(test.getState(capabilities, VANE_TILT_POSITION), UnDefType.UNDEF);
 
-        // front shade 50% down, tilt 30%
-        test = new ShadePosition().setPosition(capabilities, PRIMARY_POSITION, 50).setPosition(capabilities,
-                VANE_TILT_POSITION, 30);
+        // front shade 30% down
+        test = new ShadePosition().setPosition(capabilities, PRIMARY_POSITION, 30);
         assertNotNull(test);
-        assertShadePosition(test.getState(capabilities, PRIMARY_POSITION), 50);
+        assertShadePosition(test.getState(capabilities, PRIMARY_POSITION), 30);
         assertShadePosition(test.getState(capabilities, SECONDARY_POSITION), 0);
-        assertShadePosition(test.getState(capabilities, VANE_TILT_POSITION), 30);
+        assertShadePosition(test.getState(capabilities, VANE_TILT_POSITION), UnDefType.UNDEF);
 
-        // front shade 100% down, back shade 0% down, tilt 30%
-        test = new ShadePosition().setPosition(capabilities, PRIMARY_POSITION, 100).setPosition(capabilities,
-                VANE_TILT_POSITION, 30);
+        // front shade 100% down
+        test = new ShadePosition().setPosition(capabilities, PRIMARY_POSITION, 100);
         assertNotNull(test);
         assertShadePosition(test.getState(capabilities, PRIMARY_POSITION), 100);
         assertShadePosition(test.getState(capabilities, SECONDARY_POSITION), 0);
-        assertShadePosition(test.getState(capabilities, VANE_TILT_POSITION), 30);
+        assertShadePosition(test.getState(capabilities, VANE_TILT_POSITION), 0);
 
-        // front shade 100% down, back shade 0% down, tilt 30% (ALTERNATE)
-        test = new ShadePosition().setPosition(capabilities, SECONDARY_POSITION, 0).setPosition(capabilities,
-                VANE_TILT_POSITION, 30);
+        // tilt 0%
+        test = new ShadePosition().setPosition(capabilities, VANE_TILT_POSITION, 0);
+        assertNotNull(test);
+        assertShadePosition(test.getState(capabilities, PRIMARY_POSITION), 100);
+        assertShadePosition(test.getState(capabilities, SECONDARY_POSITION), 0);
+        assertShadePosition(test.getState(capabilities, VANE_TILT_POSITION), 0);
+
+        // tilt 30%
+        test = new ShadePosition().setPosition(capabilities, VANE_TILT_POSITION, 30);
         assertNotNull(test);
         assertShadePosition(test.getState(capabilities, PRIMARY_POSITION), 100);
         assertShadePosition(test.getState(capabilities, SECONDARY_POSITION), 0);
         assertShadePosition(test.getState(capabilities, VANE_TILT_POSITION), 30);
 
-        // front shade 100% down, back shade 50% down, tilt 30%
-        test = new ShadePosition().setPosition(capabilities, SECONDARY_POSITION, 50).setPosition(capabilities,
+        // tilt 100%
+        test = new ShadePosition().setPosition(capabilities, VANE_TILT_POSITION, 100);
+        assertNotNull(test);
+        assertShadePosition(test.getState(capabilities, PRIMARY_POSITION), 100);
+        assertShadePosition(test.getState(capabilities, SECONDARY_POSITION), 0);
+        assertShadePosition(test.getState(capabilities, VANE_TILT_POSITION), 100);
+
+        // back shade 0% down
+        test = new ShadePosition().setPosition(capabilities, SECONDARY_POSITION, 0);
+        assertNotNull(test);
+        assertShadePosition(test.getState(capabilities, PRIMARY_POSITION), 100);
+        assertShadePosition(test.getState(capabilities, SECONDARY_POSITION), 0);
+        assertShadePosition(test.getState(capabilities, VANE_TILT_POSITION), 100);
+
+        // back shade 30% down
+        test = new ShadePosition().setPosition(capabilities, SECONDARY_POSITION, 30);
+        assertNotNull(test);
+        assertShadePosition(test.getState(capabilities, PRIMARY_POSITION), 100);
+        assertShadePosition(test.getState(capabilities, SECONDARY_POSITION), 30);
+        assertShadePosition(test.getState(capabilities, VANE_TILT_POSITION), 100);
+
+        // back shade 100% down
+        test = new ShadePosition().setPosition(capabilities, SECONDARY_POSITION, 100);
+        assertNotNull(test);
+        assertShadePosition(test.getState(capabilities, PRIMARY_POSITION), 100);
+        assertShadePosition(test.getState(capabilities, SECONDARY_POSITION), 100);
+        assertShadePosition(test.getState(capabilities, VANE_TILT_POSITION), 100);
+
+        // test constraints on impossible values: primary 30% => tilt 30%
+        test = new ShadePosition().setPosition(capabilities, PRIMARY_POSITION, 30).setPosition(capabilities,
                 VANE_TILT_POSITION, 30);
         assertNotNull(test);
         assertShadePosition(test.getState(capabilities, PRIMARY_POSITION), 100);
-        assertShadePosition(test.getState(capabilities, SECONDARY_POSITION), 50);
+        assertShadePosition(test.getState(capabilities, SECONDARY_POSITION), 0);
         assertShadePosition(test.getState(capabilities, VANE_TILT_POSITION), 30);
 
-        // front shade 100% down, back shade 100% down, tilt 70%
-        test = new ShadePosition().setPosition(capabilities, SECONDARY_POSITION, 100).setPosition(capabilities,
-                VANE_TILT_POSITION, 70);
+        // test constraints on impossible values: primary 30% => tilt 30% => back shade 30% down
+        test = new ShadePosition().setPosition(capabilities, PRIMARY_POSITION, 30)
+                .setPosition(capabilities, VANE_TILT_POSITION, 30).setPosition(capabilities, SECONDARY_POSITION, 30);
         assertNotNull(test);
         assertShadePosition(test.getState(capabilities, PRIMARY_POSITION), 100);
-        assertShadePosition(test.getState(capabilities, SECONDARY_POSITION), 100);
-        assertShadePosition(test.getState(capabilities, VANE_TILT_POSITION), 70);
+        assertShadePosition(test.getState(capabilities, SECONDARY_POSITION), 30);
+        assertShadePosition(test.getState(capabilities, VANE_TILT_POSITION), 100);
+    }
+
+    /**
+     * Test parsing of Capabilities 0 ShadePosition (shade fully up).
+     *
+     */
+    @Test
+    public void testCaps0ShadePositionParsingFullyUp() {
+        Capabilities capabilities = db.getCapabilities(0);
+        ShadePosition test = new ShadePosition().setPosition(capabilities, PRIMARY_POSITION, 0);
+        assertNotNull(test);
+        assertShadePosition(test.getState(capabilities, PRIMARY_POSITION), 0);
+        assertShadePosition(test.getState(capabilities, SECONDARY_POSITION), UnDefType.UNDEF);
+        assertShadePosition(test.getState(capabilities, VANE_TILT_POSITION), UnDefType.UNDEF);
+    }
+
+    /**
+     * Test parsing of Capabilities 0 ShadePosition (shade fully down).
+     *
+     */
+    @Test
+    public void testCap0ShadePositionParsingShadeFullyDown() {
+        Capabilities capabilities = db.getCapabilities(0);
+        ShadePosition test = new ShadePosition().setPosition(capabilities, PRIMARY_POSITION, 100);
+        assertNotNull(test);
+        assertShadePosition(test.getState(capabilities, PRIMARY_POSITION), 100);
+        assertShadePosition(test.getState(capabilities, SECONDARY_POSITION), UnDefType.UNDEF);
+        assertShadePosition(test.getState(capabilities, VANE_TILT_POSITION), UnDefType.UNDEF);
+    }
+
+    /**
+     * Helper method; test if shade State is correct.
+     *
+     * @param actual the shade State
+     * @param target the test value to compare with
+     */
+    private void assertShadePosition(State actual, State target) {
+        assertTrue(target.equals(actual));
+    }
+
+    /**
+     * Test parsing of Type 44 ShadePosition (shade fully up).
+     *
+     */
+    @Test
+    public void testType44ShadePositionParsingFullyUp() {
+        Capabilities capabilities = db.getCapabilities(44, null);
+        ShadePosition test = new ShadePosition().setPosition(capabilities, PRIMARY_POSITION, 0);
+        assertNotNull(test);
+        assertShadePosition(test.getState(capabilities, PRIMARY_POSITION), 0);
+        assertShadePosition(test.getState(capabilities, SECONDARY_POSITION), UnDefType.UNDEF);
+        assertShadePosition(test.getState(capabilities, VANE_TILT_POSITION), UnDefType.UNDEF);
+    }
+
+    /**
+     * Test parsing of Type 44 ShadePosition (shade fully down (method 2) and vane fully open).
+     *
+     */
+    @Test
+    public void testType44ShadePositionParsingShadeFullyDownVaneOpen() {
+        Capabilities capabilities = db.getCapabilities(44, null);
+        ShadePosition test = new ShadePosition().setPosition(capabilities, VANE_TILT_POSITION, 88);
+        assertNotNull(test);
+        assertShadePosition(test.getState(capabilities, PRIMARY_POSITION), 100);
+        assertShadePosition(test.getState(capabilities, SECONDARY_POSITION), UnDefType.UNDEF);
+        assertShadePosition(test.getState(capabilities, VANE_TILT_POSITION), 88);
     }
 }