]> git.basschouten.com Git - openhab-addons.git/blob
1c397d60bbac465abd313c71df64e49bdcc9926b
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2022 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.hdpowerview.internal.api;
14
15 import static org.openhab.binding.hdpowerview.internal.api.CoordinateSystem.*;
16
17 import org.eclipse.jdt.annotation.NonNullByDefault;
18 import org.eclipse.jdt.annotation.Nullable;
19 import org.openhab.binding.hdpowerview.internal.database.ShadeCapabilitiesDatabase.Capabilities;
20 import org.openhab.core.library.types.PercentType;
21 import org.openhab.core.types.State;
22 import org.openhab.core.types.UnDefType;
23 import org.slf4j.Logger;
24 import org.slf4j.LoggerFactory;
25
26 /**
27  * The position of a single shade, as returned by the HD PowerView hub
28  *
29  * @author Andy Lintner - Initial contribution
30  * @author Andrew Fiddian-Green - Added support for secondary rail positions
31  */
32 @NonNullByDefault
33 public class ShadePosition {
34
35     private final transient Logger logger = LoggerFactory.getLogger(ShadePosition.class);
36
37     /**
38      * Primary actuator position.
39      */
40     private int posKind1;
41     private int position1;
42
43     /**
44      * Secondary actuator position.
45      *
46      * Here we have to use Integer objects rather than just int primitives because these are secondary optional position
47      * elements in the JSON payload, so the GSON de-serializer might leave them as null.
48      */
49     private @Nullable Integer posKind2 = null;
50     private @Nullable Integer position2 = null;
51
52     public ShadePosition() {
53     }
54
55     /**
56      * Get the shade's State for the given actuator class resp. coordinate system.
57      *
58      * @param shadeCapabilities the shade Thing capabilities.
59      * @param posKindCoords the actuator class (coordinate system) whose state is to be returned.
60      * @return the current state.
61      */
62     public State getState(Capabilities shadeCapabilities, CoordinateSystem posKindCoords) {
63         State result = getPosition1(shadeCapabilities, posKindCoords);
64         if (result == UnDefType.UNDEF) {
65             result = getPosition2(shadeCapabilities, posKindCoords);
66         }
67         logger.trace("getState(): capabilities={}, coords={} => result={}", shadeCapabilities, posKindCoords, result);
68         return result;
69     }
70
71     /**
72      * Set the shade's position1 value for the given actuator class resp. coordinate system.
73      *
74      * @param shadeCapabilities the shade Thing capabilities.
75      * @param posKindCoords the actuator class (coordinate system) whose state is to be changed.
76      * @param percent the new position value.
77      */
78     private void setPosition1(Capabilities shadeCapabilities, CoordinateSystem posKindCoords, int percent) {
79         switch (posKindCoords) {
80             case PRIMARY_POSITION:
81                 /*
82                  * Primary rail of a bottom-up shade, or lower rail of a dual action shade: => INVERTED
83                  */
84                 if (shadeCapabilities.supportsPrimary() && shadeCapabilities.supportsSecondary()) {
85                     // on dual rail shades constrain percent to not move the lower rail above the upper
86                     State secondary = getState(shadeCapabilities, SECONDARY_POSITION);
87                     if (secondary instanceof PercentType) {
88                         int secPercent = ((PercentType) secondary).intValue();
89                         if (percent < secPercent) {
90                             percent = secPercent;
91                         }
92                     }
93                 }
94                 posKind1 = posKindCoords.ordinal();
95                 position1 = MAX_SHADE - (int) Math.round((double) percent / 100 * MAX_SHADE);
96                 break;
97
98             case SECONDARY_POSITION:
99                 /*
100                  * Secondary, blackout shade a 'Duolite' shade: => INVERTED
101                  * Secondary, upper rail of a dual action shade: => NOT INVERTED
102                  */
103                 posKind1 = posKindCoords.ordinal();
104                 if (shadeCapabilities.supportsBlackoutShade()) {
105                     position1 = MAX_SHADE - (int) Math.round((double) percent / 100 * MAX_SHADE);
106                 } else {
107                     position1 = (int) Math.round((double) percent / 100 * MAX_SHADE);
108                 }
109                 break;
110
111             case VANE_TILT_POSITION:
112                 /*
113                  * Vane angle of the primary rail of a bottom-up single action shade: => NOT INVERTED
114                  */
115                 posKind1 = posKindCoords.ordinal();
116                 int max = shadeCapabilities.supportsTilt180() ? MAX_SHADE : MAX_VANE;
117                 position1 = (int) Math.round((double) percent / 100 * max);
118                 break;
119
120             default:
121                 posKind1 = CoordinateSystem.NONE.ordinal();
122                 position1 = 0;
123         }
124     }
125
126     /**
127      * Get the shade's position1 State for the given actuator class resp. coordinate system.
128      *
129      * @param shadeCapabilities the shade Thing capabilities.
130      * @param posKindCoords the actuator class (coordinate system) whose state is to be returned.
131      * @return the State (or UNDEF if not available).
132      */
133     private State getPosition1(Capabilities shadeCapabilities, CoordinateSystem posKindCoords) {
134         switch (posKindCoords) {
135             case PRIMARY_POSITION:
136                 /*
137                  * Primary rail of a bottom-up shade, or lower rail of a dual action shade: => INVERTED
138                  */
139                 if (posKindCoords.equals(posKind1)) {
140                     return new PercentType(100 - (int) Math.round((double) position1 / MAX_SHADE * 100));
141                 }
142                 if (VANE_TILT_POSITION.equals(posKind1) && shadeCapabilities.supportsTiltOnClosed()) {
143                     return PercentType.HUNDRED;
144                 }
145                 if (SECONDARY_POSITION.equals(posKind1) && shadeCapabilities.supportsBlackoutShade()) {
146                     return PercentType.HUNDRED;
147                 }
148                 break;
149
150             case SECONDARY_POSITION:
151                 /*
152                  * Secondary, blackout shade a 'Duolite' shade: => INVERTED
153                  * Secondary, upper rail of a dual action shade: => NOT INVERTED
154                  */
155                 if (posKindCoords.equals(posKind1)) {
156                     if (shadeCapabilities.supportsBlackoutShade()) {
157                         return new PercentType(100 - (int) Math.round((double) position1 / MAX_SHADE * 100));
158                     }
159                     return new PercentType((int) Math.round((double) position1 / MAX_SHADE * 100));
160                 }
161                 if (PRIMARY_POSITION.equals(posKind1) && shadeCapabilities.supportsBlackoutShade()) {
162                     return PercentType.ZERO;
163                 }
164                 break;
165
166             case VANE_TILT_POSITION:
167                 /*
168                  * Vane angle of the primary rail of a bottom-up single action shade: => NOT INVERTED
169                  *
170                  * If the shades are not open, the vane position is undefined; if the the shades
171                  * are exactly open then the vanes are at zero; otherwise return the actual vane
172                  * position itself
173                  *
174                  * note: sometimes the hub may return a value of position1 > MAX_VANE (seems to
175                  * be a bug in the hub) so we avoid an out of range exception via the Math.min()
176                  * function below..
177                  */
178                 if (posKindCoords.equals(posKind1)) {
179                     int max = shadeCapabilities.supportsTilt180() ? MAX_SHADE : MAX_VANE;
180                     return new PercentType((int) Math.round((double) Math.min(position1, max) / max * 100));
181                 }
182                 if (PRIMARY_POSITION.equals(posKind1) && shadeCapabilities.supportsTiltOnClosed()) {
183                     return position1 != 0 ? UnDefType.UNDEF : PercentType.ZERO;
184                 }
185                 break;
186
187             case ERROR_UNKNOWN:
188             case NONE:
189                 // fall through, return UNDEF
190         }
191         return UnDefType.UNDEF;
192     }
193
194     /**
195      * Set the shade's position2 value for the given actuator class resp. coordinate system.
196      *
197      * @param shadeCapabilities the shade Thing capabilities.
198      * @param posKindCoords the actuator class (coordinate system) whose state is to be changed.
199      * @param percent the new position value.
200      */
201     private void setPosition2(Capabilities shadeCapabilities, CoordinateSystem posKindCoords, int percent) {
202         switch (posKindCoords) {
203             case PRIMARY_POSITION:
204                 /*
205                  * Primary rail of a bottom-up shade, or lower rail of a dual action shade: => INVERTED
206                  */
207                 posKind2 = posKindCoords.ordinal();
208                 position2 = Integer.valueOf(MAX_SHADE - (int) Math.round((double) percent / 100 * MAX_SHADE));
209                 break;
210
211             case SECONDARY_POSITION:
212                 /*
213                  * Secondary, upper rail of a dual action shade: => NOT INVERTED
214                  */
215                 if (shadeCapabilities.supportsPrimary() && shadeCapabilities.supportsSecondary()) {
216                     // on dual rail shades constrain percent to not move the upper rail below the lower
217                     State primary = getState(shadeCapabilities, PRIMARY_POSITION);
218                     if (primary instanceof PercentType) {
219                         int primaryPercent = ((PercentType) primary).intValue();
220                         if (percent > primaryPercent) {
221                             percent = primaryPercent;
222                         }
223                     }
224                 }
225                 posKind2 = posKindCoords.ordinal();
226                 position2 = Integer.valueOf((int) Math.round((double) percent / 100 * MAX_SHADE));
227                 break;
228
229             case VANE_TILT_POSITION:
230                 posKind2 = posKindCoords.ordinal();
231                 int max = shadeCapabilities.supportsTilt180() ? MAX_SHADE : MAX_VANE;
232                 position2 = Integer.valueOf((int) Math.round((double) percent / 100 * max));
233                 break;
234
235             default:
236                 posKind2 = null;
237                 position2 = null;
238         }
239     }
240
241     /**
242      * Get the shade's position2 State for the given actuator class resp. coordinate system.
243      *
244      * @param shadeCapabilities the shade Thing capabilities.
245      * @param posKindCoords the actuator class (coordinate system) whose state is to be returned.
246      * @return the State (or UNDEF if not available).
247      */
248     private State getPosition2(Capabilities shadeCapabilities, CoordinateSystem posKindCoords) {
249         Integer posKind2 = this.posKind2;
250         Integer position2 = this.position2;
251
252         if (position2 == null || posKind2 == null) {
253             return UnDefType.UNDEF;
254         }
255
256         switch (posKindCoords) {
257             case PRIMARY_POSITION:
258                 /*
259                  * Primary rail of a bottom-up shade, or lower rail of a dual action shade: => INVERTED
260                  */
261                 if (posKindCoords.equals(posKind2)) {
262                     return new PercentType(100 - (int) Math.round(position2.doubleValue() / MAX_SHADE * 100));
263                 }
264                 break;
265
266             case SECONDARY_POSITION:
267                 /*
268                  * Secondary, upper rail of a dual action shade: => NOT INVERTED
269                  */
270                 if (posKindCoords.equals(posKind2)) {
271                     return new PercentType((int) Math.round(position2.doubleValue() / MAX_SHADE * 100));
272                 }
273                 break;
274
275             /*
276              * Vane angle of the primary rail of a bottom-up single action shade: => NOT INVERTED
277              */
278             case VANE_TILT_POSITION:
279                 if (posKindCoords.equals(posKind2)) {
280                     int max = shadeCapabilities.supportsTilt180() ? MAX_SHADE : MAX_VANE;
281                     return new PercentType((int) Math.round((double) Math.min(position2.intValue(), max) / max * 100));
282                 }
283                 break;
284
285             case ERROR_UNKNOWN:
286             case NONE:
287                 // fall through, return UNDEF
288         }
289         return UnDefType.UNDEF;
290     }
291
292     /**
293      * Detect if the ShadePosition has a posKindN value indicating potential support for a secondary rail.
294      *
295      * @return true if the ShadePosition supports a secondary rail.
296      */
297     public boolean secondaryRailDetected() {
298         return SECONDARY_POSITION.equals(posKind1) || SECONDARY_POSITION.equals(posKind2);
299     }
300
301     /**
302      * Detect if the ShadePosition has both a posKindN value indicating potential support for tilt, AND a posKindN
303      * indicating support for a primary rail. i.e. it potentially supports tilt anywhere functionality.
304      *
305      * @return true if potential support for tilt anywhere functionality was detected.
306      */
307     public boolean tiltAnywhereDetected() {
308         return ((PRIMARY_POSITION.equals(posKind1)) && (VANE_TILT_POSITION.equals(posKind2))
309                 || ((PRIMARY_POSITION.equals(posKind2) && (VANE_TILT_POSITION.equals(posKind1)))));
310     }
311
312     /**
313      * Set the shade's position for the given actuator class resp. coordinate system.
314      *
315      * @param shadeCapabilities the shade Thing capabilities.
316      * @param posKindCoords the actuator class (coordinate system) whose state is to be changed.
317      * @param percent the new position value.
318      * @return this object.
319      */
320     public ShadePosition setPosition(Capabilities shadeCapabilities, CoordinateSystem posKindCoords, int percent) {
321         logger.trace("setPosition(): capabilities={}, coords={}, percent={}", shadeCapabilities, posKindCoords,
322                 percent);
323         // if necessary swap the order of position1 and position2
324         if (PRIMARY_POSITION.equals(posKind2) && !PRIMARY_POSITION.equals(posKind1)) {
325             final Integer posKind2Temp = posKind2;
326             final Integer position2Temp = position2;
327             posKind2 = Integer.valueOf(posKind1);
328             position2 = Integer.valueOf(position1);
329             posKind1 = posKind2Temp != null ? posKind2Temp.intValue() : NONE.ordinal();
330             position1 = position2Temp != null ? position2Temp.intValue() : 0;
331         }
332
333         // delete position2 if it has an invalid position kind
334         if (ERROR_UNKNOWN.equals(posKind2) || NONE.equals(posKind2)) {
335             posKind2 = null;
336             position2 = null;
337         }
338
339         // logic to set either position1 or position2
340         switch (posKindCoords) {
341             case PRIMARY_POSITION:
342                 if (shadeCapabilities.supportsPrimary()) {
343                     setPosition1(shadeCapabilities, posKindCoords, percent);
344                 }
345                 break;
346
347             case SECONDARY_POSITION:
348                 if (shadeCapabilities.supportsSecondary()) {
349                     if (shadeCapabilities.supportsPrimary()) {
350                         setPosition2(shadeCapabilities, posKindCoords, percent);
351                     } else {
352                         setPosition1(shadeCapabilities, posKindCoords, percent);
353                     }
354                 } else if (shadeCapabilities.supportsBlackoutShade()) {
355                     setPosition1(shadeCapabilities, posKindCoords, percent);
356                 }
357                 break;
358
359             case VANE_TILT_POSITION:
360                 if (shadeCapabilities.supportsPrimary()) {
361                     if (shadeCapabilities.supportsTiltOnClosed()) {
362                         setPosition1(shadeCapabilities, posKindCoords, percent);
363                     } else if (shadeCapabilities.supportsTiltAnywhere()) {
364                         setPosition2(shadeCapabilities, posKindCoords, percent);
365                     }
366                 } else if (shadeCapabilities.supportsTiltAnywhere()) {
367                     setPosition1(shadeCapabilities, posKindCoords, percent);
368                 }
369                 break;
370
371             case ERROR_UNKNOWN:
372             case NONE:
373                 // fall through, do nothing
374         }
375         return this;
376     }
377 }