]> git.basschouten.com Git - openhab-addons.git/blob
85e0d8e98cddd853d03d0a64bbbecfefd74f4a04
[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.database;
14
15 import java.util.Arrays;
16 import java.util.Map;
17 import java.util.function.Function;
18 import java.util.stream.Collectors;
19
20 import org.eclipse.jdt.annotation.NonNullByDefault;
21 import org.eclipse.jdt.annotation.Nullable;
22 import org.slf4j.Logger;
23 import org.slf4j.LoggerFactory;
24
25 /**
26  * Class containing the database of all known shade 'types' and their respective 'capabilities'.
27  *
28  * If user systems detect shade types that are not in the database, then this class can issue logger warning messages
29  * indicating such absence, and prompting the user to report it to developers so that the database and the respective
30  * binding functionality can (hopefully) be extended over time.
31  *
32  * @author Andrew Fiddian-Green - Initial Contribution
33  */
34 @NonNullByDefault
35 public class ShadeCapabilitiesDatabase {
36
37     private final Logger logger = LoggerFactory.getLogger(ShadeCapabilitiesDatabase.class);
38
39     /*
40      * Database of known shade capabilities.
41      */
42     private static final Map<Integer, Capabilities> CAPABILITIES_DATABASE = Arrays.asList(
43     // @formatter:off
44             new Capabilities(0).primary()                                                       .text("Bottom Up"),
45             new Capabilities(1).primary()        .tiltOnClosed()                                .text("Bottom Up Tilt 90°"),
46             new Capabilities(2).primary()        .tiltAnywhere().tilt180()                      .text("Bottom Up Tilt 180°"),
47             new Capabilities(3).primary()        .tiltAnywhere().tilt180()                      .text("Vertical Tilt 180°"),
48             new Capabilities(4).primary()                                                       .text("Vertical"),
49             new Capabilities(5)                  .tiltAnywhere().tilt180()                      .text("Tilt Only 180°"),
50             new Capabilities(6).primaryInverted()                                               .text("Top Down"),
51             new Capabilities(7).primary()                                 .secondary()          .text("Top Down Bottom Up"),
52             new Capabilities(8).primary()                                 .secondaryOverlapped().text("Dual Overlapped"),
53             // note: for the following capabilities entry the 'tiltOnClosed()' applies to the primary shade
54             new Capabilities(9).primary()        .tiltOnClosed()          .secondaryOverlapped().text("Dual Overlapped Tilt 90°"),
55     // @formatter:on
56             new Capabilities()).stream().collect(Collectors.toMap(Capabilities::getValue, Function.identity()));
57
58     /*
59      * Database of known shade types and corresponding capabilities.
60      */
61     private static final Map<Integer, Type> TYPE_DATABASE = Arrays.asList(
62     // @formatter:off
63             new Type( 1).capabilities(0).text("Roller / Solar"),
64             new Type( 4).capabilities(0).text("Roman"),
65             new Type( 5).capabilities(0).text("Bottom Up"),
66             new Type( 6).capabilities(0).text("Duette"),
67             new Type( 7).capabilities(6).text("Top Down"),
68             new Type( 8).capabilities(7).text("Duette Top Down Bottom Up"),
69             new Type( 9).capabilities(7).text("Duette DuoLite Top Down Bottom Up"),
70             new Type(18).capabilities(1).text("Pirouette"),
71             new Type(23).capabilities(1).text("Silhouette"),
72             new Type(31).capabilities(0).text("Vignette"),
73             new Type(33).capabilities(7).text("Duette Architella"),
74             new Type(38).capabilities(9).text("Silhouette Duolite"),
75             new Type(42).capabilities(0).text("M25T Roller Blind"),
76             new Type(43).capabilities(1).text("Facette"),
77             // note: the following shade type has the functionality of a capabilities 1 shade
78             new Type(44).capabilities(0).text("Twist").capabilitiesOverride(1),
79             new Type(47).capabilities(7).text("Pleated Top Down Bottom Up"),
80             new Type(49).capabilities(0).text("AC Roller"),
81             new Type(51).capabilities(2).text("Venetian"),
82             new Type(54).capabilities(3).text("Vertical Slats Left Stack"),
83             new Type(55).capabilities(3).text("Vertical Slats Right Stack"),
84             new Type(56).capabilities(3).text("Vertical Slats Split Stack"),
85             new Type(62).capabilities(2).text("Venetian"),
86             new Type(65).capabilities(8).text("Vignette Duolite"),
87             new Type(66).capabilities(5).text("Shutter"),
88             new Type(69).capabilities(4).text("Curtain Left Stack"),
89             new Type(70).capabilities(4).text("Curtain Right Stack"),
90             new Type(71).capabilities(4).text("Curtain Split Stack"),
91             new Type(79).capabilities(8).text("Duolite Lift"),
92     // @formatter:on
93             new Type()).stream().collect(Collectors.toMap(Type::getValue, Function.identity()));
94
95     /**
96      * Base class that is extended by Type and Capabilities classes.
97      *
98      * @author Andrew Fiddian-Green - Initial Contribution
99      */
100     private static class Base {
101         protected int intValue = -1;
102         protected String text = "-- not in database --";
103
104         public Integer getValue() {
105             return intValue;
106         }
107
108         @Override
109         public String toString() {
110             return String.format("%s (%d)", text, intValue);
111         }
112     }
113
114     /**
115      * Describes a shade type entry in the database; implements 'capabilities' parameter.
116      *
117      * @author Andrew Fiddian-Green - Initial Contribution
118      */
119     public static class Type extends Base {
120         private int capabilities = -1;
121         private int capabilitiesOverride = -1;
122
123         protected Type() {
124         }
125
126         protected Type(int type) {
127             intValue = type;
128         }
129
130         protected Type text(String text) {
131             this.text = text;
132             return this;
133         }
134
135         protected Type capabilities(int capabilities) {
136             this.capabilities = capabilities;
137             return this;
138         }
139
140         protected Type capabilitiesOverride(int capabilitiesOverride) {
141             this.capabilitiesOverride = capabilitiesOverride;
142             return this;
143         }
144
145         /**
146          * Get shade types's 'capabilities'.
147          *
148          * @return 'capabilities'.
149          */
150         public int getCapabilities() {
151             return capabilities;
152         }
153
154         /**
155          * Get shade's type specific 'capabilities'.
156          *
157          * @return 'typeCapabilities'.
158          */
159         public int getCapabilitiesOverride() {
160             return capabilitiesOverride;
161         }
162     }
163
164     /**
165      * Describes a shade 'capabilities' entry in the database; adds properties indicating its supported functionality.
166      *
167      * @author Andrew Fiddian-Green - Initial Contribution
168      */
169     public static class Capabilities extends Base {
170         private boolean supportsPrimary;
171         private boolean supportsSecondary;
172         private boolean supportsTiltOnClosed;
173         private boolean supportsTiltAnywhere;
174         private boolean supportsSecondaryOverlapped;
175         private boolean primaryInverted;
176         private boolean tilt180Degrees;
177
178         public Capabilities() {
179         }
180
181         protected Capabilities secondaryOverlapped() {
182             supportsSecondaryOverlapped = true;
183             return this;
184         }
185
186         protected Capabilities(int capabilities) {
187             intValue = capabilities;
188         }
189
190         protected Capabilities text(String text) {
191             this.text = text;
192             return this;
193         }
194
195         protected Capabilities primary() {
196             supportsPrimary = true;
197             return this;
198         }
199
200         protected Capabilities tiltOnClosed() {
201             supportsTiltOnClosed = true;
202             return this;
203         }
204
205         protected Capabilities secondary() {
206             supportsSecondary = true;
207             return this;
208         }
209
210         protected Capabilities tiltAnywhere() {
211             supportsTiltAnywhere = true;
212             return this;
213         }
214
215         protected Capabilities primaryInverted() {
216             supportsPrimary = true;
217             primaryInverted = true;
218             return this;
219         }
220
221         protected Capabilities tilt180() {
222             tilt180Degrees = true;
223             return this;
224         }
225
226         /**
227          * Check if the Capabilities class instance supports a primary shade.
228          *
229          * @return true if it supports a primary shade.
230          */
231         public boolean supportsPrimary() {
232             return supportsPrimary;
233         }
234
235         /**
236          * Check if the Capabilities class instance supports a vane/tilt function (by means of a second motor).
237          *
238          * @return true if it supports a vane/tilt function (by means of a second motor).
239          */
240         public boolean supportsTiltAnywhere() {
241             return supportsTiltAnywhere;
242         }
243
244         /**
245          * Check if the Capabilities class instance supports a secondary shade.
246          *
247          * @return true if it supports a secondary shade.
248          */
249         public boolean supportsSecondary() {
250             return supportsSecondary;
251         }
252
253         /**
254          * Check if the Capabilities class instance if the primary shade is inverted.
255          *
256          * @return true if the primary shade is inverted.
257          */
258         public boolean isPrimaryInverted() {
259             return primaryInverted;
260         }
261
262         /**
263          * Check if the Capabilities class instance supports 'tilt on closed'.
264          *
265          * Note: Simple bottom up or vertical shades that do not have independent vane controls, can be tilted in a
266          * simple way, only when they are fully closed, by moving the shade motor a bit further.
267          *
268          * @return true if the it supports tilt on closed.
269          */
270         public boolean supportsTiltOnClosed() {
271             return supportsTiltOnClosed && !supportsTiltAnywhere;
272         }
273
274         /**
275          * Check if the Capabilities class instance supports 180 degrees tilt.
276          *
277          * @return true if the tilt range is 180 degrees.
278          */
279         public boolean supportsTilt180() {
280             return tilt180Degrees;
281         }
282
283         /**
284          * Check if the Capabilities class instance supports an overlapped secondary shade.
285          * e.g. a 'DuoLite' or blackout shade.
286          *
287          * @return true if the shade supports a secondary overlapped shade.
288          */
289         public boolean supportsSecondaryOverlapped() {
290             return supportsSecondaryOverlapped;
291         }
292     }
293
294     /**
295      * Determines if a given shade 'type' is in the database.
296      *
297      * @param type the shade 'type' parameter.
298      * @return true if the shade 'type' is known.
299      */
300     public boolean isTypeInDatabase(int type) {
301         return TYPE_DATABASE.containsKey(type);
302     }
303
304     /**
305      * Determines if a given 'capabilities' value is in the database.
306      *
307      * @param capabilities the shade 'capabilities' parameter
308      * @return true if the 'capabilities' value is known
309      */
310     public boolean isCapabilitiesInDatabase(int capabilities) {
311         return CAPABILITIES_DATABASE.containsKey(capabilities);
312     }
313
314     /**
315      * Return a Type class instance that corresponds to the given 'type' parameter.
316      *
317      * @param type the shade 'type' parameter.
318      * @return corresponding instance of Type class.
319      */
320     public Type getType(int type) {
321         return TYPE_DATABASE.getOrDefault(type, new Type());
322     }
323
324     /**
325      * Return a Capabilities class instance that corresponds to the given 'capabilitiesId' parameter. If the
326      * 'capabilitiesId' parameter is for a valid capabilities entry in the database, then that respective Capabilities
327      * class instance is returned. Otherwise a blank Capabilities class instance is returned.
328      *
329      * @param capabilitiesId the target capabilities Id.
330      * @return corresponding Capabilities class instance.
331      */
332     public Capabilities getCapabilities(@Nullable Integer capabilitiesId) {
333         return CAPABILITIES_DATABASE.getOrDefault(capabilitiesId != null ? capabilitiesId.intValue() : -1,
334                 new Capabilities());
335     }
336
337     /**
338      * Return a Capabilities class instance that corresponds to the given 'typeId' parameter.
339      * <p>
340      * <ul>
341      * <li>If the 'typeId' parameter is a valid type in the database, and it has a 'capabilitiesOverride' value, then an
342      * instance of the respective overridden Capabilities class is returned.
343      * <li>Otherwise if the 'capabilitiesId' parameter is for a valid capabilities entry in the database, then that
344      * respective Capabilities class instance is returned.
345      * <li>Otherwise if the type is a valid type in the database, then its 'capabilities' instance is returned.
346      * <li>Otherwise a default Capabilities '0' class instance is returned.
347      * </ul>
348      * <p>
349      *
350      * @param typeId the target shade type Id (to check if it has a 'capabilitiesOverride' value).
351      * @param capabilitiesId the target capabilities value (when type Id does not have a 'capabilitiesOverride').
352      * @return corresponding Capabilities class instance.
353      */
354     public Capabilities getCapabilities(int typeId, @Nullable Integer capabilitiesId) {
355         Type type = TYPE_DATABASE.getOrDefault(typeId, new Type());
356         // first try capabilitiesOverride for type Id
357         int targetCapabilities = type.getCapabilitiesOverride();
358         // then try capabilitiesId
359         if (targetCapabilities < 0 && capabilitiesId != null && isCapabilitiesInDatabase(capabilitiesId.intValue())) {
360             targetCapabilities = capabilitiesId.intValue();
361         }
362         // then try capabilities for typeId
363         if (targetCapabilities < 0) {
364             targetCapabilities = type.getCapabilities();
365         }
366         // fallback to default capabilities 0 (so at least something may work..)
367         if (targetCapabilities < 0) {
368             targetCapabilities = 0;
369         }
370         return getCapabilities(targetCapabilities);
371     }
372
373     private static final String REQUEST_DEVELOPERS_TO_UPDATE = " => Please request developers to update the database!";
374
375     /**
376      * Log a message indicating that 'type' is not in database.
377      *
378      * @param type
379      */
380     public void logTypeNotInDatabase(int type) {
381         logger.warn("The shade 'type:{}' is not in the database!{}", type, REQUEST_DEVELOPERS_TO_UPDATE);
382     }
383
384     /**
385      * Log a message indicating that 'capabilities' is not in database.
386      *
387      * @param capabilities
388      */
389     public void logCapabilitiesNotInDatabase(int type, int capabilities) {
390         logger.warn("The 'capabilities:{}' for shade 'type:{}' are not in the database!{}", capabilities, type,
391                 REQUEST_DEVELOPERS_TO_UPDATE);
392     }
393
394     /**
395      * Log a message indicating the type's capabilities and the passed capabilities are not equal.
396      *
397      * @param type
398      * @param capabilities
399      */
400     public void logCapabilitiesMismatch(int type, int capabilities) {
401         logger.warn("The 'capabilities:{}' reported by shade 'type:{}' don't match the database!{}", capabilities, type,
402                 REQUEST_DEVELOPERS_TO_UPDATE);
403     }
404
405     /**
406      * Log a message indicating that a shade's secondary/vanes support, as observed via its actual JSON payload, does
407      * not match the expected value as declared in its 'type' and 'capabilities'.
408      *
409      * @param propertyKey
410      * @param type
411      * @param capabilities
412      * @param propertyValue
413      */
414     public void logPropertyMismatch(String propertyKey, int type, int capabilities, boolean propertyValue) {
415         logger.warn(
416                 "The '{}:{}' property actually reported by shade 'type:{}' is different "
417                         + "than expected from its 'capabilities:{}' in the database!{}",
418                 propertyKey, propertyValue, type, capabilities, REQUEST_DEVELOPERS_TO_UPDATE);
419     }
420 }