2 * Copyright (c) 2010-2022 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 requestingCommand the command that requested the data from the hub and so triggered calling this method.
130 * @param newProduct the product containing new data.
132 * @return true if the product exists in the database.
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());
141 VeluxProduct theProduct = this.get(productBridgeIndex);
143 String oldProduct = "";
144 if (logger.isDebugEnabled()) {
145 oldProduct = theProduct.toString();
148 boolean dirty = false;
150 // ignore commands with state 'not used'
151 boolean ignoreNotUsed = (ProductState.NOT_USED == ProductState.of(newProduct.getState()));
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());
159 if ((!ignoreNotUsed) && (!ignoreSpecial)) {
160 int newState = newProduct.getState();
161 int theState = theProduct.getState();
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);
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);
174 newValue = newProduct.getTarget();
175 if (VeluxProductPosition.isUnknownOrValid(newValue)) {
176 dirty |= theProduct.setTarget(newValue);
178 if (theProduct.supportsVanePosition()) {
179 FunctionalParameters newFunctionalParameters = newProduct.getFunctionalParameters();
180 if (newFunctionalParameters != null) {
181 dirty |= theProduct.setFunctionalParameters(newFunctionalParameters);
187 // update modified product database
190 String uniqueIndex = theProduct.getProductUniqueIndex();
191 logger.trace("update(): updating by UniqueIndex {}.", uniqueIndex);
192 existingProductsByUniqueIndex.replace(uniqueIndex, theProduct);
193 modifiedProductsByUniqueIndex.put(uniqueIndex, theProduct);
196 if (logger.isDebugEnabled()) {
198 logger.debug("update() theProduct:{} (previous)", oldProduct);
200 logger.debug("update() newProduct:{} ({})", newProduct, dirty ? "modifier" : "identical");
201 logger.debug("update() theProduct:{} ({})", theProduct, dirty ? "modified" : "unchanged");
207 public VeluxProduct get(String productUniqueIndex) {
208 logger.trace("get({}) called.", productUniqueIndex);
209 return existingProductsByUniqueIndex.getOrDefault(productUniqueIndex, VeluxProduct.UNKNOWN);
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;
219 public VeluxProduct[] values() {
220 return existingProductsByUniqueIndex.values().toArray(new VeluxProduct[0]);
223 public VeluxProduct[] valuesOfModified() {
224 return modifiedProductsByUniqueIndex.values().toArray(new VeluxProduct[0]);
227 public int getNoMembers() {
228 logger.trace("getNoMembers() returns {}.", memberCount);
232 public boolean isDirty() {
233 logger.trace("isDirty() returns {}.", dirty);
237 public void resetDirtyFlag() {
238 logger.trace("resetDirtyFlag() called.");
239 modifiedProductsByUniqueIndex = new ConcurrentHashMap<>();
243 public String toString(boolean showSummary, String delimiter) {
244 StringBuilder sb = new StringBuilder();
247 sb.append(memberCount).append(" members: ");
249 for (VeluxProduct product : this.values()) {
250 sb.append(product).append(delimiter);
252 if (sb.lastIndexOf(delimiter) > 0) {
253 sb.deleteCharAt(sb.lastIndexOf(delimiter));
255 return sb.toString();
259 public String toString() {
260 return toString(true, VeluxBindingConstants.OUTPUT_VALUE_SEPARATOR);