]> git.basschouten.com Git - openhab-addons.git/blob
81c93697ec326b8ccef99ffc0c6817e1d9051730
[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.handler;
14
15 import static org.openhab.binding.velux.internal.VeluxBindingConstants.*;
16
17 import org.eclipse.jdt.annotation.NonNullByDefault;
18 import org.eclipse.jdt.annotation.Nullable;
19 import org.openhab.binding.velux.internal.bridge.common.GetProduct;
20 import org.openhab.binding.velux.internal.bridge.common.RunProductCommand;
21 import org.openhab.binding.velux.internal.bridge.slip.FunctionalParameters;
22 import org.openhab.binding.velux.internal.bridge.slip.SCrunProductCommand;
23 import org.openhab.binding.velux.internal.handler.utils.Thing2VeluxActuator;
24 import org.openhab.binding.velux.internal.things.StatusReply;
25 import org.openhab.binding.velux.internal.things.VeluxExistingProducts;
26 import org.openhab.binding.velux.internal.things.VeluxProduct;
27 import org.openhab.binding.velux.internal.things.VeluxProduct.ProductBridgeIndex;
28 import org.openhab.binding.velux.internal.things.VeluxProduct.ProductState;
29 import org.openhab.binding.velux.internal.things.VeluxProductPosition;
30 import org.openhab.core.library.types.OnOffType;
31 import org.openhab.core.library.types.PercentType;
32 import org.openhab.core.library.types.StopMoveType;
33 import org.openhab.core.library.types.UpDownType;
34 import org.openhab.core.thing.ChannelUID;
35 import org.openhab.core.types.Command;
36 import org.openhab.core.types.State;
37 import org.openhab.core.types.UnDefType;
38 import org.slf4j.Logger;
39 import org.slf4j.LoggerFactory;
40
41 /**
42  * <B>Channel-specific retrieval and modification.</B>
43  * <P>
44  * This class implements the Channel <B>position</B> of the Thing <B>actuator</B>:
45  * <UL>
46  * <LI><I>Velux</I> <B>bridge</B> &rarr; <B>OpenHAB</B>:
47  * <P>
48  * Information retrieval by method {@link #handleRefresh}.</LI>
49  * </UL>
50  * <UL>
51  * <LI><B>OpenHAB</B> Event Bus &rarr; <I>Velux</I> <B>bridge</B>
52  * <P>
53  * Sending commands and value updates by method {@link #handleCommand}.</LI>
54  * </UL>
55  *
56  * @author Guenther Schreiner - Initial contribution.
57  * @author Andrew Fiddian-Green - Refactoring and use alternate API set for Vane Position.
58  */
59 @NonNullByDefault
60 final class ChannelActuatorPosition extends ChannelHandlerTemplate {
61     private static final Logger LOGGER = LoggerFactory.getLogger(ChannelActuatorPosition.class);
62
63     // Constructors
64
65     /**
66      * Suppress default constructor for non-instantiability.
67      */
68     private ChannelActuatorPosition() {
69         throw new AssertionError();
70     }
71
72     // Public methods
73
74     /**
75      * Communication method to retrieve information to update the channel value.
76      *
77      * @param channelUID The item passed as type {@link ChannelUID} for which a refresh is intended.
78      * @param channelId The same item passed as type {@link String} for which a refresh is intended.
79      * @param thisBridgeHandler The Velux bridge handler with a specific communication protocol which provides
80      *            information for this channel.
81      * @return newState The value retrieved for the passed channel, or <I>null</I> in case if there is no (new) value.
82      */
83     static @Nullable State handleRefresh(ChannelUID channelUID, String channelId,
84             VeluxBridgeHandler thisBridgeHandler) {
85         LOGGER.debug("handleRefresh({},{},{}) called.", channelUID, channelId, thisBridgeHandler);
86         State newState = null;
87         do { // just for common exit
88             if (thisBridgeHandler.bridgeParameters.actuators.autoRefresh(thisBridgeHandler.thisBridge)) {
89                 LOGGER.trace("handleRefresh(): there are some existing products.");
90             }
91
92             Thing2VeluxActuator veluxActuator = thisBridgeHandler.channel2VeluxActuator.get(channelUID);
93             if (veluxActuator == null || !veluxActuator.isKnown()) {
94                 LOGGER.warn("handleRefresh(): unknown actuator.");
95                 break;
96             }
97
98             GetProduct bcp = null;
99             switch (channelId) {
100                 case CHANNEL_ACTUATOR_POSITION:
101                 case CHANNEL_ACTUATOR_STATE:
102                 case CHANNEL_VANE_POSITION:
103                     bcp = thisBridgeHandler.thisBridge.bridgeAPI().getProductStatus();
104                 default:
105                     // unknown channel, will exit
106             }
107
108             if (bcp == null) {
109                 LOGGER.trace("handleRefresh(): aborting processing as handler is null.");
110                 break;
111             }
112
113             bcp.setProductId(veluxActuator.getProductBridgeIndex().toInt());
114             if ((!thisBridgeHandler.thisBridge.bridgeCommunicate(bcp)) || (!bcp.isCommunicationSuccessful())) {
115                 LOGGER.trace("handleRefresh(): bridge communication request failed.");
116                 break;
117             }
118
119             VeluxProduct newProduct = bcp.getProduct();
120             ProductBridgeIndex productBridgeIndex = newProduct.getBridgeProductIndex();
121             VeluxExistingProducts existingProducts = thisBridgeHandler.existingProducts();
122             VeluxProduct existingProduct = existingProducts.get(productBridgeIndex);
123             ProductState productState = newProduct.getProductState();
124             switch (productState) {
125                 case DONE:
126                 case EXECUTING:
127                 case MANUAL:
128                 case UNKNOWN:
129                     if (!VeluxProduct.UNKNOWN.equals(existingProduct)) {
130                         switch (channelId) {
131                             case CHANNEL_VANE_POSITION:
132                             case CHANNEL_ACTUATOR_POSITION:
133                             case CHANNEL_ACTUATOR_STATE: {
134                                 if (existingProducts.update(newProduct)) {
135                                     existingProduct = existingProducts.get(productBridgeIndex);
136                                     int posValue = VeluxProductPosition.VPP_VELUX_UNKNOWN;
137                                     switch (channelId) {
138                                         case CHANNEL_VANE_POSITION:
139                                             posValue = existingProduct.getVaneDisplayPosition();
140                                             break;
141                                         case CHANNEL_ACTUATOR_POSITION:
142                                         case CHANNEL_ACTUATOR_STATE:
143                                             posValue = existingProduct.getDisplayPosition();
144                                     }
145                                     VeluxProductPosition position = new VeluxProductPosition(posValue);
146                                     if (position.isValid()) {
147                                         switch (channelId) {
148                                             case CHANNEL_VANE_POSITION:
149                                                 newState = position.getPositionAsPercentType(false);
150                                                 break;
151                                             case CHANNEL_ACTUATOR_POSITION:
152                                                 newState = position
153                                                         .getPositionAsPercentType(veluxActuator.isInverted());
154                                                 break;
155                                             case CHANNEL_ACTUATOR_STATE:
156                                                 newState = OnOffType.from(
157                                                         position.getPositionAsPercentType(veluxActuator.isInverted())
158                                                                 .intValue() > 50);
159                                         }
160                                     }
161                                 }
162                             }
163                         }
164                     }
165                     break;
166                 case WAITING_FOR_POWER:
167                 case ERROR:
168                     StatusReply statusReply = productState == ProductState.WAITING_FOR_POWER
169                             ? StatusReply.NODE_WAITING_FOR_POWER
170                             : bcp.getStatusReply();
171                     if (statusReply.isError()) {
172                         String id = VeluxProduct.UNKNOWN.equals(existingProduct)
173                                 ? newProduct.getBridgeProductIndex().toString()
174                                 : existingProduct.getProductUniqueIndex();
175                         if (statusReply.isCriticalError()) {
176                             LOGGER.warn("Product Id:{} encountered an error with StatusReply:{}", id, statusReply);
177                         } else {
178                             LOGGER.info("Product Id:{} encountered an error with StatusReply:{}", id, statusReply);
179                         }
180                     }
181                 default:
182             }
183
184             if (newState == null) {
185                 newState = UnDefType.UNDEF;
186             }
187         } while (false); // common exit
188         LOGGER.trace("handleRefresh(): new state for channel id '{}' is '{}'.", channelId, newState);
189         return newState;
190     }
191
192     /**
193      * Communication method to update the real world according to the passed channel value (or command).
194      *
195      * @param channelUID The item passed as type {@link ChannelUID} for which to following command is addressed to.
196      * @param channelId The same item passed as type {@link String} for which a refresh is intended.
197      * @param command The command passed as type {@link Command} for the mentioned item.
198      * @param thisBridgeHandler The Velux bridge handler with a specific communication protocol which provides
199      *            information for this channel.
200      * @return newValue ...
201      */
202     static @Nullable Command handleCommand(ChannelUID channelUID, String channelId, Command command,
203             VeluxBridgeHandler thisBridgeHandler) {
204         LOGGER.debug("handleCommand({},{},{},{}) called.", channelUID, channelId, command, thisBridgeHandler);
205         Command newValue = null;
206         do { // just for common exit
207             if (thisBridgeHandler.bridgeParameters.actuators.autoRefresh(thisBridgeHandler.thisBridge)) {
208                 LOGGER.trace("handleCommand(): there are some existing products.");
209             }
210
211             Thing2VeluxActuator veluxActuator = thisBridgeHandler.channel2VeluxActuator.get(channelUID);
212             if (veluxActuator == null || !veluxActuator.isKnown()) {
213                 LOGGER.warn("handleRefresh(): unknown actuator.");
214                 break;
215             }
216
217             VeluxProductPosition mainParameter = null;
218             FunctionalParameters functionalParameters = null;
219             VeluxExistingProducts existingProducts = thisBridgeHandler.existingProducts();
220             ProductBridgeIndex productBridgeIndex = veluxActuator.getProductBridgeIndex();
221
222             switch (channelId) {
223                 case CHANNEL_VANE_POSITION:
224                     if (command instanceof PercentType) {
225                         VeluxProduct existingProductClone = existingProducts.get(productBridgeIndex).clone();
226                         existingProductClone.setVanePosition(
227                                 new VeluxProductPosition((PercentType) command).getPositionAsVeluxType());
228                         functionalParameters = existingProductClone.getFunctionalParameters();
229                     }
230                     break;
231
232                 case CHANNEL_ACTUATOR_POSITION:
233                     if (command instanceof UpDownType) {
234                         mainParameter = UpDownType.UP.equals(command) ^ veluxActuator.isInverted()
235                                 ? new VeluxProductPosition(PercentType.ZERO)
236                                 : new VeluxProductPosition(PercentType.HUNDRED);
237                     } else if (command instanceof StopMoveType) {
238                         mainParameter = StopMoveType.STOP.equals(command) ? new VeluxProductPosition() : mainParameter;
239                     } else if (command instanceof PercentType) {
240                         PercentType ptCommand = (PercentType) command;
241                         if (veluxActuator.isInverted()) {
242                             ptCommand = new PercentType(PercentType.HUNDRED.intValue() - ptCommand.intValue());
243                         }
244                         mainParameter = new VeluxProductPosition(ptCommand);
245                     }
246                     break;
247
248                 case CHANNEL_ACTUATOR_STATE:
249                     if (command instanceof OnOffType) {
250                         mainParameter = OnOffType.OFF.equals(command) ^ veluxActuator.isInverted()
251                                 ? new VeluxProductPosition(PercentType.ZERO)
252                                 : new VeluxProductPosition(PercentType.HUNDRED);
253                     }
254                     break;
255
256                 default:
257                     // unknown channel => do nothing..
258             }
259
260             if ((mainParameter != null) || (functionalParameters != null)) {
261                 LOGGER.debug("handleCommand(): sending command '{}' for channel id '{}'.", command, channelId);
262                 RunProductCommand bcp = thisBridgeHandler.thisBridge.bridgeAPI().runProductCommand();
263                 boolean success = false;
264                 if (bcp instanceof SCrunProductCommand) {
265                     synchronized (bcp) {
266                         if (bcp.setNodeIdAndParameters(productBridgeIndex.toInt(), mainParameter, functionalParameters)
267                                 && thisBridgeHandler.thisBridge.bridgeCommunicate(bcp)
268                                 && bcp.isCommunicationSuccessful()) {
269                             success = true;
270                             if (thisBridgeHandler.bridgeParameters.actuators
271                                     .autoRefresh(thisBridgeHandler.thisBridge)) {
272                                 LOGGER.trace("handleCommand(): actuator position will be updated via polling.");
273                             }
274                             if (existingProducts.update(((SCrunProductCommand) bcp).getProduct())) {
275                                 LOGGER.trace("handleCommand(): actuator position immediate update requested.");
276                             }
277                         }
278                     }
279                 }
280                 LOGGER.debug("handleCommand(): sendCommand() finished {}.",
281                         (success ? "successfully" : "with failure"));
282             } else {
283                 LOGGER.info("handleCommand(): ignoring command '{}' for channel id '{}'.", command, channelId);
284             }
285         } while (false); // common exit
286         return newValue;
287     }
288 }