]> git.basschouten.com Git - openhab-addons.git/blob
381462af9a8586b4ea9c0194f4e8a420dc936af9
[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.hdpowerview.internal.dto;
14
15 import static org.openhab.binding.hdpowerview.internal.dto.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.supportsSecondaryOverlapped()) {
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.supportsSecondaryOverlapped()) {
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.supportsSecondaryOverlapped()) {
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 (!SECONDARY_POSITION.equals(posKind1) && shadeCapabilities.supportsSecondaryOverlapped()) {
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                 if (SECONDARY_POSITION.equals(posKind1) && shadeCapabilities.supportsSecondaryOverlapped()
186                         && shadeCapabilities.supportsTiltOnClosed()) {
187                     return PercentType.HUNDRED;
188                 }
189                 break;
190
191             case ERROR_UNKNOWN:
192             case NONE:
193                 // fall through, return UNDEF
194         }
195         return UnDefType.UNDEF;
196     }
197
198     /**
199      * Set the shade's position2 value for the given actuator class resp. coordinate system.
200      *
201      * @param shadeCapabilities the shade Thing capabilities.
202      * @param posKindCoords the actuator class (coordinate system) whose state is to be changed.
203      * @param percent the new position value.
204      */
205     private void setPosition2(Capabilities shadeCapabilities, CoordinateSystem posKindCoords, int percent) {
206         switch (posKindCoords) {
207             case PRIMARY_POSITION:
208                 /*
209                  * Primary rail of a bottom-up shade, or lower rail of a dual action shade: => INVERTED
210                  */
211                 posKind2 = posKindCoords.ordinal();
212                 position2 = Integer.valueOf(MAX_SHADE - (int) Math.round((double) percent / 100 * MAX_SHADE));
213                 break;
214
215             case SECONDARY_POSITION:
216                 /*
217                  * Secondary, upper rail of a dual action shade: => NOT INVERTED
218                  */
219                 if (shadeCapabilities.supportsPrimary() && shadeCapabilities.supportsSecondary()) {
220                     // on dual rail shades constrain percent to not move the upper rail below the lower
221                     State primary = getState(shadeCapabilities, PRIMARY_POSITION);
222                     if (primary instanceof PercentType) {
223                         int primaryPercent = ((PercentType) primary).intValue();
224                         if (percent > primaryPercent) {
225                             percent = primaryPercent;
226                         }
227                     }
228                 }
229                 posKind2 = posKindCoords.ordinal();
230                 position2 = Integer.valueOf((int) Math.round((double) percent / 100 * MAX_SHADE));
231                 break;
232
233             case VANE_TILT_POSITION:
234                 posKind2 = posKindCoords.ordinal();
235                 int max = shadeCapabilities.supportsTilt180() ? MAX_SHADE : MAX_VANE;
236                 position2 = Integer.valueOf((int) Math.round((double) percent / 100 * max));
237                 break;
238
239             default:
240                 posKind2 = null;
241                 position2 = null;
242         }
243     }
244
245     /**
246      * Get the shade's position2 State for the given actuator class resp. coordinate system.
247      *
248      * @param shadeCapabilities the shade Thing capabilities.
249      * @param posKindCoords the actuator class (coordinate system) whose state is to be returned.
250      * @return the State (or UNDEF if not available).
251      */
252     private State getPosition2(Capabilities shadeCapabilities, CoordinateSystem posKindCoords) {
253         Integer posKind2 = this.posKind2;
254         Integer position2 = this.position2;
255
256         if (position2 == null || posKind2 == null) {
257             return UnDefType.UNDEF;
258         }
259
260         switch (posKindCoords) {
261             case PRIMARY_POSITION:
262                 /*
263                  * Primary rail of a bottom-up shade, or lower rail of a dual action shade: => INVERTED
264                  */
265                 if (posKindCoords.equals(posKind2)) {
266                     return new PercentType(100 - (int) Math.round(position2.doubleValue() / MAX_SHADE * 100));
267                 }
268                 break;
269
270             case SECONDARY_POSITION:
271                 /*
272                  * Secondary, upper rail of a dual action shade: => NOT INVERTED
273                  */
274                 if (posKindCoords.equals(posKind2)) {
275                     return new PercentType((int) Math.round(position2.doubleValue() / MAX_SHADE * 100));
276                 }
277                 break;
278
279             /*
280              * Vane angle of the primary rail of a bottom-up single action shade: => NOT INVERTED
281              */
282             case VANE_TILT_POSITION:
283                 if (posKindCoords.equals(posKind2)) {
284                     int max = shadeCapabilities.supportsTilt180() ? MAX_SHADE : MAX_VANE;
285                     return new PercentType((int) Math.round((double) Math.min(position2.intValue(), max) / max * 100));
286                 }
287                 break;
288
289             case ERROR_UNKNOWN:
290             case NONE:
291                 // fall through, return UNDEF
292         }
293         return UnDefType.UNDEF;
294     }
295
296     /**
297      * Detect if the ShadePosition has a posKindN value indicating potential support for a secondary rail.
298      *
299      * @return true if the ShadePosition supports a secondary rail.
300      */
301     public boolean secondaryRailDetected() {
302         return SECONDARY_POSITION.equals(posKind1) || SECONDARY_POSITION.equals(posKind2);
303     }
304
305     /**
306      * Detect if the ShadePosition has both a posKindN value indicating potential support for tilt, AND a posKindN
307      * indicating support for a primary rail. i.e. it potentially supports tilt anywhere functionality.
308      *
309      * @return true if potential support for tilt anywhere functionality was detected.
310      */
311     public boolean tiltAnywhereDetected() {
312         return ((PRIMARY_POSITION.equals(posKind1)) && (VANE_TILT_POSITION.equals(posKind2))
313                 || ((PRIMARY_POSITION.equals(posKind2) && (VANE_TILT_POSITION.equals(posKind1)))));
314     }
315
316     /**
317      * Set the shade's position for the given actuator class resp. coordinate system.
318      *
319      * @param shadeCapabilities the shade Thing capabilities.
320      * @param posKindCoords the actuator class (coordinate system) whose state is to be changed.
321      * @param percent the new position value.
322      * @return this object.
323      */
324     public ShadePosition setPosition(Capabilities shadeCapabilities, CoordinateSystem posKindCoords, int percent) {
325         logger.trace("setPosition(): capabilities={}, coords={}, percent={}", shadeCapabilities, posKindCoords,
326                 percent);
327         // if necessary swap the order of position1 and position2
328         if (PRIMARY_POSITION.equals(posKind2) && !PRIMARY_POSITION.equals(posKind1)) {
329             final Integer posKind2Temp = posKind2;
330             final Integer position2Temp = position2;
331             posKind2 = Integer.valueOf(posKind1);
332             position2 = Integer.valueOf(position1);
333             posKind1 = posKind2Temp != null ? posKind2Temp.intValue() : NONE.ordinal();
334             position1 = position2Temp != null ? position2Temp.intValue() : 0;
335         }
336
337         // delete position2 if it has an invalid position kind
338         if (ERROR_UNKNOWN.equals(posKind2) || NONE.equals(posKind2)) {
339             posKind2 = null;
340             position2 = null;
341         }
342
343         // logic to set either position1 or position2
344         switch (posKindCoords) {
345             case PRIMARY_POSITION:
346                 if (shadeCapabilities.supportsPrimary()) {
347                     setPosition1(shadeCapabilities, posKindCoords, percent);
348                 }
349                 break;
350
351             case SECONDARY_POSITION:
352                 if (shadeCapabilities.supportsSecondary()) {
353                     if (shadeCapabilities.supportsPrimary()) {
354                         setPosition2(shadeCapabilities, posKindCoords, percent);
355                     } else {
356                         setPosition1(shadeCapabilities, posKindCoords, percent);
357                     }
358                 } else if (shadeCapabilities.supportsSecondaryOverlapped()) {
359                     setPosition1(shadeCapabilities, posKindCoords, percent);
360                 }
361                 break;
362
363             case VANE_TILT_POSITION:
364                 if (shadeCapabilities.supportsPrimary()) {
365                     if (shadeCapabilities.supportsTiltOnClosed()) {
366                         setPosition1(shadeCapabilities, posKindCoords, percent);
367                     } else if (shadeCapabilities.supportsTiltAnywhere()) {
368                         setPosition2(shadeCapabilities, posKindCoords, percent);
369                     }
370                 } else if (shadeCapabilities.supportsTiltAnywhere()) {
371                     setPosition1(shadeCapabilities, posKindCoords, percent);
372                 }
373                 break;
374
375             case ERROR_UNKNOWN:
376             case NONE:
377                 // fall through, do nothing
378         }
379         return this;
380     }
381 }