2 * Copyright (c) 2010-2024 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
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
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.hdpowerview.internal.database;
15 import java.util.Arrays;
17 import java.util.function.Function;
18 import java.util.stream.Collectors;
20 import org.eclipse.jdt.annotation.NonNullByDefault;
21 import org.eclipse.jdt.annotation.Nullable;
22 import org.slf4j.Logger;
23 import org.slf4j.LoggerFactory;
26 * Class containing the database of all known shade 'types' and their respective 'capabilities'.
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.
32 * @author Andrew Fiddian-Green - Initial Contribution
35 public class ShadeCapabilitiesDatabase {
37 private final Logger logger = LoggerFactory.getLogger(ShadeCapabilitiesDatabase.class);
40 * Database of known shade capabilities.
42 private static final Map<Integer, Capabilities> CAPABILITIES_DATABASE = Arrays.asList(
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() .text("Vertical"),
48 new Capabilities( 4).primary() .tiltAnywhere().tilt180() .text("Vertical Tilt 180°"),
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 new Capabilities(10).primary() .tiltOnClosed().tilt180().secondaryOverlapped().text("Dual Overlapped Tilt 180°"),
57 new Capabilities()).stream().collect(Collectors.toMap(Capabilities::getValue, Function.identity()));
60 * Database of known shade types and corresponding capabilities.
62 private static final Map<Integer, Type> TYPE_DATABASE = Arrays.asList(
64 new Type( 1).capabilities(0).text("Roller / Solar"),
65 new Type( 4).capabilities(0).text("Roman"),
66 new Type( 5).capabilities(0).text("Bottom Up"),
67 new Type( 6).capabilities(0).text("Duette"),
68 new Type( 7).capabilities(6).text("Top Down"),
69 new Type( 8).capabilities(7).text("Duette Top Down Bottom Up"),
70 new Type( 9).capabilities(7).text("Duette DuoLite Top Down Bottom Up"),
71 new Type(10).capabilities(0).text("Duette/Applause Skylift"),
72 new Type(18).capabilities(1).text("Pirouette"),
73 new Type(23).capabilities(1).text("Silhouette"),
74 new Type(26).capabilities(3).text("Skyline Panel, Left Stack"),
75 new Type(27).capabilities(3).text("Skyline Panel, Right Stack"),
76 new Type(28).capabilities(3).text("Skyline Panel, Split Stack"),
77 new Type(31).capabilities(0).text("Vignette"),
78 new Type(33).capabilities(7).text("Duette Architella"),
79 new Type(38).capabilities(9).text("Silhouette Duolite"),
80 new Type(42).capabilities(0).text("M25T Roller Blind"),
81 new Type(43).capabilities(1).text("Facette"),
82 // note: the following shade type has the functionality of a capabilities 1 shade
83 new Type(44).capabilities(0).text("Twist").capabilitiesOverride(1),
84 new Type(47).capabilities(7).text("Pleated Top Down Bottom Up"),
85 new Type(49).capabilities(0).text("AC Roller"),
86 new Type(51).capabilities(2).text("Venetian"),
87 // note: sometimes shade type 54/55/56 wrongly report capabilities:3 so force capabilities:4
88 new Type(54).capabilities(4).text("Vertical Slats Left Stack").capabilitiesOverride(4),
89 new Type(55).capabilities(4).text("Vertical Slats Right Stack").capabilitiesOverride(4),
90 new Type(56).capabilities(4).text("Vertical Slats Split Stack").capabilitiesOverride(4),
91 new Type(62).capabilities(2).text("Venetian"),
92 new Type(65).capabilities(8).text("Vignette Duolite"),
93 new Type(66).capabilities(5).text("Shutter"),
94 new Type(69).capabilities(3).text("Curtain Left Stack"),
95 new Type(70).capabilities(3).text("Curtain Right Stack"),
96 new Type(71).capabilities(3).text("Curtain Split Stack"),
97 new Type(79).capabilities(8).text("Duolite Lift"),
99 new Type()).stream().collect(Collectors.toMap(Type::getValue, Function.identity()));
102 * Base class that is extended by Type and Capabilities classes.
104 * @author Andrew Fiddian-Green - Initial Contribution
106 private static class Base {
107 protected int intValue = -1;
108 protected String text = "-- not in database --";
110 public Integer getValue() {
115 public String toString() {
116 return String.format("%s (%d)", text, intValue);
121 * Describes a shade type entry in the database; implements 'capabilities' parameter.
123 * @author Andrew Fiddian-Green - Initial Contribution
125 public static class Type extends Base {
126 private int capabilities = -1;
127 private int capabilitiesOverride = -1;
132 public Type(int type) {
136 protected Type text(String text) {
141 protected Type capabilities(int capabilities) {
142 this.capabilities = capabilities;
146 protected Type capabilitiesOverride(int capabilitiesOverride) {
147 this.capabilitiesOverride = capabilitiesOverride;
152 * Get shade types's 'capabilities'.
154 * @return 'capabilities'.
156 public int getCapabilities() {
161 * Get shade's overridden 'capabilities'.
163 * @return 'capabilitiesOverride'.
165 public int getCapabilitiesOverride() {
166 return capabilitiesOverride;
171 * Describes a shade 'capabilities' entry in the database; adds properties indicating its supported functionality.
173 * @author Andrew Fiddian-Green - Initial Contribution
175 public static class Capabilities extends Base {
176 private boolean supportsPrimary;
177 private boolean supportsSecondary;
178 private boolean supportsTiltOnClosed;
179 private boolean supportsTiltAnywhere;
180 private boolean supportsSecondaryOverlapped;
181 private boolean primaryInverted;
182 private boolean tilt180Degrees;
184 public Capabilities() {
187 protected Capabilities secondaryOverlapped() {
188 supportsSecondaryOverlapped = true;
192 protected Capabilities(int capabilities) {
193 intValue = capabilities;
196 protected Capabilities text(String text) {
201 protected Capabilities primary() {
202 supportsPrimary = true;
206 protected Capabilities tiltOnClosed() {
207 supportsTiltOnClosed = true;
211 protected Capabilities secondary() {
212 supportsSecondary = true;
216 protected Capabilities tiltAnywhere() {
217 supportsTiltAnywhere = true;
221 protected Capabilities primaryInverted() {
222 supportsPrimary = true;
223 primaryInverted = true;
227 protected Capabilities tilt180() {
228 tilt180Degrees = true;
233 * Check if the Capabilities class instance supports a primary shade.
235 * @return true if it supports a primary shade.
237 public boolean supportsPrimary() {
238 return supportsPrimary;
242 * Check if the Capabilities class instance supports a vane/tilt function (by means of a second motor).
244 * @return true if it supports a vane/tilt function (by means of a second motor).
246 public boolean supportsTiltAnywhere() {
247 return supportsTiltAnywhere;
251 * Check if the Capabilities class instance supports a secondary shade.
253 * @return true if it supports a secondary shade.
255 public boolean supportsSecondary() {
256 return supportsSecondary;
260 * Check if the Capabilities class instance if the primary shade is inverted.
262 * @return true if the primary shade is inverted.
264 public boolean isPrimaryInverted() {
265 return primaryInverted;
269 * Check if the Capabilities class instance supports 'tilt on closed'.
271 * Note: Simple bottom up or vertical shades that do not have independent vane controls, can be tilted in a
272 * simple way, only when they are fully closed, by moving the shade motor a bit further.
274 * @return true if the it supports tilt on closed.
276 public boolean supportsTiltOnClosed() {
277 return supportsTiltOnClosed && !supportsTiltAnywhere;
281 * Check if the Capabilities class instance supports 180 degrees tilt.
283 * @return true if the tilt range is 180 degrees.
285 public boolean supportsTilt180() {
286 return tilt180Degrees;
290 * Check if the Capabilities class instance supports an overlapped secondary shade.
291 * e.g. a 'DuoLite' or blackout shade.
293 * @return true if the shade supports a secondary overlapped shade.
295 public boolean supportsSecondaryOverlapped() {
296 return supportsSecondaryOverlapped;
301 * Determines if a given shade 'type' is in the database.
303 * @param type the shade 'type' parameter.
304 * @return true if the shade 'type' is known.
306 public boolean isTypeInDatabase(int type) {
307 return TYPE_DATABASE.containsKey(type);
311 * Determines if a given 'capabilities' value is in the database.
313 * @param capabilities the shade 'capabilities' parameter
314 * @return true if the 'capabilities' value is known
316 public boolean isCapabilitiesInDatabase(int capabilities) {
317 return CAPABILITIES_DATABASE.containsKey(capabilities);
321 * Return a Type class instance that corresponds to the given 'type' parameter.
323 * @param type the shade 'type' parameter.
324 * @return corresponding instance of Type class.
326 public Type getType(int type) {
327 return TYPE_DATABASE.getOrDefault(type, new Type());
331 * Return a Capabilities class instance that corresponds to the given 'capabilitiesId' parameter. If the
332 * 'capabilitiesId' parameter is for a valid capabilities entry in the database, then that respective Capabilities
333 * class instance is returned. Otherwise a blank Capabilities class instance is returned.
335 * @param capabilitiesId the target capabilities Id.
336 * @return corresponding Capabilities class instance.
338 public Capabilities getCapabilities(@Nullable Integer capabilitiesId) {
339 return CAPABILITIES_DATABASE.getOrDefault(capabilitiesId != null ? capabilitiesId.intValue() : -1,
344 * Return a Capabilities class instance that corresponds to the given 'typeId' parameter.
347 * <li>If the 'typeId' parameter is a valid type in the database, and it has a 'capabilitiesOverride' value, then an
348 * instance of the respective overridden Capabilities class is returned.
349 * <li>Otherwise if the 'capabilitiesId' parameter is for a valid capabilities entry in the database, then that
350 * respective Capabilities class instance is returned.
351 * <li>Otherwise if the type is a valid type in the database, then its 'capabilities' instance is returned.
352 * <li>Otherwise a default Capabilities '0' class instance is returned.
356 * @param typeId the target shade type Id (to check if it has a 'capabilitiesOverride' value).
357 * @param capabilitiesId the target capabilities value (when type Id does not have a 'capabilitiesOverride').
358 * @return corresponding Capabilities class instance.
360 public Capabilities getCapabilities(int typeId, @Nullable Integer capabilitiesId) {
361 Type type = TYPE_DATABASE.getOrDefault(typeId, new Type());
362 // first try capabilitiesOverride for type Id
363 int targetCapabilities = type.getCapabilitiesOverride();
364 // then try capabilitiesId
365 if (targetCapabilities < 0 && capabilitiesId != null && isCapabilitiesInDatabase(capabilitiesId.intValue())) {
366 targetCapabilities = capabilitiesId.intValue();
368 // then try capabilities for typeId
369 if (targetCapabilities < 0) {
370 targetCapabilities = type.getCapabilities();
372 // fallback to default capabilities 0 (so at least something may work..)
373 if (targetCapabilities < 0) {
374 targetCapabilities = 0;
376 return getCapabilities(targetCapabilities);
379 private static final String REQUEST_DEVELOPERS_TO_UPDATE = " => Please request developers to update the database!";
382 * Log a message indicating that 'type' is not in database.
386 public void logTypeNotInDatabase(int type) {
387 logger.warn("The shade 'type:{}' is not in the database!{}", type, REQUEST_DEVELOPERS_TO_UPDATE);
391 * Log a message indicating that 'capabilities' is not in database.
393 * @param capabilities
395 public void logCapabilitiesNotInDatabase(int type, int capabilities) {
396 logger.warn("The 'capabilities:{}' for shade 'type:{}' are not in the database!{}", capabilities, type,
397 REQUEST_DEVELOPERS_TO_UPDATE);
401 * Log a message indicating the type's capabilities and the passed capabilities are not equal.
404 * @param capabilities
406 public void logCapabilitiesMismatch(int type, int capabilities) {
407 logger.warn("The 'capabilities:{}' reported by shade 'type:{}' don't match the database!{}", capabilities, type,
408 REQUEST_DEVELOPERS_TO_UPDATE);
412 * Log a message indicating that a shade's secondary/vanes support, as observed via its actual JSON payload, does
413 * not match the expected value as declared in its 'type' and 'capabilities'.
417 * @param capabilities
418 * @param propertyValue
420 public void logPropertyMismatch(String propertyKey, int type, int capabilities, boolean propertyValue) {
422 The '{}:{}' property actually reported by shade 'type:{}' is different \
423 than expected from its 'capabilities:{}' in the database!{}\
424 """, propertyKey, propertyValue, type, capabilities, REQUEST_DEVELOPERS_TO_UPDATE);