]> git.basschouten.com Git - openhab-addons.git/blob
bb1cedc2f84383d128a8705abacd9cd140bd3e13
[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.velux.internal.things;
14
15 import java.util.Arrays;
16 import java.util.List;
17 import java.util.Map;
18 import java.util.concurrent.ConcurrentHashMap;
19
20 import org.eclipse.jdt.annotation.NonNullByDefault;
21 import org.openhab.binding.velux.internal.VeluxBindingConstants;
22 import org.openhab.binding.velux.internal.bridge.slip.FunctionalParameters;
23 import org.openhab.binding.velux.internal.things.VeluxKLFAPI.Command;
24 import org.openhab.binding.velux.internal.things.VeluxProduct.ProductBridgeIndex;
25 import org.openhab.binding.velux.internal.things.VeluxProduct.ProductState;
26 import org.slf4j.Logger;
27 import org.slf4j.LoggerFactory;
28
29 /**
30  * Combined set of product informations provided by the <B>Velux</B> bridge,
31  * which can be used for later interactions.
32  * <P>
33  * The following class access methods exist:
34  * <UL>
35  * <LI>{@link VeluxExistingProducts#isRegistered} for querying existence of a {@link VeluxProduct},</LI>
36  * <LI>{@link VeluxExistingProducts#register} for storing a {@link VeluxProduct},</LI>
37  * <LI>{@link VeluxExistingProducts#update} for updating/storing of a {@link VeluxProduct},</LI>
38  * <LI>{@link VeluxExistingProducts#get} for retrieval of a {@link VeluxProduct},</LI>
39  * <LI>{@link VeluxExistingProducts#values} for retrieval of all {@link VeluxProduct}s,</LI>
40  * <LI>{@link VeluxExistingProducts#getNoMembers} for retrieval of the number of all {@link VeluxProduct}s,</LI>
41  * <LI>{@link VeluxExistingProducts#toString} for a descriptive string representation.</LI>
42  * </UL>
43  *
44  * @see VeluxProduct
45  *
46  * @author Guenther Schreiner - initial contribution.
47  */
48 @NonNullByDefault
49 public class VeluxExistingProducts {
50     private final Logger logger = LoggerFactory.getLogger(VeluxExistingProducts.class);
51
52     // Type definitions, class-internal variables
53
54     private Map<String, VeluxProduct> existingProductsByUniqueIndex;
55     private Map<Integer, String> bridgeIndexToUniqueIndex;
56     private Map<String, VeluxProduct> modifiedProductsByUniqueIndex;
57     private int memberCount;
58
59     /*
60      * Value to flag any changes towards the getter.
61      */
62     private boolean dirty;
63
64     /*
65      * Permitted list of product states whose position values shall be accepted.
66      */
67     private static final List<ProductState> PERMITTED_VALUE_STATES = Arrays.asList(ProductState.EXECUTING,
68             ProductState.DONE);
69
70     // Constructor methods
71
72     public VeluxExistingProducts() {
73         logger.trace("VeluxExistingProducts(constructor) called.");
74         existingProductsByUniqueIndex = new ConcurrentHashMap<>();
75         bridgeIndexToUniqueIndex = new ConcurrentHashMap<>();
76         modifiedProductsByUniqueIndex = new ConcurrentHashMap<>();
77         memberCount = 0;
78         dirty = true;
79         logger.trace("VeluxExistingProducts(constructor) done.");
80     }
81
82     // Class access methods
83
84     public boolean isRegistered(String productUniqueIndex) {
85         boolean result = existingProductsByUniqueIndex.containsKey(productUniqueIndex);
86         logger.trace("isRegistered(String {}) returns {}.", productUniqueIndex, result);
87         return result;
88     }
89
90     public boolean isRegistered(VeluxProduct product) {
91         boolean result = existingProductsByUniqueIndex.containsKey(product.getProductUniqueIndex());
92         logger.trace("isRegistered(VeluxProduct {}) returns {}.", product, result);
93         return result;
94     }
95
96     public boolean isRegistered(ProductBridgeIndex bridgeProductIndex) {
97         boolean result = bridgeIndexToUniqueIndex.containsKey(bridgeProductIndex.toInt());
98         logger.trace("isRegistered(ProductBridgeIndex {}) returns {}.", bridgeProductIndex, result);
99         return result;
100     }
101
102     public boolean register(VeluxProduct newProduct) {
103         logger.trace("register({}) called.", newProduct);
104         if (isRegistered(newProduct)) {
105             return false;
106         }
107         logger.trace("register() registering new product {}.", newProduct);
108
109         String uniqueIndex = newProduct.getProductUniqueIndex();
110         logger.trace("register() registering by UniqueIndex {}", uniqueIndex);
111         existingProductsByUniqueIndex.put(uniqueIndex, newProduct);
112
113         logger.trace("register() registering by ProductBridgeIndex {}", newProduct.getBridgeProductIndex().toInt());
114         bridgeIndexToUniqueIndex.put(newProduct.getBridgeProductIndex().toInt(), uniqueIndex);
115
116         logger.trace("register() registering set of modifications by UniqueIndex {}", uniqueIndex);
117         modifiedProductsByUniqueIndex.put(uniqueIndex, newProduct);
118
119         memberCount++;
120         dirty = true;
121         return true;
122     }
123
124     /**
125      * Update the product in the existing products database by applying the data from the new product argument. This
126      * method may ignore the new product if it was created by certain originating commands, or if the new product has
127      * certain actuator states.
128      *
129      * @param requestingCommand the command that requested the data from the hub and so triggered calling this method.
130      * @param newProduct the product containing new data.
131      *
132      * @return true if the product exists in the database.
133      */
134     public boolean update(VeluxProduct newProduct) {
135         ProductBridgeIndex productBridgeIndex = newProduct.getBridgeProductIndex();
136         if (!isRegistered(productBridgeIndex)) {
137             logger.warn("update() failed as actuator (with index {}) is not registered.", productBridgeIndex.toInt());
138             return false;
139         }
140
141         VeluxProduct theProduct = this.get(productBridgeIndex);
142
143         String oldProduct = "";
144         if (logger.isDebugEnabled()) {
145             oldProduct = theProduct.toString();
146         }
147
148         boolean dirty = false;
149
150         // ignore commands with state 'not used'
151         boolean ignoreNotUsed = (ProductState.NOT_USED == ProductState.of(newProduct.getState()));
152
153         // specially ignore commands from buggy devices (e.g. Somfy) which have bad data
154         boolean ignoreSpecial = theProduct.isSomfyProduct()
155                 && (Command.GW_OPENHAB_RECEIVEONLY == newProduct.getCreatorCommand())
156                 && !VeluxProductPosition.isValid(newProduct.getCurrentPosition())
157                 && !VeluxProductPosition.isValid(newProduct.getTarget());
158
159         if ((!ignoreNotUsed) && (!ignoreSpecial)) {
160             int newState = newProduct.getState();
161             int theState = theProduct.getState();
162
163             // always update the actuator state, but only set dirty flag if they are not operationally equivalent
164             if (theProduct.setState(newState)) {
165                 dirty |= !ProductState.equivalent(theState, newState);
166             }
167
168             // only update the actual position values if the state is permitted
169             if (PERMITTED_VALUE_STATES.contains(ProductState.of(newState))) {
170                 int newValue = newProduct.getCurrentPosition();
171                 if (VeluxProductPosition.isUnknownOrValid(newValue)) {
172                     dirty |= theProduct.setCurrentPosition(newValue);
173                 }
174                 newValue = newProduct.getTarget();
175                 if (VeluxProductPosition.isUnknownOrValid(newValue)) {
176                     dirty |= theProduct.setTarget(newValue);
177                 }
178                 if (theProduct.supportsVanePosition()) {
179                     FunctionalParameters newFunctionalParameters = newProduct.getFunctionalParameters();
180                     if (newFunctionalParameters != null) {
181                         dirty |= theProduct.setFunctionalParameters(newFunctionalParameters);
182                     }
183                 }
184             }
185         }
186
187         // update modified product database
188         if (dirty) {
189             this.dirty = true;
190             String uniqueIndex = theProduct.getProductUniqueIndex();
191             logger.trace("update(): updating by UniqueIndex {}.", uniqueIndex);
192             existingProductsByUniqueIndex.replace(uniqueIndex, theProduct);
193             modifiedProductsByUniqueIndex.put(uniqueIndex, theProduct);
194         }
195
196         if (logger.isDebugEnabled()) {
197             if (dirty) {
198                 logger.debug("update() theProduct:{} (previous)", oldProduct);
199             }
200             logger.debug("update() newProduct:{} ({})", newProduct, dirty ? "modifier" : "identical");
201             logger.debug("update() theProduct:{} ({})", theProduct, dirty ? "modified" : "unchanged");
202         }
203
204         return true;
205     }
206
207     public VeluxProduct get(String productUniqueIndex) {
208         logger.trace("get({}) called.", productUniqueIndex);
209         return existingProductsByUniqueIndex.getOrDefault(productUniqueIndex, VeluxProduct.UNKNOWN);
210     }
211
212     public VeluxProduct get(ProductBridgeIndex bridgeProductIndex) {
213         logger.trace("get({}) called.", bridgeProductIndex);
214         String unique = bridgeIndexToUniqueIndex.get(bridgeProductIndex.toInt());
215         return unique != null ? existingProductsByUniqueIndex.getOrDefault(unique, VeluxProduct.UNKNOWN)
216                 : VeluxProduct.UNKNOWN;
217     }
218
219     public VeluxProduct[] values() {
220         return existingProductsByUniqueIndex.values().toArray(new VeluxProduct[0]);
221     }
222
223     public VeluxProduct[] valuesOfModified() {
224         return modifiedProductsByUniqueIndex.values().toArray(new VeluxProduct[0]);
225     }
226
227     public int getNoMembers() {
228         logger.trace("getNoMembers() returns {}.", memberCount);
229         return memberCount;
230     }
231
232     public boolean isDirty() {
233         logger.trace("isDirty() returns {}.", dirty);
234         return dirty;
235     }
236
237     public void resetDirtyFlag() {
238         logger.trace("resetDirtyFlag() called.");
239         modifiedProductsByUniqueIndex = new ConcurrentHashMap<>();
240         dirty = false;
241     }
242
243     public String toString(boolean showSummary, String delimiter) {
244         StringBuilder sb = new StringBuilder();
245
246         if (showSummary) {
247             sb.append(memberCount).append(" members: ");
248         }
249         for (VeluxProduct product : this.values()) {
250             sb.append(product).append(delimiter);
251         }
252         if (sb.lastIndexOf(delimiter) > 0) {
253             sb.deleteCharAt(sb.lastIndexOf(delimiter));
254         }
255         return sb.toString();
256     }
257
258     @Override
259     public String toString() {
260         return toString(true, VeluxBindingConstants.OUTPUT_VALUE_SEPARATOR);
261     }
262 }