2 * Copyright (c) 2010-2023 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.velux.internal.things;
15 import java.util.Arrays;
16 import java.util.List;
18 import java.util.concurrent.ConcurrentHashMap;
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;
30 * Combined set of product informations provided by the <B>Velux</B> bridge,
31 * which can be used for later interactions.
33 * The following class access methods exist:
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>
46 * @author Guenther Schreiner - initial contribution.
49 public class VeluxExistingProducts {
50 private final Logger logger = LoggerFactory.getLogger(VeluxExistingProducts.class);
52 // Type definitions, class-internal variables
54 private Map<String, VeluxProduct> existingProductsByUniqueIndex;
55 private Map<Integer, String> bridgeIndexToUniqueIndex;
56 private Map<String, VeluxProduct> modifiedProductsByUniqueIndex;
57 private int memberCount;
60 * Value to flag any changes towards the getter.
62 private boolean dirty;
65 * Permitted list of product states whose position values shall be accepted.
67 private static final List<ProductState> PERMITTED_VALUE_STATES = Arrays.asList(ProductState.EXECUTING,
70 // Constructor methods
72 public VeluxExistingProducts() {
73 logger.trace("VeluxExistingProducts(constructor) called.");
74 existingProductsByUniqueIndex = new ConcurrentHashMap<>();
75 bridgeIndexToUniqueIndex = new ConcurrentHashMap<>();
76 modifiedProductsByUniqueIndex = new ConcurrentHashMap<>();
79 logger.trace("VeluxExistingProducts(constructor) done.");
82 // Class access methods
84 public boolean isRegistered(String productUniqueIndex) {
85 boolean result = existingProductsByUniqueIndex.containsKey(productUniqueIndex);
86 logger.trace("isRegistered(String {}) returns {}.", productUniqueIndex, result);
90 public boolean isRegistered(VeluxProduct product) {
91 boolean result = existingProductsByUniqueIndex.containsKey(product.getProductUniqueIndex());
92 logger.trace("isRegistered(VeluxProduct {}) returns {}.", product, result);
96 public boolean isRegistered(ProductBridgeIndex bridgeProductIndex) {
97 boolean result = bridgeIndexToUniqueIndex.containsKey(bridgeProductIndex.toInt());
98 logger.trace("isRegistered(ProductBridgeIndex {}) returns {}.", bridgeProductIndex, result);
102 public boolean register(VeluxProduct newProduct) {
103 logger.trace("register({}) called.", newProduct);
104 if (isRegistered(newProduct)) {
107 logger.trace("register() registering new product {}.", newProduct);
109 String uniqueIndex = newProduct.getProductUniqueIndex();
110 logger.trace("register() registering by UniqueIndex {}", uniqueIndex);
111 existingProductsByUniqueIndex.put(uniqueIndex, newProduct);
113 logger.trace("register() registering by ProductBridgeIndex {}", newProduct.getBridgeProductIndex().toInt());
114 bridgeIndexToUniqueIndex.put(newProduct.getBridgeProductIndex().toInt(), uniqueIndex);
116 logger.trace("register() registering set of modifications by UniqueIndex {}", uniqueIndex);
117 modifiedProductsByUniqueIndex.put(uniqueIndex, newProduct);
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.
129 * @param newProduct the product containing new data.
131 * @return true if the product exists in the database.
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());
140 VeluxProduct theProduct = this.get(productBridgeIndex);
142 String oldProduct = "";
143 if (logger.isDebugEnabled()) {
144 oldProduct = theProduct.toString();
147 boolean dirty = false;
149 // ignore commands with state 'not used'
150 boolean ignoreNotUsed = (ProductState.NOT_USED == ProductState.of(newProduct.getState()));
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());
158 if ((!ignoreNotUsed) && (!ignoreSpecial)) {
159 int newState = newProduct.getState();
160 int theState = theProduct.getState();
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);
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);
173 newValue = newProduct.getTarget();
174 if (VeluxProductPosition.isUnknownOrValid(newValue)) {
175 dirty |= theProduct.setTarget(newValue);
177 if (theProduct.supportsVanePosition()) {
178 FunctionalParameters newFunctionalParameters = newProduct.getFunctionalParameters();
179 if (newFunctionalParameters != null) {
180 dirty |= theProduct.setFunctionalParameters(newFunctionalParameters);
186 // update modified product database
189 String uniqueIndex = theProduct.getProductUniqueIndex();
190 logger.trace("update(): updating by UniqueIndex {}.", uniqueIndex);
191 existingProductsByUniqueIndex.replace(uniqueIndex, theProduct);
192 modifiedProductsByUniqueIndex.put(uniqueIndex, theProduct);
195 if (logger.isDebugEnabled()) {
197 logger.debug("update() theProduct:{} (previous)", oldProduct);
199 logger.debug("update() newProduct:{} ({})", newProduct, dirty ? "modifier" : "identical");
200 logger.debug("update() theProduct:{} ({})", theProduct, dirty ? "modified" : "unchanged");
206 public VeluxProduct get(String productUniqueIndex) {
207 logger.trace("get({}) called.", productUniqueIndex);
208 return existingProductsByUniqueIndex.getOrDefault(productUniqueIndex, VeluxProduct.UNKNOWN);
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;
218 public VeluxProduct[] values() {
219 return existingProductsByUniqueIndex.values().toArray(new VeluxProduct[0]);
222 public VeluxProduct[] valuesOfModified() {
223 return modifiedProductsByUniqueIndex.values().toArray(new VeluxProduct[0]);
226 public int getNoMembers() {
227 logger.trace("getNoMembers() returns {}.", memberCount);
231 public boolean isDirty() {
232 logger.trace("isDirty() returns {}.", dirty);
236 public void resetDirtyFlag() {
237 logger.trace("resetDirtyFlag() called.");
238 modifiedProductsByUniqueIndex = new ConcurrentHashMap<>();
242 public String toString(boolean showSummary, String delimiter) {
243 StringBuilder sb = new StringBuilder();
246 sb.append(memberCount).append(" members: ");
248 for (VeluxProduct product : this.values()) {
249 sb.append(product).append(delimiter);
251 if (sb.lastIndexOf(delimiter) > 0) {
252 sb.deleteCharAt(sb.lastIndexOf(delimiter));
254 return sb.toString();
258 public String toString() {
259 return toString(true, VeluxBindingConstants.OUTPUT_VALUE_SEPARATOR);