]> git.basschouten.com Git - openhab-addons.git/blob
c9df727b6cf5734e24f4b1ae3a73371ef8177a75
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2021 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_ZERO_IS_CLOSED:
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_ZERO_IS_OPEN);
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_ZERO_IS_OPEN:
99                 /*
100                  * Secondary, upper rail of a dual action shade: => NOT INVERTED
101                  */
102                 posKind1 = posKindCoords.ordinal();
103                 position1 = (int) Math.round((double) percent / 100 * MAX_SHADE);
104                 break;
105
106             case VANE_TILT_COORDS:
107                 /*
108                  * Vane angle of the primary rail of a bottom-up single action shade: => NOT INVERTED
109                  */
110                 posKind1 = posKindCoords.ordinal();
111                 int max = shadeCapabilities.supportsTilt180() ? MAX_SHADE : MAX_VANE;
112                 position1 = (int) Math.round((double) percent / 100 * max);
113                 break;
114
115             default:
116                 posKind1 = CoordinateSystem.NONE.ordinal();
117                 position1 = 0;
118         }
119     }
120
121     /**
122      * Get the shade's position1 State for the given actuator class resp. coordinate system.
123      *
124      * @param shadeCapabilities the shade Thing capabilities.
125      * @param posKindCoords the actuator class (coordinate system) whose state is to be returned.
126      * @return the State (or UNDEF if not available).
127      */
128     private State getPosition1(Capabilities shadeCapabilities, CoordinateSystem posKindCoords) {
129         switch (posKindCoords) {
130             case PRIMARY_ZERO_IS_CLOSED:
131                 /*
132                  * Primary rail of a bottom-up shade, or lower rail of a dual action shade: => INVERTED
133                  */
134                 if (posKindCoords.equals(posKind1)) {
135                     return new PercentType(100 - (int) Math.round((double) position1 / MAX_SHADE * 100));
136                 }
137                 if (VANE_TILT_COORDS.equals(posKind1) && shadeCapabilities.supportsTiltOnClosed()) {
138                     return PercentType.HUNDRED;
139                 }
140                 break;
141
142             case SECONDARY_ZERO_IS_OPEN:
143                 /*
144                  * Secondary, upper rail of a dual action shade: => NOT INVERTED
145                  */
146                 if (posKindCoords.equals(posKind1)) {
147                     return new PercentType((int) Math.round((double) position1 / MAX_SHADE * 100));
148                 }
149                 break;
150
151             case VANE_TILT_COORDS:
152                 /*
153                  * Vane angle of the primary rail of a bottom-up single action shade: => NOT INVERTED
154                  *
155                  * If the shades are not open, the vane position is undefined; if the the shades
156                  * are exactly open then the vanes are at zero; otherwise return the actual vane
157                  * position itself
158                  *
159                  * note: sometimes the hub may return a value of position1 > MAX_VANE (seems to
160                  * be a bug in the hub) so we avoid an out of range exception via the Math.min()
161                  * function below..
162                  */
163                 if (posKindCoords.equals(posKind1)) {
164                     int max = shadeCapabilities.supportsTilt180() ? MAX_SHADE : MAX_VANE;
165                     return new PercentType((int) Math.round((double) Math.min(position1, max) / max * 100));
166                 }
167                 if (PRIMARY_ZERO_IS_CLOSED.equals(posKind1) && shadeCapabilities.supportsTiltOnClosed()) {
168                     return position1 != 0 ? UnDefType.UNDEF : PercentType.ZERO;
169                 }
170                 break;
171
172             case ERROR_UNKNOWN:
173             case NONE:
174                 // fall through, return UNDEF
175         }
176         return UnDefType.UNDEF;
177     }
178
179     /**
180      * Set the shade's position2 value for the given actuator class resp. coordinate system.
181      *
182      * @param shadeCapabilities the shade Thing capabilities.
183      * @param posKindCoords the actuator class (coordinate system) whose state is to be changed.
184      * @param percent the new position value.
185      */
186     private void setPosition2(Capabilities shadeCapabilities, CoordinateSystem posKindCoords, int percent) {
187         switch (posKindCoords) {
188             case PRIMARY_ZERO_IS_CLOSED:
189                 /*
190                  * Primary rail of a bottom-up shade, or lower rail of a dual action shade: => INVERTED
191                  */
192                 posKind2 = posKindCoords.ordinal();
193                 position2 = Integer.valueOf(MAX_SHADE - (int) Math.round((double) percent / 100 * MAX_SHADE));
194                 break;
195
196             case SECONDARY_ZERO_IS_OPEN:
197                 /*
198                  * Secondary, upper rail of a dual action shade: => NOT INVERTED
199                  */
200                 if (shadeCapabilities.supportsPrimary() && shadeCapabilities.supportsSecondary()) {
201                     // on dual rail shades constrain percent to not move the upper rail below the lower
202                     State primary = getState(shadeCapabilities, PRIMARY_ZERO_IS_CLOSED);
203                     if (primary instanceof PercentType) {
204                         int primaryPercent = ((PercentType) primary).intValue();
205                         if (percent > primaryPercent) {
206                             percent = primaryPercent;
207                         }
208                     }
209                 }
210                 posKind2 = posKindCoords.ordinal();
211                 position2 = Integer.valueOf((int) Math.round((double) percent / 100 * MAX_SHADE));
212                 break;
213
214             case VANE_TILT_COORDS:
215                 posKind2 = posKindCoords.ordinal();
216                 int max = shadeCapabilities.supportsTilt180() ? MAX_SHADE : MAX_VANE;
217                 position2 = Integer.valueOf((int) Math.round((double) percent / 100 * max));
218                 break;
219
220             default:
221                 posKind2 = null;
222                 position2 = null;
223         }
224     }
225
226     /**
227      * Get the shade's position2 State for the given actuator class resp. coordinate system.
228      *
229      * @param shadeCapabilities the shade Thing capabilities.
230      * @param posKindCoords the actuator class (coordinate system) whose state is to be returned.
231      * @return the State (or UNDEF if not available).
232      */
233     private State getPosition2(Capabilities shadeCapabilities, CoordinateSystem posKindCoords) {
234         Integer posKind2 = this.posKind2;
235         Integer position2 = this.position2;
236
237         if (position2 == null || posKind2 == null) {
238             return UnDefType.UNDEF;
239         }
240
241         switch (posKindCoords) {
242             case PRIMARY_ZERO_IS_CLOSED:
243                 /*
244                  * Primary rail of a bottom-up shade, or lower rail of a dual action shade: => INVERTED
245                  */
246                 if (posKindCoords.equals(posKind2)) {
247                     return new PercentType(100 - (int) Math.round(position2.doubleValue() / MAX_SHADE * 100));
248                 }
249                 break;
250
251             case SECONDARY_ZERO_IS_OPEN:
252                 /*
253                  * Secondary, upper rail of a dual action shade: => NOT INVERTED
254                  */
255                 if (posKindCoords.equals(posKind2)) {
256                     return new PercentType((int) Math.round(position2.doubleValue() / MAX_SHADE * 100));
257                 }
258                 break;
259
260             /*
261              * Vane angle of the primary rail of a bottom-up single action shade: => NOT INVERTED
262              *
263              * note: sometimes the hub may return a value of position1 > MAX_VANE (seems to
264              * be a bug in the hub) so we avoid an out of range exception via the Math.min()
265              * function below..
266              */
267             case VANE_TILT_COORDS:
268                 if (posKindCoords.equals(posKind2)) {
269                     int max = shadeCapabilities.supportsTilt180() ? MAX_SHADE : MAX_VANE;
270                     return new PercentType((int) Math.round((double) Math.min(position2.intValue(), max) / max * 100));
271                 }
272                 break;
273
274             case ERROR_UNKNOWN:
275             case NONE:
276                 // fall through, return UNDEF
277         }
278         return UnDefType.UNDEF;
279     }
280
281     /**
282      * Detect if the ShadePosition has a posKindN value indicating potential support for a secondary rail.
283      *
284      * @return true if the ShadePosition supports a secondary rail.
285      */
286     public boolean secondaryRailDetected() {
287         return SECONDARY_ZERO_IS_OPEN.equals(posKind1) || SECONDARY_ZERO_IS_OPEN.equals(posKind2);
288     }
289
290     /**
291      * Detect if the ShadePosition has both a posKindN value indicating potential support for tilt, AND a posKindN
292      * indicating support for a primary rail. i.e. it potentially supports tilt anywhere functionality.
293      *
294      * @return true if potential support for tilt anywhere functionality was detected.
295      */
296     public boolean tiltAnywhereDetected() {
297         return ((PRIMARY_ZERO_IS_CLOSED.equals(posKind1)) && (VANE_TILT_COORDS.equals(posKind2))
298                 || ((PRIMARY_ZERO_IS_CLOSED.equals(posKind2) && (VANE_TILT_COORDS.equals(posKind1)))));
299     }
300
301     /**
302      * Set the shade's position for the given actuator class resp. coordinate system.
303      *
304      * @param shadeCapabilities the shade Thing capabilities.
305      * @param posKindCoords the actuator class (coordinate system) whose state is to be changed.
306      * @param percent the new position value.
307      * @return this object.
308      */
309     public ShadePosition setPosition(Capabilities shadeCapabilities, CoordinateSystem posKindCoords, int percent) {
310         logger.trace("setPosition(): capabilities={}, coords={}, percent={}", shadeCapabilities, posKindCoords,
311                 percent);
312         // if necessary swap the order of position1 and position2
313         if (PRIMARY_ZERO_IS_CLOSED.equals(posKind2) && !PRIMARY_ZERO_IS_CLOSED.equals(posKind1)) {
314             final Integer posKind2Temp = posKind2;
315             final Integer position2Temp = position2;
316             posKind2 = Integer.valueOf(posKind1);
317             position2 = Integer.valueOf(position1);
318             posKind1 = posKind2Temp != null ? posKind2Temp.intValue() : NONE.ordinal();
319             position1 = position2Temp != null ? position2Temp.intValue() : 0;
320         }
321
322         // delete position2 if it has an invalid position kind
323         if (ERROR_UNKNOWN.equals(posKind2) || NONE.equals(posKind2)) {
324             posKind2 = null;
325             position2 = null;
326         }
327
328         // logic to set either position1 or position2
329         switch (posKindCoords) {
330             case PRIMARY_ZERO_IS_CLOSED:
331                 if (shadeCapabilities.supportsPrimary()) {
332                     setPosition1(shadeCapabilities, posKindCoords, percent);
333                 }
334                 break;
335
336             case SECONDARY_ZERO_IS_OPEN:
337                 if (shadeCapabilities.supportsSecondary()) {
338                     if (shadeCapabilities.supportsPrimary()) {
339                         setPosition2(shadeCapabilities, posKindCoords, percent);
340                     } else {
341                         setPosition1(shadeCapabilities, posKindCoords, percent);
342                     }
343                 }
344                 break;
345
346             case VANE_TILT_COORDS:
347                 if (shadeCapabilities.supportsPrimary()) {
348                     if (shadeCapabilities.supportsTiltOnClosed()) {
349                         setPosition1(shadeCapabilities, posKindCoords, percent);
350                     } else if (shadeCapabilities.supportsTiltAnywhere()) {
351                         setPosition2(shadeCapabilities, posKindCoords, percent);
352                     }
353                 } else if (shadeCapabilities.supportsTiltAnywhere()) {
354                     setPosition1(shadeCapabilities, posKindCoords, percent);
355                 }
356                 break;
357
358             case ERROR_UNKNOWN:
359             case NONE:
360                 // fall through, do nothing
361         }
362         return this;
363     }
364 }