]> git.basschouten.com Git - openhab-addons.git/blob
a5702df9292345dd75ac8c84e03201d56e1e3541
[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 newProduct the product containing new data.
130      *
131      * @return true if the product exists in the database.
132      */
133     public boolean update(VeluxProduct newProduct) {
134         ProductBridgeIndex productBridgeIndex = newProduct.getBridgeProductIndex();
135         if (!isRegistered(productBridgeIndex)) {
136             logger.warn("update() failed as actuator (with index {}) is not registered.", productBridgeIndex.toInt());
137             return false;
138         }
139
140         VeluxProduct theProduct = this.get(productBridgeIndex);
141
142         String oldProduct = "";
143         if (logger.isDebugEnabled()) {
144             oldProduct = theProduct.toString();
145         }
146
147         boolean dirty = false;
148
149         // ignore commands with state 'not used'
150         boolean ignoreNotUsed = (ProductState.NOT_USED == ProductState.of(newProduct.getState()));
151
152         // specially ignore commands from buggy devices (e.g. Somfy) which have bad data
153         boolean ignoreSpecial = theProduct.isSomfyProduct()
154                 && (Command.GW_OPENHAB_RECEIVEONLY == newProduct.getCreatorCommand())
155                 && !VeluxProductPosition.isValid(newProduct.getCurrentPosition())
156                 && !VeluxProductPosition.isValid(newProduct.getTarget());
157
158         if ((!ignoreNotUsed) && (!ignoreSpecial)) {
159             int newState = newProduct.getState();
160             int theState = theProduct.getState();
161
162             // always update the actuator state, but only set dirty flag if they are not operationally equivalent
163             if (theProduct.setState(newState)) {
164                 dirty |= !ProductState.equivalent(theState, newState);
165             }
166
167             // only update the actual position values if the state is permitted
168             if (PERMITTED_VALUE_STATES.contains(ProductState.of(newState))) {
169                 int newValue = newProduct.getCurrentPosition();
170                 if (VeluxProductPosition.isUnknownOrValid(newValue)) {
171                     dirty |= theProduct.setCurrentPosition(newValue);
172                 }
173                 newValue = newProduct.getTarget();
174                 if (VeluxProductPosition.isUnknownOrValid(newValue)) {
175                     dirty |= theProduct.setTarget(newValue);
176                 }
177                 if (theProduct.supportsVanePosition()) {
178                     FunctionalParameters newFunctionalParameters = newProduct.getFunctionalParameters();
179                     if (newFunctionalParameters != null) {
180                         dirty |= theProduct.setFunctionalParameters(newFunctionalParameters);
181                     }
182                 }
183             }
184         }
185
186         // update modified product database
187         if (dirty) {
188             this.dirty = true;
189             String uniqueIndex = theProduct.getProductUniqueIndex();
190             logger.trace("update(): updating by UniqueIndex {}.", uniqueIndex);
191             existingProductsByUniqueIndex.replace(uniqueIndex, theProduct);
192             modifiedProductsByUniqueIndex.put(uniqueIndex, theProduct);
193         }
194
195         if (logger.isDebugEnabled()) {
196             if (dirty) {
197                 logger.debug("update() theProduct:{} (previous)", oldProduct);
198             }
199             logger.debug("update() newProduct:{} ({})", newProduct, dirty ? "modifier" : "identical");
200             logger.debug("update() theProduct:{} ({})", theProduct, dirty ? "modified" : "unchanged");
201         }
202
203         return true;
204     }
205
206     public VeluxProduct get(String productUniqueIndex) {
207         logger.trace("get({}) called.", productUniqueIndex);
208         return existingProductsByUniqueIndex.getOrDefault(productUniqueIndex, VeluxProduct.UNKNOWN);
209     }
210
211     public VeluxProduct get(ProductBridgeIndex bridgeProductIndex) {
212         logger.trace("get({}) called.", bridgeProductIndex);
213         String unique = bridgeIndexToUniqueIndex.get(bridgeProductIndex.toInt());
214         return unique != null ? existingProductsByUniqueIndex.getOrDefault(unique, VeluxProduct.UNKNOWN)
215                 : VeluxProduct.UNKNOWN;
216     }
217
218     public VeluxProduct[] values() {
219         return existingProductsByUniqueIndex.values().toArray(new VeluxProduct[0]);
220     }
221
222     public VeluxProduct[] valuesOfModified() {
223         return modifiedProductsByUniqueIndex.values().toArray(new VeluxProduct[0]);
224     }
225
226     public int getNoMembers() {
227         logger.trace("getNoMembers() returns {}.", memberCount);
228         return memberCount;
229     }
230
231     public boolean isDirty() {
232         logger.trace("isDirty() returns {}.", dirty);
233         return dirty;
234     }
235
236     public void resetDirtyFlag() {
237         logger.trace("resetDirtyFlag() called.");
238         modifiedProductsByUniqueIndex = new ConcurrentHashMap<>();
239         dirty = false;
240     }
241
242     public String toString(boolean showSummary, String delimiter) {
243         StringBuilder sb = new StringBuilder();
244
245         if (showSummary) {
246             sb.append(memberCount).append(" members: ");
247         }
248         for (VeluxProduct product : this.values()) {
249             sb.append(product).append(delimiter);
250         }
251         if (sb.lastIndexOf(delimiter) > 0) {
252             sb.deleteCharAt(sb.lastIndexOf(delimiter));
253         }
254         return sb.toString();
255     }
256
257     @Override
258     public String toString() {
259         return toString(true, VeluxBindingConstants.OUTPUT_VALUE_SEPARATOR);
260     }
261 }